xc-copilot-api 1.1.3 → 1.2.1

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
@@ -35,7 +35,7 @@ A reverse-engineered proxy for the GitHub Copilot API that exposes it as an Open
35
35
  ## Features
36
36
 
37
37
  - **OpenAI & Anthropic Compatibility**: Exposes GitHub Copilot as an OpenAI-compatible (`/v1/chat/completions`, `/v1/models`, `/v1/embeddings`) and Anthropic-compatible (`/v1/messages`) API.
38
- - **Claude Code Integration**: Easily configure and launch [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) to use Copilot as its backend with a simple command-line flag (`--claude-code`).
38
+ - **Claude Code & Codex CLI Integration**: Configure [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) or Codex CLI to use Copilot as backend with `config --claude` or `config --codex`.
39
39
  - **Usage Dashboard**: A web-based dashboard to monitor your Copilot API usage, view quotas, and see detailed statistics.
40
40
  - **Rate Limit Control**: Manage API usage with rate-limiting options (`--rate-limit`) and a waiting mechanism (`--wait`) to prevent errors from rapid requests.
41
41
  - **Manual Request Approval**: Manually approve or deny each API request for fine-grained control over usage (`--manual`).
@@ -188,10 +188,28 @@ Copilot API now uses a subcommand structure with these main commands:
188
188
 
189
189
  - `start`: Start the Copilot API server. This command will also handle authentication if needed.
190
190
  - `auth`: Run GitHub authentication flow without starting the server. This is typically used if you need to generate a token for use with the `--github-token` option, especially in non-interactive environments.
191
+ - `config`: Configure external AI tools (Claude Code, Codex CLI) to use Copilot API.
191
192
  - `check-usage`: Show your current GitHub Copilot usage and quota information directly in the terminal (no server required).
192
193
  - `debug`: Display diagnostic information including version, runtime details, file paths, and authentication status. Useful for troubleshooting and support.
193
194
 
194
- A separate **[`xc-copilot-api-daemon`](docs/daemon.md)** CLI is available for managing the server as a background daemon (install, status, restart, stop, uninstall, logs) on macOS, Linux, and Windows.
195
+ ### Running as a Background Service
196
+
197
+ Use [easy-service](https://github.com/billxc/easy-service) to run as a background service on macOS, Linux, and Windows:
198
+
199
+ ```bash
200
+ # Install easy-service
201
+ uv tool install git+https://github.com/billxc/easy-service.git
202
+
203
+ # Install and start the service
204
+ easy-service install copilot-api -- npx -y xc-copilot-api@latest start
205
+
206
+ # Manage the service
207
+ easy-service status copilot-api
208
+ easy-service logs copilot-api -f
209
+ easy-service restart copilot-api
210
+ easy-service stop copilot-api
211
+ easy-service uninstall copilot-api
212
+ ```
195
213
 
196
214
  ## Command Line Options
197
215
 
@@ -208,10 +226,16 @@ The following command line options are available for the `start` command:
208
226
  | --rate-limit | Rate limit in seconds between requests | none | -r |
209
227
  | --wait | Wait instead of error when rate limit is hit | false | -w |
210
228
  | --github-token | Provide GitHub token directly (must be generated using the `auth` subcommand) | none | -g |
211
- | --claude-code | Generate a command to launch Claude Code with Copilot API config | false | -c |
212
229
  | --show-token | Show GitHub and Copilot tokens on fetch and refresh | false | none |
213
230
  | --proxy-env | Initialize proxy from environment variables | false | none |
214
231
 
232
+ ### Config Command Options
233
+
234
+ | Option | Description | Default | Alias |
235
+ | ------- | ------------------------------------------------- | ------- | ----- |
236
+ | --claude | Configure Claude Code (`~/.claude/settings.json`) | false | -c |
237
+ | --codex | Configure Codex CLI (`~/.codex/config.toml`) | false | -x |
238
+
215
239
  ### Auth Command Options
216
240
 
217
241
  | Option | Description | Default | Alias |
@@ -328,44 +352,29 @@ The dashboard provides a user-friendly interface to view your Copilot usage data
328
352
 
329
353
  ## Using with Claude Code
330
354
 
331
- This proxy can be used to power [Claude Code](https://docs.anthropic.com/en/claude-code), an experimental conversational AI assistant for developers from Anthropic.
332
-
333
- There are two ways to configure Claude Code to use this proxy:
334
-
335
- ### Interactive Setup with `--claude-code` flag
355
+ This proxy can be used to power [Claude Code](https://docs.anthropic.com/en/claude-code).
336
356
 
337
- To get started, run the `start` command with the `--claude-code` flag:
357
+ The quickest way to get started:
338
358
 
339
359
  ```sh
340
- npx xc-copilot-api@latest start --claude-code
341
- ```
342
-
343
- You will be prompted to select a primary model and a "small, fast" model for background tasks. After selecting the models, a command will be copied to your clipboard. This command sets the necessary environment variables for Claude Code to use the proxy.
360
+ # Start the server
361
+ npx xc-copilot-api@latest start
344
362
 
345
- Paste and run this command in a new terminal to launch Claude Code.
363
+ # Configure Claude Code (writes to ~/.claude/settings.json)
364
+ npx xc-copilot-api@latest config --claude
365
+ ```
346
366
 
347
- ### Manual Configuration with `settings.json`
367
+ This merges the required `ANTHROPIC_BASE_URL` and `ANTHROPIC_AUTH_TOKEN` into your existing settings without overwriting other configuration. A backup of your original config is saved as `settings.json.bak`.
348
368
 
349
- Alternatively, you can configure Claude Code by creating a `.claude/settings.json` file in your project's root directory. This file should contain the environment variables needed by Claude Code. This way you don't need to run the interactive setup every time.
369
+ > **Note:** Copilot API natively understands Claude model names (e.g. `claude-sonnet-4.5`, `opus[1m]`), so you don't need to set `ANTHROPIC_MODEL` or other model environment variables they just work.
350
370
 
351
- Here is an example `.claude/settings.json` file:
371
+ If you prefer manual configuration, add the following to your `~/.claude/settings.json`:
352
372
 
353
373
  ```json
354
374
  {
355
375
  "env": {
356
376
  "ANTHROPIC_BASE_URL": "http://localhost:4141",
357
- "ANTHROPIC_AUTH_TOKEN": "dummy",
358
- "ANTHROPIC_MODEL": "gpt-4.1",
359
- "ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-4.1",
360
- "ANTHROPIC_SMALL_FAST_MODEL": "gpt-4.1",
361
- "ANTHROPIC_DEFAULT_HAIKU_MODEL": "gpt-4.1",
362
- "DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1",
363
- "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
364
- },
365
- "permissions": {
366
- "deny": [
367
- "WebSearch"
368
- ]
377
+ "ANTHROPIC_AUTH_TOKEN": "Powered by xc copilot"
369
378
  }
370
379
  }
371
380
  ```
@@ -374,6 +383,30 @@ You can find more options here: [Claude Code settings](https://docs.anthropic.co
374
383
 
375
384
  You can also read more about IDE integration here: [Add Claude Code to your IDE](https://docs.anthropic.com/en/docs/claude-code/ide-integrations)
376
385
 
386
+ ## Using with Codex CLI
387
+
388
+ ```sh
389
+ # Start the server
390
+ npx xc-copilot-api@latest start
391
+
392
+ # Configure Codex CLI (writes to ~/.codex/config.toml)
393
+ npx xc-copilot-api@latest config --codex
394
+ ```
395
+
396
+ A backup of your original config is saved as `config.toml.bak`.
397
+
398
+ If you prefer manual configuration, add the following to your `~/.codex/config.toml`:
399
+
400
+ ```toml
401
+ model = "gpt-5.4"
402
+ model_provider = "copilot-api"
403
+
404
+ [model_providers.copilot-api]
405
+ name = "copilot-api"
406
+ base_url = "http://localhost:4141/v1"
407
+ wire_api = "responses"
408
+ ```
409
+
377
410
  ## Running from Source
378
411
 
379
412
  The project can be run from source in several ways:
package/dist/main.js CHANGED
@@ -1,17 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import { defineCommand, runMain } from "citty";
3
- import { execSync } from "node:child_process";
4
- import os from "node:os";
5
- import path from "node:path";
6
3
  import consola from "consola";
7
- import fs from "node:fs/promises";
4
+ import fs, { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
5
+ import os, { homedir } from "node:os";
6
+ import path, { join } from "node:path";
8
7
  import { randomUUID } from "node:crypto";
9
- import clipboard from "clipboardy";
10
8
  import { serve } from "srvx";
11
- import invariant from "tiny-invariant";
12
9
  import { getProxyForUrl } from "proxy-from-env";
13
10
  import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
14
- import process$1 from "node:process";
15
11
  import { Hono } from "hono";
16
12
  import { cors } from "hono/cors";
17
13
  import { streamSSE } from "hono/streaming";
@@ -21,7 +17,7 @@ import "hono/logger";
21
17
 
22
18
  //#region package.json
23
19
  var name = "xc-copilot-api";
24
- var version = "1.1.3";
20
+ var version = "1.2.1";
25
21
  var description = "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code and Codex";
26
22
  var keywords = [
27
23
  "proxy",
@@ -36,10 +32,7 @@ var repository = {
36
32
  };
37
33
  var author = "Xiaochen <wxc9312@gmail.com>";
38
34
  var type = "module";
39
- var bin = {
40
- "xc-copilot-api": "./dist/main.js",
41
- "xc-copilot-api-daemon": "./dist/daemon-main.js"
42
- };
35
+ var bin = { "xc-copilot-api": "./dist/main.js" };
43
36
  var files = ["dist"];
44
37
  var scripts = {
45
38
  "build": "tsdown",
@@ -57,14 +50,12 @@ var simple_git_hooks = {};
57
50
  var lint_staged = {};
58
51
  var dependencies = {
59
52
  "citty": "^0.1.6",
60
- "clipboardy": "^5.0.0",
61
53
  "consola": "^3.4.2",
62
54
  "fetch-event-stream": "^0.1.5",
63
55
  "gpt-tokenizer": "^3.0.1",
64
56
  "hono": "^4.9.9",
65
57
  "proxy-from-env": "^1.1.0",
66
58
  "srvx": "^0.8.9",
67
- "tiny-invariant": "^1.3.3",
68
59
  "undici": "^7.16.0",
69
60
  "zod": "^4.1.11"
70
61
  };
@@ -469,6 +460,123 @@ const checkUsage = defineCommand({
469
460
  }
470
461
  });
471
462
 
463
+ //#endregion
464
+ //#region src/config.ts
465
+ async function backupFile(filePath) {
466
+ try {
467
+ const backupPath = `${filePath}.bak`;
468
+ await copyFile(filePath, backupPath);
469
+ consola.info(`Backup saved: ${backupPath}`);
470
+ } catch {}
471
+ }
472
+ async function configureClaude(port) {
473
+ const configDir = join(homedir(), ".claude");
474
+ const configPath = join(configDir, "settings.json");
475
+ await mkdir(configDir, { recursive: true });
476
+ let existing = {};
477
+ try {
478
+ const raw = await readFile(configPath, "utf-8");
479
+ existing = JSON.parse(raw);
480
+ } catch (error) {
481
+ if (error.code !== "ENOENT") {
482
+ consola.error(`Failed to parse ${configPath}. Please fix it manually.`);
483
+ process.exit(1);
484
+ }
485
+ }
486
+ const existingEnv = typeof existing.env === "object" && existing.env !== null ? existing.env : {};
487
+ const merged = {
488
+ ...existing,
489
+ env: {
490
+ ...existingEnv,
491
+ ANTHROPIC_BASE_URL: `http://localhost:${port}`,
492
+ ANTHROPIC_AUTH_TOKEN: "Powered by xc copilot"
493
+ },
494
+ model: "opus[1m]"
495
+ };
496
+ await backupFile(configPath);
497
+ await writeFile(configPath, JSON.stringify(merged, null, 2) + "\n", "utf-8");
498
+ consola.success(`Claude Code configured: ${configPath}`);
499
+ }
500
+ /**
501
+ * Set or replace a top-level key in a TOML string.
502
+ * If the key exists, its value is updated in place. Otherwise appended after
503
+ * the last existing top-level key (before the first [section]).
504
+ */
505
+ function setTomlTopLevelKey(content, key, value) {
506
+ const pattern = new RegExp(`^${key}\\s*=.*$`, "m");
507
+ const line = `${key} = ${JSON.stringify(value)}`;
508
+ if (pattern.test(content)) return content.replace(pattern, line);
509
+ const sectionMatch = content.match(/^\[/m);
510
+ if (sectionMatch?.index !== void 0) return content.slice(0, sectionMatch.index) + line + "\n" + content.slice(sectionMatch.index);
511
+ return content.trimEnd() + "\n" + line + "\n";
512
+ }
513
+ /**
514
+ * Set or replace an entire [section] block in a TOML string.
515
+ * Replaces everything from the header to the next section (or EOF).
516
+ */
517
+ function setTomlSection(content, header, body) {
518
+ const escaped = header.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
519
+ const pattern = new RegExp(`^\\[${escaped}\\]\\n[^[]*`, "m");
520
+ const block = `[${header}]\n${body}\n\n`;
521
+ if (pattern.test(content)) return content.replace(pattern, block);
522
+ return content.trimEnd() + "\n\n" + block;
523
+ }
524
+ async function configureCodex(port) {
525
+ const configDir = join(homedir(), ".codex");
526
+ const configPath = join(configDir, "config.toml");
527
+ await mkdir(configDir, { recursive: true });
528
+ let content = "";
529
+ try {
530
+ content = await readFile(configPath, "utf-8");
531
+ } catch (error) {
532
+ if (error.code !== "ENOENT") {
533
+ consola.error(`Failed to read ${configPath}. Please fix it manually.`);
534
+ process.exit(1);
535
+ }
536
+ }
537
+ content = setTomlTopLevelKey(content, "model", "gpt-5.4");
538
+ content = setTomlTopLevelKey(content, "model_provider", "copilot-api");
539
+ content = setTomlSection(content, "model_providers.copilot-api", `name = "copilot-api"\nbase_url = "http://localhost:${port}/v1"\nwire_api = "responses"`);
540
+ await backupFile(configPath);
541
+ await writeFile(configPath, content, "utf-8");
542
+ consola.success(`Codex CLI configured: ${configPath}`);
543
+ }
544
+ const config = defineCommand({
545
+ meta: {
546
+ name: "config",
547
+ description: "Configure external AI tools (Claude Code, Codex CLI) to use Copilot API"
548
+ },
549
+ args: {
550
+ claude: {
551
+ alias: "c",
552
+ type: "boolean",
553
+ default: false,
554
+ description: "Configure Claude Code (~/.claude/settings.json)"
555
+ },
556
+ codex: {
557
+ alias: "x",
558
+ type: "boolean",
559
+ default: false,
560
+ description: "Configure Codex CLI (~/.codex/config.toml)"
561
+ },
562
+ port: {
563
+ alias: "p",
564
+ type: "string",
565
+ default: "4141",
566
+ description: "Port the Copilot API server listens on"
567
+ }
568
+ },
569
+ async run({ args }) {
570
+ if (!args.claude && !args.codex) {
571
+ consola.error("Specify at least one: --claude or --codex");
572
+ process.exit(1);
573
+ }
574
+ const port = Number.parseInt(args.port, 10);
575
+ if (args.claude) await configureClaude(port);
576
+ if (args.codex) await configureCodex(port);
577
+ }
578
+ });
579
+
472
580
  //#endregion
473
581
  //#region src/debug.ts
474
582
  async function getPackageVersion() {
@@ -589,59 +697,6 @@ function initProxyFromEnv() {
589
697
  }
590
698
  }
591
699
 
592
- //#endregion
593
- //#region src/lib/shell.ts
594
- function getShell() {
595
- const { platform, ppid, env } = process$1;
596
- if (platform === "win32") {
597
- try {
598
- const command = `wmic process get ParentProcessId,Name | findstr "${ppid}"`;
599
- if (execSync(command, { stdio: "pipe" }).toString().toLowerCase().includes("powershell.exe")) return "powershell";
600
- } catch {
601
- return "cmd";
602
- }
603
- return "cmd";
604
- } else {
605
- const shellPath = env.SHELL;
606
- if (shellPath) {
607
- if (shellPath.endsWith("zsh")) return "zsh";
608
- if (shellPath.endsWith("fish")) return "fish";
609
- if (shellPath.endsWith("bash")) return "bash";
610
- }
611
- return "sh";
612
- }
613
- }
614
- /**
615
- * Generates a copy-pasteable script to set multiple environment variables
616
- * and run a subsequent command.
617
- * @param {EnvVars} envVars - An object of environment variables to set.
618
- * @param {string} commandToRun - The command to run after setting the variables.
619
- * @returns {string} The formatted script string.
620
- */
621
- function generateEnvScript(envVars, commandToRun = "") {
622
- const shell = getShell();
623
- const filteredEnvVars = Object.entries(envVars).filter(([, value]) => value !== void 0);
624
- let commandBlock;
625
- switch (shell) {
626
- case "powershell":
627
- commandBlock = filteredEnvVars.map(([key, value]) => `$env:${key} = ${value}`).join("; ");
628
- break;
629
- case "cmd":
630
- commandBlock = filteredEnvVars.map(([key, value]) => `set ${key}=${value}`).join(" & ");
631
- break;
632
- case "fish":
633
- commandBlock = filteredEnvVars.map(([key, value]) => `set -gx ${key} ${value}`).join("; ");
634
- break;
635
- default: {
636
- const assignments = filteredEnvVars.map(([key, value]) => `${key}=${value}`).join(" ");
637
- commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : "";
638
- break;
639
- }
640
- }
641
- if (commandBlock && commandToRun) return `${commandBlock}${shell === "cmd" ? " & " : " && "}${commandToRun}`;
642
- return commandBlock || commandToRun;
643
- }
644
-
645
700
  //#endregion
646
701
  //#region src/lib/logger.ts
647
702
  function nowIso() {
@@ -1951,34 +2006,6 @@ async function runServer(options) {
1951
2006
  await cacheModels();
1952
2007
  consola.info(`Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`);
1953
2008
  const serverUrl = `http://localhost:${options.port}`;
1954
- if (options.claudeCode) {
1955
- invariant(state.models, "Models should be loaded by now");
1956
- const selectedModel = await consola.prompt("Select a model to use with Claude Code", {
1957
- type: "select",
1958
- options: state.models.data.map((model) => model.id)
1959
- });
1960
- const selectedSmallModel = await consola.prompt("Select a small model to use with Claude Code", {
1961
- type: "select",
1962
- options: state.models.data.map((model) => model.id)
1963
- });
1964
- const command = generateEnvScript({
1965
- ANTHROPIC_BASE_URL: serverUrl,
1966
- ANTHROPIC_AUTH_TOKEN: "dummy",
1967
- ANTHROPIC_MODEL: selectedModel,
1968
- ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel,
1969
- ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel,
1970
- ANTHROPIC_DEFAULT_HAIKU_MODEL: selectedSmallModel,
1971
- DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1",
1972
- CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1"
1973
- }, "claude");
1974
- try {
1975
- clipboard.writeSync(command);
1976
- consola.success("Copied Claude Code command to clipboard!");
1977
- } catch {
1978
- consola.warn("Failed to copy to clipboard. Here is the Claude Code command:");
1979
- consola.log(command);
1980
- }
1981
- }
1982
2009
  consola.box(`🌐 Usage Viewer: https://billxc.github.io/copilot-api?endpoint=${serverUrl}/usage`);
1983
2010
  serve({
1984
2011
  fetch: server.fetch,
@@ -2030,12 +2057,6 @@ const start = defineCommand({
2030
2057
  type: "string",
2031
2058
  description: "Provide GitHub token directly (must be generated using the `auth` subcommand)"
2032
2059
  },
2033
- "claude-code": {
2034
- alias: "c",
2035
- type: "boolean",
2036
- default: false,
2037
- description: "Generate a command to launch Claude Code with Copilot API config"
2038
- },
2039
2060
  "show-token": {
2040
2061
  type: "boolean",
2041
2062
  default: false,
@@ -2058,7 +2079,6 @@ const start = defineCommand({
2058
2079
  rateLimit,
2059
2080
  rateLimitWait: args.wait,
2060
2081
  githubToken: args["github-token"],
2061
- claudeCode: args["claude-code"],
2062
2082
  showToken: args["show-token"],
2063
2083
  proxyEnv: args["proxy-env"]
2064
2084
  });
@@ -2087,6 +2107,7 @@ const main = defineCommand({
2087
2107
  subCommands: {
2088
2108
  auth,
2089
2109
  start,
2110
+ config,
2090
2111
  "check-usage": checkUsage,
2091
2112
  debug
2092
2113
  }