@jer-y/copilot-proxy 0.1.6 → 0.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/dist/main.js CHANGED
@@ -1,44 +1,28 @@
1
1
  #!/usr/bin/env node
2
+ import { PATHS, ensurePaths } from "./paths-CA6OZ0WA.js";
3
+ import { loadDaemonConfig } from "./config-D1kMGXKU.js";
4
+ import { isDaemonRunning, isProcessRunning, readPid, removePidFile } from "./pid-DtjYMiQS.js";
5
+ import { daemonStart } from "./start-BNocR0hU.js";
2
6
  import { defineCommand, runMain } from "citty";
3
7
  import consola from "consola";
4
8
  import fs from "node:fs/promises";
5
9
  import os from "node:os";
6
- import path from "node:path";
7
10
  import { randomUUID } from "node:crypto";
8
11
  import process from "node:process";
12
+ import fs$1 from "node:fs";
13
+ import { Buffer } from "node:buffer";
14
+ import { execSync } from "node:child_process";
9
15
  import clipboard from "clipboardy";
10
16
  import { serve } from "srvx";
11
17
  import invariant from "tiny-invariant";
12
18
  import { getProxyForUrl } from "proxy-from-env";
13
19
  import { Agent, ProxyAgent, setGlobalDispatcher } from "undici";
14
- import { execSync } from "node:child_process";
15
20
  import { Hono } from "hono";
16
21
  import { cors } from "hono/cors";
17
22
  import { logger } from "hono/logger";
18
23
  import { streamSSE } from "hono/streaming";
19
24
  import { events } from "fetch-event-stream";
20
25
 
21
- //#region src/lib/paths.ts
22
- const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-proxy");
23
- const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token");
24
- const PATHS = {
25
- APP_DIR,
26
- GITHUB_TOKEN_PATH
27
- };
28
- async function ensurePaths() {
29
- await fs.mkdir(PATHS.APP_DIR, { recursive: true });
30
- await ensureFile(PATHS.GITHUB_TOKEN_PATH);
31
- }
32
- async function ensureFile(filePath) {
33
- try {
34
- await fs.access(filePath, fs.constants.W_OK);
35
- } catch {
36
- await fs.writeFile(filePath, "");
37
- await fs.chmod(filePath, 384);
38
- }
39
- }
40
-
41
- //#endregion
42
26
  //#region src/lib/state.ts
43
27
  const state = {
44
28
  accountType: "individual",
@@ -163,20 +147,21 @@ async function getGitHubUser() {
163
147
  //#endregion
164
148
  //#region src/services/copilot/get-models.ts
165
149
  async function getModels() {
150
+ const headers = copilotHeaders(state);
151
+ const response = await fetch(`${copilotBaseUrl(state)}/models`, { headers });
152
+ if (response.ok) return await response.json();
153
+ consola.warn(`vscode-chat models request failed (${response.status} ${response.statusText}), falling back to copilot-developer-cli`);
166
154
  if (state.githubToken) try {
167
155
  const cliHeaders = copilotHeaders(state);
168
156
  cliHeaders.Authorization = `Bearer ${state.githubToken}`;
169
157
  cliHeaders["copilot-integration-id"] = "copilot-developer-cli";
170
- const response$1 = await fetch(`${copilotBaseUrl(state)}/models`, { headers: cliHeaders });
171
- if (response$1.ok) return await response$1.json();
172
- consola.warn(`copilot-developer-cli models request failed (${response$1.status} ${response$1.statusText}), falling back to standard auth`);
158
+ const cliResponse = await fetch(`${copilotBaseUrl(state)}/models`, { headers: cliHeaders });
159
+ if (cliResponse.ok) return await cliResponse.json();
160
+ consola.warn(`copilot-developer-cli fallback also failed (${cliResponse.status} ${cliResponse.statusText})`);
173
161
  } catch (e) {
174
- consola.warn("copilot-developer-cli models request error, falling back:", e);
162
+ consola.warn("copilot-developer-cli fallback error:", e);
175
163
  }
176
- const headers = copilotHeaders(state);
177
- const response = await fetch(`${copilotBaseUrl(state)}/models`, { headers });
178
- if (!response.ok) throw new HTTPError("Failed to get models", response);
179
- return await response.json();
164
+ throw new HTTPError("Failed to get models", response);
180
165
  }
181
166
 
182
167
  //#endregion
@@ -259,7 +244,8 @@ async function setupCopilotToken() {
259
244
  state.copilotToken = token;
260
245
  consola.debug("GitHub Copilot Token fetched successfully!");
261
246
  if (state.showToken) consola.info("Copilot token:", token);
262
- const refreshInterval = (refresh_in - 60) * 1e3;
247
+ const rawInterval = (refresh_in - 60) * 1e3;
248
+ const refreshInterval = Number.isFinite(rawInterval) ? Math.min(Math.max(rawInterval, 6e4), 1440 * 60 * 1e3) : 6e4;
263
249
  setInterval(async () => {
264
250
  consola.debug("Refreshing Copilot token");
265
251
  try {
@@ -268,8 +254,7 @@ async function setupCopilotToken() {
268
254
  consola.debug("Copilot token refreshed");
269
255
  if (state.showToken) consola.info("Refreshed Copilot token:", token$1);
270
256
  } catch (error) {
271
- consola.error("Failed to refresh Copilot token:", error);
272
- throw error;
257
+ consola.error("Failed to refresh Copilot token, will retry next cycle:", error);
273
258
  }
274
259
  }, refreshInterval);
275
260
  }
@@ -387,6 +372,228 @@ const checkUsage = defineCommand({
387
372
  }
388
373
  });
389
374
 
375
+ //#endregion
376
+ //#region src/daemon/disable.ts
377
+ const disable = defineCommand({
378
+ meta: {
379
+ name: "disable",
380
+ description: "Remove auto-start service"
381
+ },
382
+ async run() {
383
+ const { platform } = process;
384
+ let success = true;
385
+ if (platform === "linux") {
386
+ const { uninstallAutoStart } = await import("./linux-CX0xETja.js");
387
+ success = await uninstallAutoStart();
388
+ } else if (platform === "darwin") {
389
+ const { uninstallAutoStart } = await import("./darwin-BVmd1DeO.js");
390
+ success = await uninstallAutoStart();
391
+ } else if (platform === "win32") {
392
+ const { uninstallAutoStart } = await import("./win32-D1-MlKl7.js");
393
+ success = await uninstallAutoStart();
394
+ } else {
395
+ consola.error(`Unsupported platform: ${platform}`);
396
+ process.exit(1);
397
+ }
398
+ if (!success) process.exit(1);
399
+ }
400
+ });
401
+
402
+ //#endregion
403
+ //#region src/daemon/enable.ts
404
+ const enable = defineCommand({
405
+ meta: {
406
+ name: "enable",
407
+ description: "Register as auto-start service"
408
+ },
409
+ async run() {
410
+ if (!loadDaemonConfig()) {
411
+ consola.error("No daemon config found. Start the daemon first with `start -d`");
412
+ process.exit(1);
413
+ }
414
+ const execPath = process.argv[0];
415
+ const args = [
416
+ process.argv[1],
417
+ "start",
418
+ "--_supervisor"
419
+ ];
420
+ let success = false;
421
+ const { platform } = process;
422
+ if (platform === "linux") {
423
+ const { installAutoStart } = await import("./linux-CX0xETja.js");
424
+ success = await installAutoStart(execPath, args);
425
+ } else if (platform === "darwin") {
426
+ const { installAutoStart } = await import("./darwin-BVmd1DeO.js");
427
+ success = await installAutoStart(execPath, args);
428
+ } else if (platform === "win32") {
429
+ const { installAutoStart } = await import("./win32-D1-MlKl7.js");
430
+ success = await installAutoStart(execPath, args);
431
+ } else {
432
+ consola.error(`Unsupported platform: ${platform}`);
433
+ process.exit(1);
434
+ }
435
+ if (!success) process.exit(1);
436
+ }
437
+ });
438
+
439
+ //#endregion
440
+ //#region src/daemon/logs.ts
441
+ const logs = defineCommand({
442
+ meta: {
443
+ name: "logs",
444
+ description: "Show daemon logs"
445
+ },
446
+ args: {
447
+ follow: {
448
+ alias: "f",
449
+ type: "boolean",
450
+ default: false,
451
+ description: "Follow log output"
452
+ },
453
+ lines: {
454
+ alias: "n",
455
+ type: "string",
456
+ default: "50",
457
+ description: "Number of lines to show"
458
+ }
459
+ },
460
+ run({ args }) {
461
+ if (!fs$1.existsSync(PATHS.DAEMON_LOG)) {
462
+ consola.info("No log file found");
463
+ return;
464
+ }
465
+ if (args.follow) followLogsWatch();
466
+ else {
467
+ const lines = fs$1.readFileSync(PATHS.DAEMON_LOG, "utf8").split("\n");
468
+ const count = Number.parseInt(args.lines, 10);
469
+ const output = lines.slice(-count).join("\n");
470
+ console.log(output);
471
+ }
472
+ }
473
+ });
474
+ function followLogsWatch() {
475
+ const content = fs$1.readFileSync(PATHS.DAEMON_LOG, "utf8");
476
+ process.stdout.write(content);
477
+ let position = Buffer.byteLength(content);
478
+ let currentIno = 0;
479
+ try {
480
+ currentIno = fs$1.statSync(PATHS.DAEMON_LOG).ino;
481
+ } catch {}
482
+ setInterval(() => {
483
+ try {
484
+ const stat = fs$1.statSync(PATHS.DAEMON_LOG);
485
+ if (stat.ino !== currentIno) {
486
+ currentIno = stat.ino;
487
+ position = 0;
488
+ }
489
+ if (stat.size < position) position = 0;
490
+ if (stat.size > position) {
491
+ const fd = fs$1.openSync(PATHS.DAEMON_LOG, "r");
492
+ const buffer = Buffer.alloc(stat.size - position);
493
+ fs$1.readSync(fd, buffer, 0, buffer.length, position);
494
+ fs$1.closeSync(fd);
495
+ process.stdout.write(buffer);
496
+ position = stat.size;
497
+ }
498
+ } catch {}
499
+ }, 500);
500
+ }
501
+
502
+ //#endregion
503
+ //#region src/daemon/stop.ts
504
+ /**
505
+ * Attempt to stop the daemon. Returns true if daemon was stopped or
506
+ * was not running. Returns false if the process could not be stopped.
507
+ */
508
+ function stopDaemon() {
509
+ const daemon = isDaemonRunning();
510
+ if (!daemon.running) {
511
+ consola.info("Daemon is not running");
512
+ removePidFile();
513
+ return true;
514
+ }
515
+ const { pid } = daemon;
516
+ consola.info(`Stopping daemon (PID: ${pid})...`);
517
+ try {
518
+ process.kill(pid, "SIGTERM");
519
+ } catch {
520
+ consola.error("Failed to send SIGTERM");
521
+ return false;
522
+ }
523
+ const deadline = Date.now() + 1e4;
524
+ while (isProcessRunning(pid) && Date.now() < deadline) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
525
+ if (isProcessRunning(pid)) {
526
+ consola.warn("Process did not exit in time, sending SIGKILL");
527
+ try {
528
+ process.kill(pid, "SIGKILL");
529
+ } catch {}
530
+ const killDeadline = Date.now() + 3e3;
531
+ while (isProcessRunning(pid) && Date.now() < killDeadline) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
532
+ if (isProcessRunning(pid)) {
533
+ consola.error(`Failed to kill process ${pid}`);
534
+ return false;
535
+ }
536
+ }
537
+ removePidFile();
538
+ consola.success("Daemon stopped");
539
+ return true;
540
+ }
541
+ const stop = defineCommand({
542
+ meta: {
543
+ name: "stop",
544
+ description: "Stop the background daemon"
545
+ },
546
+ run() {
547
+ if (!stopDaemon()) process.exit(1);
548
+ }
549
+ });
550
+
551
+ //#endregion
552
+ //#region src/daemon/restart.ts
553
+ const restart = defineCommand({
554
+ meta: {
555
+ name: "restart",
556
+ description: "Restart the background daemon"
557
+ },
558
+ run() {
559
+ const config = loadDaemonConfig();
560
+ if (!config) {
561
+ consola.error("No daemon config found. Start the daemon first with `start -d`");
562
+ process.exit(1);
563
+ }
564
+ if (isDaemonRunning().running) {
565
+ if (!stopDaemon()) {
566
+ consola.error("Cannot restart: failed to stop existing daemon");
567
+ process.exit(1);
568
+ }
569
+ }
570
+ daemonStart(config);
571
+ }
572
+ });
573
+
574
+ //#endregion
575
+ //#region src/daemon/status.ts
576
+ const status = defineCommand({
577
+ meta: {
578
+ name: "status",
579
+ description: "Show daemon status"
580
+ },
581
+ run() {
582
+ const daemon = isDaemonRunning();
583
+ if (!daemon.running) {
584
+ consola.info("Daemon is not running");
585
+ return;
586
+ }
587
+ const config = loadDaemonConfig();
588
+ const info = readPid();
589
+ const startedAt = info && info.startTime > 0 ? new Date(info.startTime).toLocaleString() : "unknown";
590
+ consola.info(`Daemon is running`);
591
+ consola.info(` PID: ${daemon.pid}`);
592
+ consola.info(` Port: ${config?.port ?? "unknown"}`);
593
+ consola.info(` Started: ${startedAt}`);
594
+ }
595
+ });
596
+
390
597
  //#endregion
391
598
  //#region src/debug.ts
392
599
  async function getPackageVersion() {
@@ -1664,6 +1871,7 @@ async function runServer(options) {
1664
1871
  fetch: server.fetch,
1665
1872
  port: options.port
1666
1873
  });
1874
+ await new Promise(() => {});
1667
1875
  }
1668
1876
  const start = defineCommand({
1669
1877
  meta: {
@@ -1725,13 +1933,83 @@ const start = defineCommand({
1725
1933
  type: "boolean",
1726
1934
  default: false,
1727
1935
  description: "Initialize proxy from environment variables"
1936
+ },
1937
+ "daemon": {
1938
+ alias: "d",
1939
+ type: "boolean",
1940
+ default: false,
1941
+ description: "Run as a background daemon"
1942
+ },
1943
+ "_supervisor": {
1944
+ type: "boolean",
1945
+ default: false,
1946
+ description: "Internal: run as supervisor (do not use directly)"
1728
1947
  }
1729
1948
  },
1730
- run({ args }) {
1949
+ async run({ args }) {
1950
+ const port = Number.parseInt(args.port, 10);
1951
+ if (Number.isNaN(port) || port <= 0 || port > 65535 || String(port) !== args.port) {
1952
+ consola.error(`Invalid port: ${args.port}`);
1953
+ process.exit(1);
1954
+ }
1731
1955
  const rateLimitRaw = args["rate-limit"];
1732
1956
  const rateLimit = rateLimitRaw === void 0 ? void 0 : Number.parseInt(rateLimitRaw, 10);
1957
+ if (rateLimitRaw !== void 0 && (Number.isNaN(rateLimit) || rateLimit <= 0 || rateLimit > 86400 || String(rateLimit) !== rateLimitRaw)) {
1958
+ consola.error(`Invalid rate-limit: ${rateLimitRaw} (must be 1-86400)`);
1959
+ process.exit(1);
1960
+ }
1961
+ const validAccountTypes = [
1962
+ "individual",
1963
+ "business",
1964
+ "enterprise"
1965
+ ];
1966
+ if (!validAccountTypes.includes(args["account-type"])) {
1967
+ consola.error(`Invalid account-type: ${args["account-type"]} (must be one of: ${validAccountTypes.join(", ")})`);
1968
+ process.exit(1);
1969
+ }
1970
+ if (args._supervisor) {
1971
+ const { loadDaemonConfig: loadDaemonConfig$1 } = await import("./config-ixm2Pm96.js");
1972
+ const config = loadDaemonConfig$1();
1973
+ if (!config) {
1974
+ consola.error("Supervisor mode: daemon config not found");
1975
+ process.exit(1);
1976
+ }
1977
+ const { runAsSupervisor } = await import("./supervisor-wpaa2IAJ.js");
1978
+ const options = {
1979
+ port: config.port,
1980
+ verbose: config.verbose,
1981
+ accountType: config.accountType,
1982
+ manual: config.manual,
1983
+ rateLimit: config.rateLimit,
1984
+ rateLimitWait: config.rateLimitWait,
1985
+ githubToken: config.githubToken,
1986
+ claudeCode: false,
1987
+ showToken: config.showToken,
1988
+ proxyEnv: config.proxyEnv
1989
+ };
1990
+ return runAsSupervisor(() => runServer(options));
1991
+ }
1992
+ if (args.daemon) {
1993
+ if (args["claude-code"]) {
1994
+ consola.error("Cannot use --claude-code with --daemon (interactive mode)");
1995
+ process.exit(1);
1996
+ }
1997
+ const { daemonStart: daemonStart$1 } = await import("./start-CUT1hJrN.js");
1998
+ daemonStart$1({
1999
+ port,
2000
+ verbose: args.verbose,
2001
+ accountType: args["account-type"],
2002
+ manual: args.manual,
2003
+ rateLimit,
2004
+ rateLimitWait: args.wait,
2005
+ githubToken: args["github-token"],
2006
+ showToken: args["show-token"],
2007
+ proxyEnv: args["proxy-env"]
2008
+ });
2009
+ return;
2010
+ }
1733
2011
  return runServer({
1734
- port: Number.parseInt(args.port, 10),
2012
+ port,
1735
2013
  verbose: args.verbose,
1736
2014
  accountType: args["account-type"],
1737
2015
  manual: args.manual,
@@ -1756,7 +2034,13 @@ const main = defineCommand({
1756
2034
  auth,
1757
2035
  start,
1758
2036
  "check-usage": checkUsage,
1759
- debug
2037
+ debug,
2038
+ stop,
2039
+ status,
2040
+ logs,
2041
+ restart,
2042
+ enable,
2043
+ disable
1760
2044
  }
1761
2045
  });
1762
2046
  await runMain(main);