@dianshuv/copilot-api 0.15.1 → 0.16.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.
Files changed (3) hide show
  1. package/README.md +3 -2
  2. package/dist/main.mjs +86 -149
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -16,7 +16,7 @@
16
16
  - **Stream repetition detection**: Detects when models get stuck in repetitive output loops using KMP-based pattern matching and logs a warning.
17
17
  - **Stale request reaping**: Automatically force-fails requests that exceed a configurable maximum age (default 600s) to prevent resource leaks.
18
18
  - **PostHog analytics**: Optional PostHog Cloud integration (`--posthog-key`) sends per-request token usage events for long-term trend analysis. Free tier (1M events/month) is more than sufficient for individual use.
19
- - **GitHub Copilot CLI emulation**: All upstream requests to GitHub — device-flow `login`, Copilot token exchange, and model calls — carry the official GitHub Copilot CLI's (`@github/copilot`) identity: its `copilot-integration-id` (`copilot-developer-cli`), `editor-version`/`user-agent` (`copilot/<version>`), `x-github-api-version`, and a persistent `x-client-machine-id` (stored at `~/.local/share/copilot-api/machine_id`). The `login` flow uses the CLI's own OAuth app, so **new** logins request the `read:user`, `read:org`, `repo`, and `gist` scopes; existing tokens keep working without re-authentication.
19
+ - **GitHub Copilot CLI emulation**: All upstream requests to GitHub — device-flow `login`, `/copilot_internal/user` bootstrap, and CAPI model/chat calls — carry the official GitHub Copilot CLI's (`@github/copilot`) identity: its `copilot-integration-id` (`copilot-developer-cli`), `editor-version`/`user-agent` (`copilot/<version>`), `x-github-api-version`, and a persistent `x-client-machine-id` (stored at `~/.local/share/copilot-api/machine_id`). The CAPI Bearer is the GitHub OAuth token itself — the CLI does not perform a separate token exchange. The `login` flow uses the CLI's own OAuth app, so **new** logins request the `read:user`, `read:org`, `repo`, and `gist` scopes; existing tokens keep working without re-authentication.
20
20
 
21
21
  ## Quick Start
22
22
 
@@ -63,6 +63,7 @@ make down
63
63
  | `--github-token`, `-g` | Provide GitHub token directly (or `GH_TOKEN` env) | none |
64
64
  | `--posthog-key` | PostHog API key for token usage analytics (opt-in) | none |
65
65
  | `--api-key` | Proxy API key for inbound authentication (see [Authentication](#authentication)). Empty = disabled | none |
66
+ | `--allow-token-endpoint` | Opt-in to expose `GET /token` (echoes the raw GitHub OAuth token; requires `--api-key` too). `COPILOT_ALLOW_TOKEN_ENDPOINT=1` env twin. | false |
66
67
 
67
68
  ### Hidden Models
68
69
 
@@ -114,7 +115,7 @@ Currently hidden (grouped):
114
115
  |----------|--------|-------------|
115
116
  | `/` | GET | Server status |
116
117
  | `/usage` | GET | Copilot usage stats |
117
- | `/token` | GET | Current Copilot token |
118
+ | `/token` | GET | Current CAPI Bearer (= raw GitHub OAuth token). Disabled by default; requires `--allow-token-endpoint` **and** `--api-key`, otherwise 403. |
118
119
  | `/health` | GET | Health check |
119
120
  | `/history` | GET | Request history Web UI with token analytics (enabled by default) |
120
121
  | `/history/api/*` | GET/DELETE | History API endpoints |
package/dist/main.mjs CHANGED
@@ -140,6 +140,7 @@ const state = {
140
140
  showToken: false,
141
141
  showAllModels: false,
142
142
  verbose: false,
143
+ allowTokenEndpoint: false,
143
144
  autoTruncate: true,
144
145
  compressToolResults: false,
145
146
  redirectAnthropic: false,
@@ -165,7 +166,10 @@ const COPILOT_INTEGRATION_ID = "copilot-developer-cli";
165
166
  const cliVersion = (state) => state.copilotCliVersion ?? CLI_VERSION_FALLBACK;
166
167
  const editorVersion = (state) => `copilot/${cliVersion(state)}`;
167
168
  const userAgent = (state) => `copilot/${cliVersion(state)} (${process.platform} ${process.version}) term/${process.env.TERM_PROGRAM ?? "unknown"}`;
168
- const copilotBaseUrl = (state) => state.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state.accountType}.githubcopilot.com`;
169
+ const copilotBaseUrl = (state) => {
170
+ if (state.copilotApiEndpoint) return state.copilotApiEndpoint;
171
+ return state.accountType === "individual" ? "https://api.githubcopilot.com" : `https://api.${state.accountType}.githubcopilot.com`;
172
+ };
169
173
  function hasHeaderKey(headers, key) {
170
174
  const lowerKey = key.toLowerCase();
171
175
  return Object.keys(headers).some((existingKey) => {
@@ -465,14 +469,6 @@ function forwardError(c, error) {
465
469
  } }, 500);
466
470
  }
467
471
 
468
- //#endregion
469
- //#region src/services/github/get-copilot-token.ts
470
- const getCopilotToken = async () => {
471
- const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, { headers: githubHeaders(state) });
472
- if (!response.ok) throw await HTTPError.fromResponse("Failed to get Copilot token", response);
473
- return await response.json();
474
- };
475
-
476
472
  //#endregion
477
473
  //#region src/services/github/get-device-code.ts
478
474
  async function getDeviceCode() {
@@ -567,48 +563,28 @@ function isRetryable(error) {
567
563
  return code !== void 0 && RETRYABLE_CAUSE_CODES.has(code);
568
564
  }
569
565
  const RETRY_ATTEMPTS_KEY = "__copilotRetryAttempts";
570
- const RETRY_AUTH_REFRESHED_KEY = "__copilotRetryAuthRefreshed";
571
566
  /**
572
567
  * Read retry metadata recorded on an error or response by fetchWithRetry.
573
568
  * Returns attempts=1 (no retry) if the value is missing or malformed.
574
569
  */
575
570
  function getRetryAttempts(target) {
576
- if (typeof target !== "object" || target === null) return {
577
- attempts: 1,
578
- authRefreshed: false
579
- };
571
+ if (typeof target !== "object" || target === null) return { attempts: 1 };
580
572
  const meta = target;
581
- return {
582
- attempts: typeof meta[RETRY_ATTEMPTS_KEY] === "number" ? meta[RETRY_ATTEMPTS_KEY] : 1,
583
- authRefreshed: meta[RETRY_AUTH_REFRESHED_KEY] === true
584
- };
573
+ return { attempts: typeof meta[RETRY_ATTEMPTS_KEY] === "number" ? meta[RETRY_ATTEMPTS_KEY] : 1 };
585
574
  }
586
- async function fetchWithRetry(input, init, options) {
587
- let currentInit = init;
588
- let authRefreshed = false;
575
+ async function fetchWithRetry(input, init) {
589
576
  let networkAttempts = 0;
590
577
  for (let attempt = 0; attempt <= RETRY_DELAYS_MS.length; attempt++) try {
591
578
  networkAttempts++;
592
579
  const fetchStart = performance.now();
593
- const response = await fetch(input, currentInit);
580
+ const response = await fetch(input, init);
594
581
  const ttfbMs = performance.now() - fetchStart;
595
- if (response.status === 401 && !authRefreshed && options?.onUnauthorized) {
596
- const refreshed = await tryRefreshAuth(options.onUnauthorized);
597
- if (refreshed) {
598
- await response.body?.cancel().catch(() => {});
599
- consola.warn("Got 401 from upstream; refreshed token and retrying");
600
- currentInit = refreshed;
601
- authRefreshed = true;
602
- attempt--;
603
- continue;
604
- }
605
- }
606
582
  addTiming(TIMING.UPSTREAM_TTFB, ttfbMs);
607
- annotateRetryMeta(response, networkAttempts, authRefreshed);
583
+ annotateRetryMeta(response, networkAttempts);
608
584
  return response;
609
585
  } catch (error) {
610
586
  if (attempt === RETRY_DELAYS_MS.length || !isRetryable(error)) {
611
- annotateRetryMeta(error, networkAttempts, authRefreshed);
587
+ annotateRetryMeta(error, networkAttempts);
612
588
  throw error;
613
589
  }
614
590
  const delay = RETRY_DELAYS_MS[attempt];
@@ -617,54 +593,31 @@ async function fetchWithRetry(input, init, options) {
617
593
  }
618
594
  throw new Error("fetchWithRetry exhausted attempts without resolution");
619
595
  }
620
- function annotateRetryMeta(target, attempts, authRefreshed) {
596
+ function annotateRetryMeta(target, attempts) {
621
597
  if (typeof target !== "object" || target === null) return;
622
598
  try {
623
599
  const meta = target;
624
600
  meta[RETRY_ATTEMPTS_KEY] = attempts;
625
- meta[RETRY_AUTH_REFRESHED_KEY] = authRefreshed;
626
601
  } catch {}
627
602
  }
628
- async function tryRefreshAuth(onUnauthorized) {
629
- try {
630
- return await onUnauthorized();
631
- } catch (error) {
632
- consola.warn("onUnauthorized callback failed:", error instanceof Error ? error.message : error);
633
- return null;
634
- }
635
- }
636
- /**
637
- * Build an onUnauthorized callback that refreshes the Copilot token and
638
- * returns a new RequestInit with an updated Authorization header.
639
- */
640
- function makeCopilotAuthRetry(refreshToken, init) {
641
- return async () => {
642
- const newToken = await refreshToken();
643
- if (!newToken) return null;
644
- const headers = new Headers(init.headers);
645
- headers.set("Authorization", `Bearer ${newToken}`);
646
- return {
647
- ...init,
648
- headers
649
- };
650
- };
651
- }
652
603
 
653
604
  //#endregion
654
605
  //#region src/services/copilot/copilot-fetch.ts
655
606
  /**
656
607
  * Single transport seam for all upstream Copilot API calls.
657
608
  *
658
- * Wraps every request with:
659
- * - Network-error retry (UND_ERR_SOCKET, ECONNRESET, ENOTFOUND, connect timeouts)
660
- * - 401 → forceRefreshCopilotToken → retry once with the new bearer token
609
+ * Wraps every request with network-error retry (UND_ERR_SOCKET, ECONNRESET,
610
+ * ENOTFOUND, connect timeouts).
661
611
  *
662
- * Callers supply the path (e.g. "/chat/completions") and the usual RequestInit.
663
- * The host prefix and resilience wiring are applied here so every endpoint
664
- * gets the same treatment and adding a new endpoint is one call site.
612
+ * Historically this seam also refreshed a short-lived CAPI session token on
613
+ * 401. That session-token flow was a VSCode/JetBrains-plugin idiom; the CLI
614
+ * we impersonate uses the GitHub OAuth token directly as the CAPI Bearer, so
615
+ * a 401 there means the OAuth token itself is invalid — no server-side
616
+ * refresh helps. If a future refresh_token grant is wired in, add it back
617
+ * here as an `onUnauthorized` callback.
665
618
  */
666
619
  function copilotFetch(path, init) {
667
- return fetchWithRetry(`${copilotBaseUrl(state)}${path}`, init, { onUnauthorized: makeCopilotAuthRetry(forceRefreshCopilotToken, init) });
620
+ return fetchWithRetry(`${copilotBaseUrl(state)}${path}`, init);
668
621
  }
669
622
 
670
623
  //#endregion
@@ -708,6 +661,14 @@ async function getCopilotCliVersion() {
708
661
  }
709
662
  }
710
663
 
664
+ //#endregion
665
+ //#region src/services/github/get-copilot-usage.ts
666
+ const getCopilotUsage = async () => {
667
+ const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, { headers: githubHeaders(state) });
668
+ if (!response.ok) throw await HTTPError.fromResponse("Failed to get Copilot usage", response);
669
+ return await response.json();
670
+ };
671
+
711
672
  //#endregion
712
673
  //#region src/lib/utils.ts
713
674
  const sleep = (ms) => new Promise((resolve) => {
@@ -736,6 +697,44 @@ const initCopilotIdentity = async () => {
736
697
  state.machineId = await getOrCreateMachineId();
737
698
  await cacheCopilotCliVersion();
738
699
  };
700
+ /**
701
+ * Bootstraps the CAPI session after GitHub OAuth is settled. Mirrors what the
702
+ * real @github/copilot CLI does at startup:
703
+ *
704
+ * 1. GET /copilot_internal/user to learn the account's CAPI endpoint set
705
+ * (`endpoints.api` — the host varies by plan tier, e.g. enterprise gets
706
+ * `api.enterprise.githubcopilot.com`).
707
+ * 2. Use the GitHub OAuth token directly as the CAPI Bearer. The CLI does NOT
708
+ * exchange the OAuth token for a separate CAPI session token — that flow
709
+ * (`GET /copilot_internal/v2/token`) is a VSCode/JetBrains-plugin idiom the
710
+ * CLI's GitHub App is not authorized for (returns 404).
711
+ *
712
+ * Must run after setupGitHubToken() and initCopilotIdentity(). Failure surfaces
713
+ * to the caller; there is no CAPI to serve without endpoints or a Bearer, so
714
+ * a missing/tampered/non-Copilot `endpoints.api` throws at boot rather than
715
+ * silently degrading to a stale accountType-derived host or routing the raw
716
+ * GitHub OAuth token at an arbitrary origin.
717
+ */
718
+ const CAPI_HOST_PATTERN = /^api(?:\.[a-z0-9-]+)?\.githubcopilot\.com$/;
719
+ const bootstrapCopilotSession = async () => {
720
+ const usage = await getCopilotUsage();
721
+ if (usage === null || typeof usage !== "object") throw new Error("bootstrapCopilotSession: /copilot_internal/user body was not an object.");
722
+ const usageObj = usage;
723
+ if (typeof usageObj.endpoints !== "object" || usageObj.endpoints === null) throw new Error("bootstrapCopilotSession: /copilot_internal/user returned no endpoints object.");
724
+ const rawApi = usageObj.endpoints.api;
725
+ if (typeof rawApi !== "string") throw new TypeError(`bootstrapCopilotSession: endpoints.api must be a string (got ${typeof rawApi}).`);
726
+ let parsed;
727
+ try {
728
+ parsed = new URL(rawApi);
729
+ } catch {
730
+ throw new Error(`bootstrapCopilotSession: endpoints.api is not a valid URL (${JSON.stringify(rawApi)}).`);
731
+ }
732
+ if (parsed.protocol !== "https:") throw new Error(`bootstrapCopilotSession: endpoints.api must use https:// (got ${parsed.protocol}).`);
733
+ if (parsed.port !== "") throw new Error(`bootstrapCopilotSession: endpoints.api must use the default https port (got :${parsed.port}).`);
734
+ if (!CAPI_HOST_PATTERN.test(parsed.hostname)) throw new Error(`bootstrapCopilotSession: endpoints.api host ${parsed.hostname} is not an api[.tier].githubcopilot.com host.`);
735
+ state.copilotApiEndpoint = parsed.origin;
736
+ state.copilotToken = state.githubToken;
737
+ };
739
738
 
740
739
  //#endregion
741
740
  //#region src/services/github/poll-access-token.ts
@@ -771,72 +770,6 @@ async function pollAccessToken(deviceCode) {
771
770
  //#region src/lib/token.ts
772
771
  const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8");
773
772
  const writeGithubToken = (token) => fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token);
774
- let copilotTokenRefreshTimer = null;
775
- /**
776
- * Refresh the Copilot token with exponential backoff retry.
777
- * Returns the new token on success, or null if all retries fail.
778
- */
779
- async function refreshCopilotTokenWithRetry(maxRetries = 3) {
780
- let lastError = null;
781
- for (let attempt = 0; attempt < maxRetries; attempt++) try {
782
- const { token } = await getCopilotToken();
783
- return token;
784
- } catch (error) {
785
- lastError = error;
786
- const delay = Math.min(1e3 * 2 ** attempt, 3e4);
787
- consola.warn(`Token refresh attempt ${attempt + 1}/${maxRetries} failed, retrying in ${delay}ms`);
788
- await new Promise((resolve) => setTimeout(resolve, delay));
789
- }
790
- consola.error("All token refresh attempts failed:", lastError);
791
- return null;
792
- }
793
- /**
794
- * Force-refresh the Copilot token on demand (e.g. after a 401 response).
795
- * Updates state.copilotToken on success and returns the new token, or null
796
- * if refresh failed. Coalesces concurrent callers so multiple in-flight 401s
797
- * only trigger one refresh.
798
- */
799
- let refreshInFlight = null;
800
- async function forceRefreshCopilotToken() {
801
- if (refreshInFlight) return refreshInFlight;
802
- refreshInFlight = refreshCopilotTokenWithRetry().then((token) => {
803
- if (token) state.copilotToken = token;
804
- return token;
805
- }).finally(() => {
806
- refreshInFlight = null;
807
- });
808
- return refreshInFlight;
809
- }
810
- /**
811
- * Clear any existing token refresh timer.
812
- * Call this before setting up a new timer or during cleanup.
813
- */
814
- function clearCopilotTokenRefresh() {
815
- if (copilotTokenRefreshTimer) {
816
- clearInterval(copilotTokenRefreshTimer);
817
- copilotTokenRefreshTimer = null;
818
- }
819
- }
820
- const setupCopilotToken = async () => {
821
- const { token, refresh_in } = await getCopilotToken();
822
- state.copilotToken = token;
823
- consola.debug("GitHub Copilot Token fetched successfully!");
824
- if (state.showToken) consola.info("Copilot token:", token);
825
- const refreshInterval = Math.max((refresh_in - 60) * 1e3, 60 * 1e3);
826
- clearCopilotTokenRefresh();
827
- copilotTokenRefreshTimer = setInterval(() => {
828
- consola.debug("Refreshing Copilot token");
829
- refreshCopilotTokenWithRetry().then((newToken) => {
830
- if (newToken) {
831
- state.copilotToken = newToken;
832
- consola.debug("Copilot token refreshed");
833
- if (state.showToken) consola.info("Refreshed Copilot token:", newToken);
834
- } else consola.error("Failed to refresh Copilot token after retries, using existing token");
835
- }).catch((error) => {
836
- consola.error("Unexpected error during token refresh:", error);
837
- });
838
- }, refreshInterval);
839
- };
840
773
  async function setupGitHubToken(options) {
841
774
  try {
842
775
  const githubToken = await readGithubToken();
@@ -869,14 +802,6 @@ async function logUser() {
869
802
  consola.info(`Logged in as ${user.login}`);
870
803
  }
871
804
 
872
- //#endregion
873
- //#region src/services/github/get-copilot-usage.ts
874
- const getCopilotUsage = async () => {
875
- const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, { headers: githubHeaders(state) });
876
- if (!response.ok) throw await HTTPError.fromResponse("Failed to get Copilot usage", response);
877
- return await response.json();
878
- };
879
-
880
805
  //#endregion
881
806
  //#region src/debug.ts
882
807
  async function getPackageVersion() {
@@ -1002,8 +927,7 @@ const debugModels = defineCommand({
1002
927
  state.githubToken = args["github-token"];
1003
928
  consola.info("Using provided GitHub token");
1004
929
  } else await setupGitHubToken();
1005
- const { token } = await getCopilotToken();
1006
- state.copilotToken = token;
930
+ await bootstrapCopilotSession();
1007
931
  consola.info("Fetching models from Copilot API...");
1008
932
  const models = await getModels();
1009
933
  console.log(JSON.stringify(models, null, 2));
@@ -1086,7 +1010,7 @@ const logout = defineCommand({
1086
1010
 
1087
1011
  //#endregion
1088
1012
  //#region package.json
1089
- var version = "0.15.1";
1013
+ var version = "0.16.0";
1090
1014
 
1091
1015
  //#endregion
1092
1016
  //#region src/lib/event-loop-lag.ts
@@ -1739,7 +1663,6 @@ async function gracefulShutdown(signal, deps) {
1739
1663
  const tracker = deps?.tracker;
1740
1664
  const server = deps?.server ?? serverInstance;
1741
1665
  const rateLimiter = deps?.rateLimiter !== void 0 ? deps.rateLimiter : getAdaptiveRateLimiter();
1742
- const stopRefresh = deps?.stopTokenRefreshFn ?? (() => {});
1743
1666
  const closeWsClients = deps?.closeAllClientsFn ?? closeAllClients;
1744
1667
  const getWsCount = deps?.getClientCountFn ?? getClientCount;
1745
1668
  const gracefulWaitMs = deps?.gracefulWaitMs ?? state.shutdownGracefulWait * 1e3;
@@ -1756,7 +1679,6 @@ async function gracefulShutdown(signal, deps) {
1756
1679
  } catch {}
1757
1680
  stopMemoryPressureMonitor();
1758
1681
  stopEventLoopLagMonitor();
1759
- stopRefresh();
1760
1682
  const wsClients = getWsCount();
1761
1683
  if (wsClients > 0) {
1762
1684
  closeWsClients();
@@ -9604,6 +9526,8 @@ responsesRoutes.post("/", async (c) => {
9604
9526
  const tokenRoute = new Hono();
9605
9527
  tokenRoute.get("/", (c) => {
9606
9528
  try {
9529
+ if (!state.allowTokenEndpoint) return c.json({ error: "/token disabled. Pass --allow-token-endpoint (or COPILOT_ALLOW_TOKEN_ENDPOINT=1) to expose the raw GitHub OAuth token; the fallback is to read ~/.local/share/copilot-api/github_token directly." }, 403);
9530
+ if (!state.proxyApiKeyDigest) return c.json({ error: "/token requires --api-key even when --allow-token-endpoint is set: the endpoint returns the raw GitHub OAuth token (repo,gist,read:user,read:org). Set --api-key or COPILOT_API_KEY to enable." }, 403);
9607
9531
  return c.json({ token: state.copilotToken });
9608
9532
  } catch (error) {
9609
9533
  return forwardError(c, error);
@@ -9626,6 +9550,11 @@ usageRoute.get("/", async (c) => {
9626
9550
  //#region src/server.ts
9627
9551
  const server = new Hono();
9628
9552
  server.use(tuiLogger());
9553
+ server.use("/token", async (c, next) => {
9554
+ await next();
9555
+ c.header("Access-Control-Allow-Origin", "null");
9556
+ c.header("Access-Control-Allow-Credentials", "false");
9557
+ });
9629
9558
  server.use(cors());
9630
9559
  server.use(authGate());
9631
9560
  server.get("/", (c) => c.text("Server running"));
@@ -9684,6 +9613,8 @@ function formatModelInfo(model) {
9684
9613
  async function runServer(options) {
9685
9614
  consola.info(`copilot-api v${version}`);
9686
9615
  configureProxyApiKey(options.apiKey);
9616
+ state.allowTokenEndpoint = options.allowTokenEndpoint;
9617
+ if (options.allowTokenEndpoint) consola.warn("/token endpoint enabled — will echo the raw GitHub OAuth token (repo,gist,read:user,read:org) to callers presenting --api-key. Only use on trusted single-operator hosts.");
9687
9618
  if (options.verbose) {
9688
9619
  consola.level = 5;
9689
9620
  consola.info("Verbose logging enabled");
@@ -9715,7 +9646,7 @@ async function runServer(options) {
9715
9646
  state.githubToken = options.githubToken;
9716
9647
  consola.info("Using provided GitHub token");
9717
9648
  } else await setupGitHubToken();
9718
- await setupCopilotToken();
9649
+ await bootstrapCopilotSession();
9719
9650
  try {
9720
9651
  await cacheModels();
9721
9652
  } catch (error) {
@@ -9800,6 +9731,11 @@ const start = defineCommand({
9800
9731
  "api-key": {
9801
9732
  type: "string",
9802
9733
  description: "Proxy API key for inbound authentication. When set (non-empty after trimming), all endpoints except / and /health require this key via 'Authorization: Bearer <key>'. Omitted or empty = auth disabled (default, all requests pass through)."
9734
+ },
9735
+ "allow-token-endpoint": {
9736
+ type: "boolean",
9737
+ default: false,
9738
+ description: "Enable the /token endpoint (default: disabled). When enabled AND --api-key is set, /token echoes the raw GitHub OAuth token (repo,gist,read:user,read:org) to authenticated callers. Never combine with --host 0.0.0.0 on untrusted networks — the token has full repo/gist write access on the operator's GitHub account. Fallback: read ~/.local/share/copilot-api/github_token directly."
9803
9739
  }
9804
9740
  },
9805
9741
  run({ args }) {
@@ -9816,7 +9752,8 @@ const start = defineCommand({
9816
9752
  githubToken: args["github-token"] || process.env.GH_TOKEN,
9817
9753
  posthogKey: args["posthog-key"],
9818
9754
  apiKey: resolvedApiKey.key,
9819
- apiKeySource: resolvedApiKey.source
9755
+ apiKeySource: resolvedApiKey.source,
9756
+ allowTokenEndpoint: args["allow-token-endpoint"] || process.env.COPILOT_ALLOW_TOKEN_ENDPOINT === "1" || process.env.COPILOT_ALLOW_TOKEN_ENDPOINT === "true"
9820
9757
  });
9821
9758
  }
9822
9759
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dianshuv/copilot-api",
3
- "version": "0.15.1",
3
+ "version": "0.16.0",
4
4
  "description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!",
5
5
  "author": "dianshuv",
6
6
  "type": "module",