@openhoo/hoopilot 0.9.2 → 0.10.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/README.md CHANGED
@@ -58,17 +58,17 @@ Run Hoopilot as a long-lived service from the published multi-arch image on the
58
58
  # 1. Sign in once; the OAuth credential is written to the persisted /data volume.
59
59
  docker run --rm -it -v hoopilot-data:/data ghcr.io/openhoo/hoopilot login
60
60
 
61
- # 2. Run the proxy. An API key is required because the container binds 0.0.0.0.
61
+ # 2. Run the proxy on localhost.
62
62
  docker run -d --name hoopilot --restart unless-stopped \
63
- -p 4141:4141 -e HOOPILOT_API_KEY=local-key \
63
+ -p 127.0.0.1:4141:4141 \
64
64
  -v hoopilot-data:/data ghcr.io/openhoo/hoopilot
65
65
  ```
66
66
 
67
- Tags follow the release version (e.g. `ghcr.io/openhoo/hoopilot:0.8`, `:0.8.3`) plus `:latest`. The image listens on `0.0.0.0:4141`, runs as a non-root user, and stores its OAuth credential at `/data/auth.json` (override with `HOOPILOT_AUTH_FILE`). A `docker-compose.yml` is provided in the repository:
67
+ Tags follow the release version (e.g. `ghcr.io/openhoo/hoopilot:0.8`, `:0.8.3`) plus `:latest`. The image listens on `0.0.0.0:4141`, runs as a non-root user, and stores its OAuth credential at `/data/auth.json` (override with `HOOPILOT_AUTH_FILE`). The Docker image allows unauthenticated local clients by default; set `HOOPILOT_API_KEY` if you publish the port beyond localhost. A `docker-compose.yml` is provided in the repository:
68
68
 
69
69
  ```sh
70
70
  docker compose run --rm hoopilot login # one-time GitHub OAuth
71
- HOOPILOT_API_KEY=local-key docker compose up -d
71
+ docker compose up -d
72
72
  ```
73
73
 
74
74
  ## Update
@@ -91,6 +91,37 @@ npx @openhoo/hoopilot login
91
91
 
92
92
  The login command prints a one-time code, opens `https://github.com/login/device` best-effort, verifies that the returned OAuth token can reach the Copilot API, and stores it in Hoopilot's auth file. Re-run `npx @openhoo/hoopilot login` after upgrading Hoopilot if Copilot reports a supported model as unavailable; older stored tokens can have a reduced model set.
93
93
 
94
+ Add `--print-key` to print the received OAuth token after verification. Login status stays on stderr, so stdout contains only the token. To append the token to a `.env` file, use one of these copyable examples.
95
+
96
+ Local CLI, sh:
97
+
98
+ ```sh
99
+ hoopilot login --print-key | sed 's/^/COPILOT_OAUTH_TOKEN=/' >> .env
100
+ ```
101
+
102
+ Local CLI, PowerShell:
103
+
104
+ ```powershell
105
+ hoopilot login --print-key |
106
+ ForEach-Object { "COPILOT_OAUTH_TOKEN=$_" } |
107
+ Add-Content -Encoding utf8 .env
108
+ ```
109
+
110
+ Docker, sh:
111
+
112
+ ```sh
113
+ docker run --rm -v hoopilot-data:/data ghcr.io/openhoo/hoopilot login --print-key \
114
+ | sed 's/^/COPILOT_OAUTH_TOKEN=/' >> .env
115
+ ```
116
+
117
+ Docker, PowerShell:
118
+
119
+ ```powershell
120
+ docker run --rm -v hoopilot-data:/data ghcr.io/openhoo/hoopilot login --print-key |
121
+ ForEach-Object { "COPILOT_OAUTH_TOKEN=$_" } |
122
+ Add-Content -Encoding utf8 .env
123
+ ```
124
+
94
125
  Then start the proxy:
95
126
 
96
127
  ```powershell
@@ -280,6 +311,7 @@ Options:
280
311
  --api-key-file <path> Read the local API key from a file instead of argv
281
312
  --auth-file <path> OAuth credential store path
282
313
  --copilot-api-base-url <url> Copilot API base URL override
314
+ --print-key Login: print the received OAuth token to stdout
283
315
  --log-level <level> trace, debug, info, warn, error, fatal, or silent
284
316
  --log-format <format> json or pretty. Default: pretty
285
317
  --no-update-check Do not check GitHub for a newer release
package/dist/cli.js CHANGED
@@ -3473,7 +3473,7 @@ async function main2(argv = Bun.argv.slice(2)) {
3473
3473
  if (await printMetaOption(args2)) {
3474
3474
  return;
3475
3475
  }
3476
- args2.logger = commandLogger(args2, "login");
3476
+ args2.logger = commandLogger(args2, "login", args2.printToken ? process.stderr : void 0);
3477
3477
  await runLogin(args2);
3478
3478
  return;
3479
3479
  }
@@ -3556,6 +3556,10 @@ function parseArgs(argv) {
3556
3556
  args.noUpdateCheck = true;
3557
3557
  continue;
3558
3558
  }
3559
+ if (arg === "--print-key" || arg === "--print-token") {
3560
+ args.printToken = true;
3561
+ continue;
3562
+ }
3559
3563
  if (!arg.startsWith("-")) {
3560
3564
  throw new Error(`Unknown argument: ${arg}.`);
3561
3565
  }
@@ -3629,14 +3633,16 @@ function readApiKeyFile(path) {
3629
3633
  }
3630
3634
  async function runLogin(options = {}) {
3631
3635
  const logger = options.logger?.child({ component: "auth" }) ?? noopLogger;
3636
+ const status = loginStatusLogger(Boolean(options.printToken));
3632
3637
  logger.debug({ event: "auth.login.started" }, "starting github copilot browser login");
3633
- console.log("Starting GitHub Copilot browser login...");
3634
- const login = await githubCopilotDeviceLogin({
3638
+ status.info("Starting GitHub Copilot browser login...");
3639
+ const deviceLogin = options.deviceLogin ?? githubCopilotDeviceLogin;
3640
+ const login = await deviceLogin({
3635
3641
  env: options.env,
3636
- logger: console,
3642
+ logger: status,
3637
3643
  openBrowser: openBrowserBestEffort
3638
3644
  });
3639
- console.log("Checking GitHub Copilot access...");
3645
+ status.info("Checking GitHub Copilot access...");
3640
3646
  const access = await verifyCopilotOAuthToken(login.token, options);
3641
3647
  logger.debug(
3642
3648
  { apiBaseUrl: access.apiBaseUrl, event: "auth.login.verified" },
@@ -3653,8 +3659,11 @@ async function runLogin(options = {}) {
3653
3659
  path
3654
3660
  );
3655
3661
  logger.debug({ authStorePath: path, event: "auth.login.stored" }, "copilot credential stored");
3656
- console.log(`Copilot OAuth credential stored at ${path}`);
3657
- console.log("Copilot authentication ready.");
3662
+ status.info(`Copilot OAuth credential stored at ${path}`);
3663
+ status.info("Copilot authentication ready.");
3664
+ if (options.printToken) {
3665
+ console.log(login.token);
3666
+ }
3658
3667
  }
3659
3668
  async function runModels(options = {}) {
3660
3669
  const logger = options.logger?.child({ component: "models" }) ?? noopLogger;
@@ -3790,15 +3799,17 @@ async function verifyCopilotOAuthToken(token, options = {}) {
3790
3799
  token
3791
3800
  };
3792
3801
  }
3793
- function openBrowserBestEffort(url) {
3802
+ function openBrowserBestEffort(url, spawnOpener = spawn) {
3794
3803
  const platform = process.platform;
3795
3804
  const command = platform === "win32" ? "cmd" : platform === "darwin" ? "open" : "xdg-open";
3796
3805
  const args = platform === "win32" ? ["/c", "start", "", url] : [url];
3797
3806
  try {
3798
- const child = spawn(command, args, {
3807
+ const child = spawnOpener(command, args, {
3799
3808
  detached: true,
3800
3809
  stdio: "ignore"
3801
3810
  });
3811
+ child.on("error", () => {
3812
+ });
3802
3813
  child.unref();
3803
3814
  } catch {
3804
3815
  }
@@ -3821,13 +3832,28 @@ function modelIdsFromResponse(body) {
3821
3832
  function withRuntimeEnv(args) {
3822
3833
  return { ...args, env: process.env };
3823
3834
  }
3824
- function commandLogger(args, command) {
3835
+ function commandLogger(args, command, stream) {
3825
3836
  return createHoopilotLogger({
3826
3837
  env: args.env,
3827
3838
  format: args.logFormat,
3828
- level: args.logLevel
3839
+ level: args.logLevel,
3840
+ stream
3829
3841
  }).child({ command, component: "cli" });
3830
3842
  }
3843
+ function loginStatusLogger(writeSecretsToStdout) {
3844
+ if (writeSecretsToStdout) {
3845
+ return {
3846
+ error: (message) => console.error(message),
3847
+ info: (message) => console.error(message),
3848
+ warn: (message) => console.error(message)
3849
+ };
3850
+ }
3851
+ return {
3852
+ error: (message) => console.error(message),
3853
+ info: (message) => console.log(message),
3854
+ warn: (message) => console.warn(message)
3855
+ };
3856
+ }
3831
3857
  function helpText(version) {
3832
3858
  return `hoopilot ${version}
3833
3859
 
@@ -3861,6 +3887,7 @@ Options:
3861
3887
  --api-key-file <path> Read the local API key from a file instead of argv
3862
3888
  --auth-file <path> OAuth credential store path
3863
3889
  --copilot-api-base-url <url> Copilot API base URL override
3890
+ --print-key Login: print the received OAuth token to stdout
3864
3891
  --log-level <level> trace, debug, info, warn, error, fatal, or silent
3865
3892
  --log-format <format> json or pretty. Default: pretty
3866
3893
  --stream-mode <mode> auto, live, or buffer. Auto buffers Windows standalone streams.
@@ -3891,7 +3918,9 @@ if (import.meta.main) {
3891
3918
  }
3892
3919
  export {
3893
3920
  main2 as main,
3921
+ openBrowserBestEffort,
3894
3922
  parseArgs,
3923
+ runLogin,
3895
3924
  runModels,
3896
3925
  runUsage,
3897
3926
  verifyCopilotOAuthToken