@nick3/copilot-api 1.0.3
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 +599 -0
- package/dist/accounts-manager-CxuKJ4qv.js +1326 -0
- package/dist/accounts-manager-CxuKJ4qv.js.map +1 -0
- package/dist/admin/assets/index-CFHE7_Zc.js +55 -0
- package/dist/admin/assets/index-DwLAH2FE.css +1 -0
- package/dist/admin/index.html +27 -0
- package/dist/admin/vite.svg +1 -0
- package/dist/main.js +746 -0
- package/dist/main.js.map +1 -0
- package/dist/server-D05YP0C0.js +5513 -0
- package/dist/server-D05YP0C0.js.map +1 -0
- package/package.json +76 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,746 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { GITHUB_APP_SCOPES, GITHUB_BASE_URL, GITHUB_CLIENT_ID, HTTPError, PATHS, accountsManager, addAccountToRegistry, cacheVSCodeVersion, ensurePaths, getCopilotUsage, getGitHubUser, isFreeModelLoadBalancingEnabled, listAccountsFromRegistry, loadAccountToken, mergeConfigWithDefaults, removeAccountFromRegistry, removeAccountToken, saveAccountToken, saveRegistry, sleep, standardHeaders, state } from "./accounts-manager-CxuKJ4qv.js";
|
|
3
|
+
import { defineCommand, runMain } from "citty";
|
|
4
|
+
import consola from "consola";
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import os from "node:os";
|
|
7
|
+
import clipboard from "clipboardy";
|
|
8
|
+
import { serve } from "srvx";
|
|
9
|
+
import invariant from "tiny-invariant";
|
|
10
|
+
import { getProxyForUrl } from "proxy-from-env";
|
|
11
|
+
import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
|
|
12
|
+
import { execSync } from "node:child_process";
|
|
13
|
+
import process$1 from "node:process";
|
|
14
|
+
|
|
15
|
+
//#region src/lib/types/account.ts
|
|
16
|
+
const ACCOUNT_TYPE_VALUES = [
|
|
17
|
+
"individual",
|
|
18
|
+
"business",
|
|
19
|
+
"enterprise"
|
|
20
|
+
];
|
|
21
|
+
function isAccountType(value) {
|
|
22
|
+
return typeof value === "string" && ACCOUNT_TYPE_VALUES.includes(value);
|
|
23
|
+
}
|
|
24
|
+
function parseAccountType(value) {
|
|
25
|
+
if (!isAccountType(value)) throw new Error(`Invalid account type: ${String(value)}. Valid values: ${ACCOUNT_TYPE_VALUES.join(", ")}`);
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/services/github/get-device-code.ts
|
|
31
|
+
async function getDeviceCode() {
|
|
32
|
+
const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: standardHeaders(),
|
|
35
|
+
body: JSON.stringify({
|
|
36
|
+
client_id: GITHUB_CLIENT_ID,
|
|
37
|
+
scope: GITHUB_APP_SCOPES
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) throw new HTTPError("Failed to get device code", response);
|
|
41
|
+
return await response.json();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
45
|
+
//#region src/services/github/poll-access-token.ts
|
|
46
|
+
async function pollAccessToken(deviceCode) {
|
|
47
|
+
const sleepDuration = (deviceCode.interval + 1) * 1e3;
|
|
48
|
+
consola.debug(`Polling access token with interval of ${sleepDuration}ms`);
|
|
49
|
+
while (true) {
|
|
50
|
+
const response = await fetch(`${GITHUB_BASE_URL}/login/oauth/access_token`, {
|
|
51
|
+
method: "POST",
|
|
52
|
+
headers: standardHeaders(),
|
|
53
|
+
body: JSON.stringify({
|
|
54
|
+
client_id: GITHUB_CLIENT_ID,
|
|
55
|
+
device_code: deviceCode.device_code,
|
|
56
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code"
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
await sleep(sleepDuration);
|
|
61
|
+
consola.error("Failed to poll access token:", await response.text());
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const json = await response.json();
|
|
65
|
+
consola.debug("Polling access token response:", json);
|
|
66
|
+
const { access_token } = json;
|
|
67
|
+
if (access_token) return access_token;
|
|
68
|
+
else await sleep(sleepDuration);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/auth.ts
|
|
74
|
+
/**
|
|
75
|
+
* Fetch quota info for an account (used by auth ls -q)
|
|
76
|
+
*/
|
|
77
|
+
async function fetchQuotaInfo(account) {
|
|
78
|
+
try {
|
|
79
|
+
const token = await loadAccountToken(account.id);
|
|
80
|
+
if (!token) return " | Quota: (no token)";
|
|
81
|
+
const premium = (await getCopilotUsage({
|
|
82
|
+
githubToken: token,
|
|
83
|
+
accountType: account.accountType
|
|
84
|
+
})).quota_snapshots.premium_interactions;
|
|
85
|
+
return premium.unlimited ? " | Quota: unlimited" : ` | Quota: ${premium.remaining}/${premium.entitlement}`;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
consola.debug(`Failed to fetch quota for ${account.id}:`, error);
|
|
88
|
+
return " | Quota: (failed to fetch)";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* auth add - Add a new GitHub Copilot account
|
|
93
|
+
*/
|
|
94
|
+
const authAdd = defineCommand({
|
|
95
|
+
meta: {
|
|
96
|
+
name: "add",
|
|
97
|
+
description: "Add a new GitHub Copilot account"
|
|
98
|
+
},
|
|
99
|
+
args: {
|
|
100
|
+
"account-type": {
|
|
101
|
+
alias: "a",
|
|
102
|
+
type: "string",
|
|
103
|
+
default: "individual",
|
|
104
|
+
description: "Account type (individual, business, enterprise)"
|
|
105
|
+
},
|
|
106
|
+
verbose: {
|
|
107
|
+
alias: "v",
|
|
108
|
+
type: "boolean",
|
|
109
|
+
default: false,
|
|
110
|
+
description: "Enable verbose logging"
|
|
111
|
+
},
|
|
112
|
+
"show-token": {
|
|
113
|
+
type: "boolean",
|
|
114
|
+
default: false,
|
|
115
|
+
description: "Show GitHub token after auth"
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
async run({ args }) {
|
|
119
|
+
if (args.verbose) {
|
|
120
|
+
consola.level = 5;
|
|
121
|
+
consola.info("Verbose logging enabled");
|
|
122
|
+
}
|
|
123
|
+
state.showToken = args["show-token"];
|
|
124
|
+
let accountType;
|
|
125
|
+
try {
|
|
126
|
+
accountType = parseAccountType(args["account-type"]);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
consola.error(error instanceof Error ? error.message : String(error));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
await ensurePaths();
|
|
132
|
+
consola.info("Starting GitHub device code authentication...");
|
|
133
|
+
const deviceResponse = await getDeviceCode();
|
|
134
|
+
consola.debug("Device code response:", deviceResponse);
|
|
135
|
+
consola.info(`Please enter the code "${deviceResponse.user_code}" at ${deviceResponse.verification_uri}`);
|
|
136
|
+
const token = await pollAccessToken(deviceResponse);
|
|
137
|
+
if (state.showToken) consola.info("GitHub token:", token);
|
|
138
|
+
const accountId = (await getGitHubUser({
|
|
139
|
+
githubToken: token,
|
|
140
|
+
accountType
|
|
141
|
+
})).login;
|
|
142
|
+
await saveAccountToken(accountId, token);
|
|
143
|
+
const existingAccounts = await listAccountsFromRegistry();
|
|
144
|
+
if (existingAccounts.some((acc) => acc.id === accountId)) {
|
|
145
|
+
await saveRegistry({
|
|
146
|
+
version: 1,
|
|
147
|
+
accounts: existingAccounts
|
|
148
|
+
});
|
|
149
|
+
consola.success(`Account "${accountId}" already exists. Token has been updated.`);
|
|
150
|
+
} else {
|
|
151
|
+
await addAccountToRegistry({
|
|
152
|
+
id: accountId,
|
|
153
|
+
accountType,
|
|
154
|
+
addedAt: Date.now()
|
|
155
|
+
});
|
|
156
|
+
consola.success(`Account "${accountId}" added successfully!`);
|
|
157
|
+
}
|
|
158
|
+
consola.info(`Account type: ${accountType}`);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
/**
|
|
162
|
+
* auth ls - List all registered accounts
|
|
163
|
+
*/
|
|
164
|
+
const authLs = defineCommand({
|
|
165
|
+
meta: {
|
|
166
|
+
name: "ls",
|
|
167
|
+
description: "List all registered accounts"
|
|
168
|
+
},
|
|
169
|
+
args: {
|
|
170
|
+
"show-quota": {
|
|
171
|
+
alias: "q",
|
|
172
|
+
type: "boolean",
|
|
173
|
+
default: false,
|
|
174
|
+
description: "Show quota information (requires API call)"
|
|
175
|
+
},
|
|
176
|
+
verbose: {
|
|
177
|
+
alias: "v",
|
|
178
|
+
type: "boolean",
|
|
179
|
+
default: false,
|
|
180
|
+
description: "Enable verbose logging"
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
async run({ args }) {
|
|
184
|
+
if (args.verbose) consola.level = 5;
|
|
185
|
+
await ensurePaths();
|
|
186
|
+
const accounts = await listAccountsFromRegistry();
|
|
187
|
+
if (accounts.length === 0) {
|
|
188
|
+
consola.info("No accounts registered. Use 'auth add' to add an account.");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
consola.info(`Found ${accounts.length} account(s):\n`);
|
|
192
|
+
for (const [i, account] of accounts.entries()) {
|
|
193
|
+
const addedDate = new Date(account.addedAt).toLocaleString();
|
|
194
|
+
const quotaInfo = args["show-quota"] ? await fetchQuotaInfo(account) : "";
|
|
195
|
+
console.log(` ${i + 1}. ${account.id} (${account.accountType})${quotaInfo}`);
|
|
196
|
+
console.log(` Added: ${addedDate}\n`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
/**
|
|
201
|
+
* auth rm - Remove an account
|
|
202
|
+
*/
|
|
203
|
+
const authRm = defineCommand({
|
|
204
|
+
meta: {
|
|
205
|
+
name: "rm",
|
|
206
|
+
description: "Remove an account"
|
|
207
|
+
},
|
|
208
|
+
args: {
|
|
209
|
+
target: {
|
|
210
|
+
type: "positional",
|
|
211
|
+
description: "Account ID or index (1-based)",
|
|
212
|
+
required: true
|
|
213
|
+
},
|
|
214
|
+
force: {
|
|
215
|
+
alias: "f",
|
|
216
|
+
type: "boolean",
|
|
217
|
+
default: false,
|
|
218
|
+
description: "Skip confirmation prompt"
|
|
219
|
+
},
|
|
220
|
+
verbose: {
|
|
221
|
+
alias: "v",
|
|
222
|
+
type: "boolean",
|
|
223
|
+
default: false,
|
|
224
|
+
description: "Enable verbose logging"
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
async run({ args }) {
|
|
228
|
+
if (args.verbose) consola.level = 5;
|
|
229
|
+
await ensurePaths();
|
|
230
|
+
const target = args.target;
|
|
231
|
+
const accounts = await listAccountsFromRegistry();
|
|
232
|
+
if (accounts.length === 0) {
|
|
233
|
+
consola.error("No accounts to remove.");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
let accountToRemove;
|
|
237
|
+
const index = Number.parseInt(target, 10);
|
|
238
|
+
if (!Number.isNaN(index) && index >= 1 && index <= accounts.length) accountToRemove = {
|
|
239
|
+
id: accounts[index - 1].id,
|
|
240
|
+
index: index - 1
|
|
241
|
+
};
|
|
242
|
+
else {
|
|
243
|
+
const foundIndex = accounts.findIndex((acc) => acc.id === target);
|
|
244
|
+
if (foundIndex !== -1) accountToRemove = {
|
|
245
|
+
id: accounts[foundIndex].id,
|
|
246
|
+
index: foundIndex
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
if (!accountToRemove) {
|
|
250
|
+
consola.error(`Account "${target}" not found.`);
|
|
251
|
+
consola.info("Use 'auth ls' to see available accounts.");
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (!args.force) {
|
|
255
|
+
if (!await consola.prompt(`Are you sure you want to remove account "${accountToRemove.id}"?`, { type: "confirm" })) {
|
|
256
|
+
consola.info("Cancelled.");
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
await removeAccountToken(accountToRemove.id);
|
|
261
|
+
await removeAccountFromRegistry(accountToRemove.id);
|
|
262
|
+
consola.success(`Account "${accountToRemove.id}" removed.`);
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
/**
|
|
266
|
+
* Main auth command with subcommands
|
|
267
|
+
*/
|
|
268
|
+
const auth = defineCommand({
|
|
269
|
+
meta: {
|
|
270
|
+
name: "auth",
|
|
271
|
+
description: "Manage GitHub Copilot accounts"
|
|
272
|
+
},
|
|
273
|
+
subCommands: {
|
|
274
|
+
add: authAdd,
|
|
275
|
+
ls: authLs,
|
|
276
|
+
rm: authRm
|
|
277
|
+
},
|
|
278
|
+
args: {
|
|
279
|
+
"account-type": {
|
|
280
|
+
alias: "a",
|
|
281
|
+
type: "string",
|
|
282
|
+
default: "individual",
|
|
283
|
+
description: "Account type (individual, business, enterprise)"
|
|
284
|
+
},
|
|
285
|
+
verbose: {
|
|
286
|
+
alias: "v",
|
|
287
|
+
type: "boolean",
|
|
288
|
+
default: false,
|
|
289
|
+
description: "Enable verbose logging"
|
|
290
|
+
},
|
|
291
|
+
"show-token": {
|
|
292
|
+
type: "boolean",
|
|
293
|
+
default: false,
|
|
294
|
+
description: "Show GitHub token after auth"
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
async run(ctx) {
|
|
298
|
+
const firstArg = ctx.rawArgs[0];
|
|
299
|
+
if (!(firstArg === "add" || firstArg === "ls" || firstArg === "rm") && authAdd.run) await authAdd.run(ctx);
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
//#endregion
|
|
304
|
+
//#region src/lib/token.ts
|
|
305
|
+
const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
|
|
306
|
+
const writeGithubToken = (token) => fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token);
|
|
307
|
+
async function setupGitHubToken(options) {
|
|
308
|
+
try {
|
|
309
|
+
const githubToken = await readGithubToken();
|
|
310
|
+
if (githubToken && !options?.force) {
|
|
311
|
+
state.githubToken = githubToken;
|
|
312
|
+
if (state.showToken) consola.info("GitHub token:", githubToken);
|
|
313
|
+
await logUser();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
consola.info("Not logged in, getting new access token");
|
|
317
|
+
const response = await getDeviceCode();
|
|
318
|
+
consola.debug("Device code response:", response);
|
|
319
|
+
consola.info(`Please enter the code "${response.user_code}" in ${response.verification_uri}`);
|
|
320
|
+
const token = await pollAccessToken(response);
|
|
321
|
+
await writeGithubToken(token);
|
|
322
|
+
state.githubToken = token;
|
|
323
|
+
if (state.showToken) consola.info("GitHub token:", token);
|
|
324
|
+
await logUser();
|
|
325
|
+
} catch (error) {
|
|
326
|
+
if (error instanceof HTTPError) {
|
|
327
|
+
consola.error("Failed to get GitHub token:", await error.response.json());
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
consola.error("Failed to get GitHub token:", error);
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function logUser() {
|
|
335
|
+
const user = await getGitHubUser();
|
|
336
|
+
consola.info(`Logged in as ${user.login}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/check-usage.ts
|
|
341
|
+
const checkUsage = defineCommand({
|
|
342
|
+
meta: {
|
|
343
|
+
name: "check-usage",
|
|
344
|
+
description: "Show current GitHub Copilot usage/quota information"
|
|
345
|
+
},
|
|
346
|
+
async run() {
|
|
347
|
+
await ensurePaths();
|
|
348
|
+
await setupGitHubToken();
|
|
349
|
+
try {
|
|
350
|
+
const usage = await getCopilotUsage();
|
|
351
|
+
const premium = usage.quota_snapshots.premium_interactions;
|
|
352
|
+
const premiumTotal = premium.entitlement;
|
|
353
|
+
const premiumUsed = premiumTotal - premium.remaining;
|
|
354
|
+
const premiumPercentUsed = premiumTotal > 0 ? premiumUsed / premiumTotal * 100 : 0;
|
|
355
|
+
const premiumPercentRemaining = premium.percent_remaining;
|
|
356
|
+
function summarizeQuota(name, snap) {
|
|
357
|
+
if (!snap) return `${name}: N/A`;
|
|
358
|
+
const total = snap.entitlement;
|
|
359
|
+
const used = total - snap.remaining;
|
|
360
|
+
const percentUsed = total > 0 ? used / total * 100 : 0;
|
|
361
|
+
const percentRemaining = snap.percent_remaining;
|
|
362
|
+
return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)`;
|
|
363
|
+
}
|
|
364
|
+
const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)`;
|
|
365
|
+
const chatLine = summarizeQuota("Chat", usage.quota_snapshots.chat);
|
|
366
|
+
const completionsLine = summarizeQuota("Completions", usage.quota_snapshots.completions);
|
|
367
|
+
consola.box(`Copilot Usage (plan: ${usage.copilot_plan})\nQuota resets: ${usage.quota_reset_date}\n\nQuotas:\n ${premiumLine}\n ${chatLine}\n ${completionsLine}`);
|
|
368
|
+
} catch (err) {
|
|
369
|
+
consola.error("Failed to fetch Copilot usage:", err);
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/debug.ts
|
|
377
|
+
async function getPackageVersion() {
|
|
378
|
+
try {
|
|
379
|
+
const packageJsonPath = new URL("../package.json", import.meta.url).pathname;
|
|
380
|
+
return JSON.parse(await fs.readFile(packageJsonPath)).version;
|
|
381
|
+
} catch {
|
|
382
|
+
return "unknown";
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function getRuntimeInfo() {
|
|
386
|
+
const isBun = typeof Bun !== "undefined";
|
|
387
|
+
return {
|
|
388
|
+
name: isBun ? "bun" : "node",
|
|
389
|
+
version: isBun ? Bun.version : process.version.slice(1),
|
|
390
|
+
platform: os.platform(),
|
|
391
|
+
arch: os.arch()
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
async function checkTokenExists() {
|
|
395
|
+
try {
|
|
396
|
+
if (!(await fs.stat(PATHS.GITHUB_TOKEN_PATH)).isFile()) return false;
|
|
397
|
+
return (await fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8")).trim().length > 0;
|
|
398
|
+
} catch {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async function getDebugInfo() {
|
|
403
|
+
const [version, tokenExists] = await Promise.all([getPackageVersion(), checkTokenExists()]);
|
|
404
|
+
return {
|
|
405
|
+
version,
|
|
406
|
+
runtime: getRuntimeInfo(),
|
|
407
|
+
paths: {
|
|
408
|
+
APP_DIR: PATHS.APP_DIR,
|
|
409
|
+
GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH
|
|
410
|
+
},
|
|
411
|
+
tokenExists
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
function printDebugInfoPlain(info) {
|
|
415
|
+
consola.info(`copilot-api debug
|
|
416
|
+
|
|
417
|
+
Version: ${info.version}
|
|
418
|
+
Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch})
|
|
419
|
+
|
|
420
|
+
Paths:
|
|
421
|
+
- APP_DIR: ${info.paths.APP_DIR}
|
|
422
|
+
- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH}
|
|
423
|
+
|
|
424
|
+
Token exists: ${info.tokenExists ? "Yes" : "No"}`);
|
|
425
|
+
}
|
|
426
|
+
function printDebugInfoJson(info) {
|
|
427
|
+
console.log(JSON.stringify(info, null, 2));
|
|
428
|
+
}
|
|
429
|
+
async function runDebug(options) {
|
|
430
|
+
const debugInfo = await getDebugInfo();
|
|
431
|
+
if (options.json) printDebugInfoJson(debugInfo);
|
|
432
|
+
else printDebugInfoPlain(debugInfo);
|
|
433
|
+
}
|
|
434
|
+
const debug = defineCommand({
|
|
435
|
+
meta: {
|
|
436
|
+
name: "debug",
|
|
437
|
+
description: "Print debug information about the application"
|
|
438
|
+
},
|
|
439
|
+
args: { json: {
|
|
440
|
+
type: "boolean",
|
|
441
|
+
default: false,
|
|
442
|
+
description: "Output debug information as JSON"
|
|
443
|
+
} },
|
|
444
|
+
run({ args }) {
|
|
445
|
+
return runDebug({ json: args.json });
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
//#endregion
|
|
450
|
+
//#region src/lib/proxy.ts
|
|
451
|
+
function initProxyFromEnv() {
|
|
452
|
+
if (typeof Bun !== "undefined") return;
|
|
453
|
+
try {
|
|
454
|
+
const direct = new Agent();
|
|
455
|
+
const proxies = /* @__PURE__ */ new Map();
|
|
456
|
+
setGlobalDispatcher({
|
|
457
|
+
dispatch(options, handler) {
|
|
458
|
+
try {
|
|
459
|
+
const origin = typeof options.origin === "string" ? new URL(options.origin) : options.origin;
|
|
460
|
+
const raw = getProxyForUrl(origin.toString());
|
|
461
|
+
const proxyUrl = raw && raw.length > 0 ? raw : void 0;
|
|
462
|
+
if (!proxyUrl) {
|
|
463
|
+
consola.debug(`HTTP proxy bypass: ${origin.hostname}`);
|
|
464
|
+
return direct.dispatch(options, handler);
|
|
465
|
+
}
|
|
466
|
+
let agent = proxies.get(proxyUrl);
|
|
467
|
+
if (!agent) {
|
|
468
|
+
agent = new ProxyAgent(proxyUrl);
|
|
469
|
+
proxies.set(proxyUrl, agent);
|
|
470
|
+
}
|
|
471
|
+
let label = proxyUrl;
|
|
472
|
+
try {
|
|
473
|
+
const u = new URL(proxyUrl);
|
|
474
|
+
label = `${u.protocol}//${u.host}`;
|
|
475
|
+
} catch {}
|
|
476
|
+
consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`);
|
|
477
|
+
return agent.dispatch(options, handler);
|
|
478
|
+
} catch {
|
|
479
|
+
return direct.dispatch(options, handler);
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
close() {
|
|
483
|
+
return direct.close();
|
|
484
|
+
},
|
|
485
|
+
destroy() {
|
|
486
|
+
return direct.destroy();
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
consola.debug("HTTP proxy configured from environment (per-URL)");
|
|
490
|
+
} catch (err) {
|
|
491
|
+
consola.debug("Proxy setup skipped:", err);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/lib/shell.ts
|
|
497
|
+
function getShell() {
|
|
498
|
+
const { platform, ppid, env } = process$1;
|
|
499
|
+
if (platform === "win32") {
|
|
500
|
+
try {
|
|
501
|
+
const command = `wmic process get ParentProcessId,Name | findstr "${ppid}"`;
|
|
502
|
+
if (execSync(command, { stdio: "pipe" }).toString().toLowerCase().includes("powershell.exe")) return "powershell";
|
|
503
|
+
} catch {
|
|
504
|
+
return "cmd";
|
|
505
|
+
}
|
|
506
|
+
return "cmd";
|
|
507
|
+
} else {
|
|
508
|
+
const shellPath = env.SHELL;
|
|
509
|
+
if (shellPath) {
|
|
510
|
+
if (shellPath.endsWith("zsh")) return "zsh";
|
|
511
|
+
if (shellPath.endsWith("fish")) return "fish";
|
|
512
|
+
if (shellPath.endsWith("bash")) return "bash";
|
|
513
|
+
}
|
|
514
|
+
return "sh";
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Generates a copy-pasteable script to set multiple environment variables
|
|
519
|
+
* and run a subsequent command.
|
|
520
|
+
* @param {EnvVars} envVars - An object of environment variables to set.
|
|
521
|
+
* @param {string} commandToRun - The command to run after setting the variables.
|
|
522
|
+
* @returns {string} The formatted script string.
|
|
523
|
+
*/
|
|
524
|
+
function generateEnvScript(envVars, commandToRun = "") {
|
|
525
|
+
const shell = getShell();
|
|
526
|
+
const filteredEnvVars = Object.entries(envVars).filter(([, value]) => value !== void 0);
|
|
527
|
+
let commandBlock;
|
|
528
|
+
switch (shell) {
|
|
529
|
+
case "powershell":
|
|
530
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `$env:${key} = ${value}`).join("; ");
|
|
531
|
+
break;
|
|
532
|
+
case "cmd":
|
|
533
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `set ${key}=${value}`).join(" & ");
|
|
534
|
+
break;
|
|
535
|
+
case "fish":
|
|
536
|
+
commandBlock = filteredEnvVars.map(([key, value]) => `set -gx ${key} ${value}`).join("; ");
|
|
537
|
+
break;
|
|
538
|
+
default: {
|
|
539
|
+
const assignments = filteredEnvVars.map(([key, value]) => `${key}=${value}`).join(" ");
|
|
540
|
+
commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : "";
|
|
541
|
+
break;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
if (commandBlock && commandToRun) return `${commandBlock}${shell === "cmd" ? " & " : " && "}${commandToRun}`;
|
|
545
|
+
return commandBlock || commandToRun;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
//#endregion
|
|
549
|
+
//#region src/start.ts
|
|
550
|
+
/**
|
|
551
|
+
* Run the interactive authentication flow to add a new account.
|
|
552
|
+
* Called automatically when no accounts are found.
|
|
553
|
+
*/
|
|
554
|
+
async function runAuthFlow(accountType) {
|
|
555
|
+
consola.warn("No accounts found. Starting authentication flow...");
|
|
556
|
+
const deviceResponse = await getDeviceCode();
|
|
557
|
+
consola.info(`Please enter the code "${deviceResponse.user_code}" at ${deviceResponse.verification_uri}`);
|
|
558
|
+
const token = await pollAccessToken(deviceResponse);
|
|
559
|
+
if (state.showToken) consola.info("GitHub token:", token);
|
|
560
|
+
const accountId = (await getGitHubUser({
|
|
561
|
+
githubToken: token,
|
|
562
|
+
accountType
|
|
563
|
+
})).login;
|
|
564
|
+
await saveAccountToken(accountId, token);
|
|
565
|
+
await addAccountToRegistry({
|
|
566
|
+
id: accountId,
|
|
567
|
+
accountType,
|
|
568
|
+
addedAt: Date.now()
|
|
569
|
+
});
|
|
570
|
+
consola.success(`Account "${accountId}" added successfully!`);
|
|
571
|
+
}
|
|
572
|
+
async function runServer(options) {
|
|
573
|
+
mergeConfigWithDefaults();
|
|
574
|
+
accountsManager.setFreeModelLoadBalancingEnabled(isFreeModelLoadBalancingEnabled());
|
|
575
|
+
if (options.proxyEnv) initProxyFromEnv();
|
|
576
|
+
state.verbose = options.verbose;
|
|
577
|
+
if (options.verbose) {
|
|
578
|
+
consola.level = 5;
|
|
579
|
+
consola.info("Verbose logging enabled");
|
|
580
|
+
}
|
|
581
|
+
state.accountType = options.accountType;
|
|
582
|
+
if (options.accountType !== "individual") consola.info(`Using ${options.accountType} plan GitHub account`);
|
|
583
|
+
state.manualApprove = options.manual;
|
|
584
|
+
state.rateLimitSeconds = options.rateLimit;
|
|
585
|
+
state.rateLimitWait = options.rateLimitWait;
|
|
586
|
+
state.showToken = options.showToken;
|
|
587
|
+
await ensurePaths();
|
|
588
|
+
await cacheVSCodeVersion();
|
|
589
|
+
await accountsManager.initialize(state.vsCodeVersion);
|
|
590
|
+
if (options.githubToken) {
|
|
591
|
+
await accountsManager.setTemporaryAccount(options.githubToken, options.accountType);
|
|
592
|
+
consola.info("Using provided GitHub token as temporary account");
|
|
593
|
+
}
|
|
594
|
+
if (!accountsManager.hasAccounts()) try {
|
|
595
|
+
await runAuthFlow(options.accountType);
|
|
596
|
+
accountsManager.shutdown();
|
|
597
|
+
await accountsManager.initialize(state.vsCodeVersion);
|
|
598
|
+
} catch (error) {
|
|
599
|
+
consola.error("Failed to add account:", error);
|
|
600
|
+
process.exit(1);
|
|
601
|
+
}
|
|
602
|
+
const models = accountsManager.getFirstAccountModels();
|
|
603
|
+
consola.info(`Available models: \n${models?.data.map((model) => `- ${model.id}`).join("\n") ?? "(no models loaded)"}`);
|
|
604
|
+
const serverUrl = `http://localhost:${options.port}`;
|
|
605
|
+
if (options.claudeCode) {
|
|
606
|
+
invariant(models, "Models should be loaded by now");
|
|
607
|
+
const selectedModel = await consola.prompt("Select a model to use with Claude Code", {
|
|
608
|
+
type: "select",
|
|
609
|
+
options: models.data.map((model) => model.id)
|
|
610
|
+
});
|
|
611
|
+
const selectedSmallModel = await consola.prompt("Select a small model to use with Claude Code", {
|
|
612
|
+
type: "select",
|
|
613
|
+
options: models.data.map((model) => model.id)
|
|
614
|
+
});
|
|
615
|
+
const command = generateEnvScript({
|
|
616
|
+
ANTHROPIC_BASE_URL: serverUrl,
|
|
617
|
+
ANTHROPIC_AUTH_TOKEN: "dummy",
|
|
618
|
+
ANTHROPIC_MODEL: selectedModel,
|
|
619
|
+
ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel,
|
|
620
|
+
ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel,
|
|
621
|
+
ANTHROPIC_DEFAULT_HAIKU_MODEL: selectedSmallModel,
|
|
622
|
+
DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1",
|
|
623
|
+
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
|
|
624
|
+
}, "claude");
|
|
625
|
+
try {
|
|
626
|
+
clipboard.writeSync(command);
|
|
627
|
+
consola.success("Copied Claude Code command to clipboard!");
|
|
628
|
+
} catch {
|
|
629
|
+
consola.warn("Failed to copy to clipboard. Here is the Claude Code command:");
|
|
630
|
+
consola.log(command);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`);
|
|
634
|
+
const { server } = await import("./server-D05YP0C0.js");
|
|
635
|
+
serve({
|
|
636
|
+
fetch: server.fetch,
|
|
637
|
+
port: options.port,
|
|
638
|
+
bun: { idleTimeout: 0 }
|
|
639
|
+
});
|
|
640
|
+
}
|
|
641
|
+
const start = defineCommand({
|
|
642
|
+
meta: {
|
|
643
|
+
name: "start",
|
|
644
|
+
description: "Start the Copilot API server"
|
|
645
|
+
},
|
|
646
|
+
args: {
|
|
647
|
+
port: {
|
|
648
|
+
alias: "p",
|
|
649
|
+
type: "string",
|
|
650
|
+
default: "4141",
|
|
651
|
+
description: "Port to listen on"
|
|
652
|
+
},
|
|
653
|
+
verbose: {
|
|
654
|
+
alias: "v",
|
|
655
|
+
type: "boolean",
|
|
656
|
+
default: false,
|
|
657
|
+
description: "Enable verbose logging"
|
|
658
|
+
},
|
|
659
|
+
"account-type": {
|
|
660
|
+
alias: "a",
|
|
661
|
+
type: "string",
|
|
662
|
+
default: "individual",
|
|
663
|
+
description: "Account type to use (individual, business, enterprise)"
|
|
664
|
+
},
|
|
665
|
+
manual: {
|
|
666
|
+
type: "boolean",
|
|
667
|
+
default: false,
|
|
668
|
+
description: "Enable manual request approval"
|
|
669
|
+
},
|
|
670
|
+
"rate-limit": {
|
|
671
|
+
alias: "r",
|
|
672
|
+
type: "string",
|
|
673
|
+
description: "Rate limit in seconds between requests"
|
|
674
|
+
},
|
|
675
|
+
wait: {
|
|
676
|
+
alias: "w",
|
|
677
|
+
type: "boolean",
|
|
678
|
+
default: false,
|
|
679
|
+
description: "Wait instead of error when rate limit is hit. Has no effect if rate limit is not set"
|
|
680
|
+
},
|
|
681
|
+
"github-token": {
|
|
682
|
+
alias: "g",
|
|
683
|
+
type: "string",
|
|
684
|
+
description: "Provide GitHub token directly (must be generated using the `auth` subcommand)"
|
|
685
|
+
},
|
|
686
|
+
"claude-code": {
|
|
687
|
+
alias: "c",
|
|
688
|
+
type: "boolean",
|
|
689
|
+
default: false,
|
|
690
|
+
description: "Generate a command to launch Claude Code with Copilot API config"
|
|
691
|
+
},
|
|
692
|
+
"show-token": {
|
|
693
|
+
type: "boolean",
|
|
694
|
+
default: false,
|
|
695
|
+
description: "Show GitHub and Copilot tokens on fetch and refresh"
|
|
696
|
+
},
|
|
697
|
+
"proxy-env": {
|
|
698
|
+
type: "boolean",
|
|
699
|
+
default: false,
|
|
700
|
+
description: "Initialize proxy from environment variables"
|
|
701
|
+
}
|
|
702
|
+
},
|
|
703
|
+
run({ args }) {
|
|
704
|
+
const rateLimitRaw = args["rate-limit"];
|
|
705
|
+
const rateLimit = rateLimitRaw === void 0 ? void 0 : Number.parseInt(rateLimitRaw, 10);
|
|
706
|
+
let accountType;
|
|
707
|
+
try {
|
|
708
|
+
accountType = parseAccountType(args["account-type"]);
|
|
709
|
+
} catch (error) {
|
|
710
|
+
consola.error(error instanceof Error ? error.message : String(error));
|
|
711
|
+
process.exit(1);
|
|
712
|
+
}
|
|
713
|
+
return runServer({
|
|
714
|
+
port: Number.parseInt(args.port, 10),
|
|
715
|
+
verbose: args.verbose,
|
|
716
|
+
accountType,
|
|
717
|
+
manual: args.manual,
|
|
718
|
+
rateLimit,
|
|
719
|
+
rateLimitWait: args.wait,
|
|
720
|
+
githubToken: args["github-token"],
|
|
721
|
+
claudeCode: args["claude-code"],
|
|
722
|
+
showToken: args["show-token"],
|
|
723
|
+
proxyEnv: args["proxy-env"]
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
|
|
728
|
+
//#endregion
|
|
729
|
+
//#region src/main.ts
|
|
730
|
+
const main = defineCommand({
|
|
731
|
+
meta: {
|
|
732
|
+
name: "copilot-api",
|
|
733
|
+
description: "A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools."
|
|
734
|
+
},
|
|
735
|
+
subCommands: {
|
|
736
|
+
auth,
|
|
737
|
+
start,
|
|
738
|
+
"check-usage": checkUsage,
|
|
739
|
+
debug
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
await runMain(main);
|
|
743
|
+
|
|
744
|
+
//#endregion
|
|
745
|
+
export { };
|
|
746
|
+
//# sourceMappingURL=main.js.map
|