@openhoo/hoopilot 0.10.0 → 1.1.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
@@ -1,34 +1,81 @@
1
- # hoopilot
1
+ # Hoopilot
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/%40openhoo%2Fhoopilot?label=npm)](https://www.npmjs.com/package/@openhoo/hoopilot)
4
4
  [![CI](https://github.com/openhoo/hoopilot/actions/workflows/ci.yml/badge.svg)](https://github.com/openhoo/hoopilot/actions/workflows/ci.yml)
5
5
 
6
- OpenAI- and Anthropic-compatible local proxy for GitHub Copilot accounts. It runs on Bun and exposes OpenAI-style `/v1/chat/completions`, `/v1/responses`, `/v1/completions`, and `/v1/models` routes plus Claude Code-compatible `/v1/messages` and `/v1/messages/count_tokens` routes.
6
+ Hoopilot is a local OpenAI- and Anthropic-compatible proxy for GitHub Copilot accounts. It runs on Bun and exposes OpenAI-style `/v1/chat/completions`, `/v1/responses`, `/v1/completions`, and `/v1/models` routes, plus Claude Code-compatible `/v1/messages` and `/v1/messages/count_tokens` routes.
7
7
 
8
- This project uses GitHub Copilot's service endpoints and is not an official GitHub product. The upstream API can change without notice. Use it only with accounts and usage patterns you are allowed to use.
8
+ This project uses GitHub Copilot service endpoints and is not an official GitHub product. Upstream behavior can change without notice. Use Hoopilot only with accounts and usage patterns you are allowed to use.
9
+
10
+ ## Highlights
11
+
12
+ - Browser-based GitHub Copilot OAuth login with a local credential store.
13
+ - OpenAI-compatible Chat Completions, Responses, legacy Completions, and model-list routes.
14
+ - Anthropic Messages compatibility for Claude Code and other Anthropic-style clients.
15
+ - Bundled `codexx` launcher that runs Codex against a local Hoopilot server with the right Responses API provider settings.
16
+ - Local API-key gate, loopback-safe defaults, structured logs, Prometheus metrics, and Copilot quota reporting.
17
+ - npm package, standalone binaries, Docker image, and self-update support for release binaries.
18
+
19
+ ## Quick start
20
+
21
+ Sign in once, then start the proxy on localhost:
22
+
23
+ ```sh
24
+ npx @openhoo/hoopilot login
25
+ npx @openhoo/hoopilot
26
+ ```
27
+
28
+ By default the server listens on `127.0.0.1:4141` and accepts local requests without authentication, so any placeholder works as the client key:
29
+
30
+ ```sh
31
+ export OPENAI_BASE_URL=http://127.0.0.1:4141/v1
32
+ export OPENAI_API_KEY=hoopilot
33
+ ```
34
+
35
+ PowerShell:
36
+
37
+ ```powershell
38
+ $env:OPENAI_BASE_URL = "http://127.0.0.1:4141/v1"
39
+ $env:OPENAI_API_KEY = "hoopilot"
40
+ ```
41
+
42
+ To require clients to authenticate — recommended whenever you expose the proxy beyond localhost — set `HOOPILOT_API_KEY` to a strong, unique secret and send that value as the client key:
43
+
44
+ ```sh
45
+ export HOOPILOT_API_KEY=$(openssl rand -hex 24)
46
+ npx @openhoo/hoopilot
47
+ ```
48
+
49
+ Run Codex through Hoopilot after the server is running:
50
+
51
+ ```sh
52
+ npx --package @openhoo/hoopilot codexx
53
+ ```
9
54
 
10
55
  ## Install
11
56
 
12
57
  ### npm
13
58
 
14
- ```powershell
59
+ Run without installing:
60
+
61
+ ```sh
15
62
  npx @openhoo/hoopilot
16
63
  ```
17
64
 
18
- Or install it globally:
65
+ Or install the package globally:
19
66
 
20
- ```powershell
67
+ ```sh
21
68
  npm install -g @openhoo/hoopilot
22
69
  bun add -g @openhoo/hoopilot
23
70
  ```
24
71
 
25
- ### Standalone Binary
72
+ ### Standalone binary
26
73
 
27
- When the npm registry is unreachable but GitHub is reachable, install a prebuilt self-contained binary from the latest GitHub release. No Node.js or Bun runtime is needed to run it.
74
+ When npm is unavailable but GitHub releases are reachable, install a prebuilt self-contained binary. Node.js and Bun are not required to run the binary.
28
75
 
29
- Linux / macOS from PowerShell:
76
+ Linux/macOS:
30
77
 
31
- ```powershell
78
+ ```sh
32
79
  curl -fsSL https://raw.githubusercontent.com/openhoo/hoopilot/main/scripts/install.sh | sh
33
80
  ```
34
81
 
@@ -38,9 +85,9 @@ Windows PowerShell:
38
85
  irm https://raw.githubusercontent.com/openhoo/hoopilot/main/scripts/install.ps1 | iex
39
86
  ```
40
87
 
41
- The installer detects your OS, CPU architecture, and libc, downloads the matching binary, verifies its SHA-256 checksum, and installs it to `~/.local/bin` on Linux/macOS or `%LOCALAPPDATA%\Programs\hoopilot` on Windows. Override the location with `HOOPILOT_INSTALL_DIR`, or pin a version:
88
+ The installer detects your OS, CPU architecture, and libc, downloads the matching binary, verifies its SHA-256 checksum, and installs it to `~/.local/bin` on Linux/macOS or `%LOCALAPPDATA%\Programs\hoopilot` on Windows. Override the install directory with `HOOPILOT_INSTALL_DIR`, or pin a version:
42
89
 
43
- ```powershell
90
+ ```sh
44
91
  curl -fsSL https://raw.githubusercontent.com/openhoo/hoopilot/main/scripts/install.sh | sh -s -- --version <version> --dir ~/bin
45
92
  ```
46
93
 
@@ -58,16 +105,23 @@ Run Hoopilot as a long-lived service from the published multi-arch image on the
58
105
  # 1. Sign in once; the OAuth credential is written to the persisted /data volume.
59
106
  docker run --rm -it -v hoopilot-data:/data ghcr.io/openhoo/hoopilot login
60
107
 
61
- # 2. Run the proxy on localhost.
108
+ # 2. Run the proxy on localhost with a strong, unique API key.
109
+ export HOOPILOT_API_KEY=$(openssl rand -hex 24)
62
110
  docker run -d --name hoopilot --restart unless-stopped \
63
111
  -p 127.0.0.1:4141:4141 \
112
+ -e HOOPILOT_API_KEY \
64
113
  -v hoopilot-data:/data ghcr.io/openhoo/hoopilot
65
114
  ```
66
115
 
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:
116
+ Tags follow the release version, for example `ghcr.io/openhoo/hoopilot:0.10`, `:0.10.0`, and `:latest`. The image listens on `0.0.0.0:4141` (required so Docker port publishing can reach it), runs as a non-root user, and stores its OAuth credential at `/data/auth.json` by default. Override that path with `HOOPILOT_AUTH_FILE`.
117
+
118
+ Because it binds a non-loopback interface, the image fails closed: it refuses to start unless you set `HOOPILOT_API_KEY` to a strong, unique secret (well-known demo keys are rejected). Clients then send that key as `Authorization: Bearer <key>` or `x-api-key: <key>`. To intentionally run without authentication — for example behind your own authenticating proxy — set `HOOPILOT_ALLOW_UNAUTHENTICATED=1`.
119
+
120
+ A `docker-compose.yml` is provided. Set `HOOPILOT_API_KEY` first; compose passes it through to the container:
68
121
 
69
122
  ```sh
70
- docker compose run --rm hoopilot login # one-time GitHub OAuth
123
+ docker compose run --rm hoopilot login
124
+ export HOOPILOT_API_KEY=$(openssl rand -hex 24)
71
125
  docker compose up -d
72
126
  ```
73
127
 
@@ -75,220 +129,233 @@ docker compose up -d
75
129
 
76
130
  Standalone binaries update themselves in place from the latest GitHub release:
77
131
 
78
- ```powershell
132
+ ```sh
79
133
  hoopilot update
80
134
  ```
81
135
 
82
136
  npm installs report when a newer version is available and print the right command. Hoopilot checks GitHub at most once a day in the background. Disable the check with `--no-update-check`, `HOOPILOT_NO_UPDATE_CHECK`, or `NO_UPDATE_NOTIFIER`.
83
137
 
84
- ## Run
138
+ ## Running the proxy
85
139
 
86
- First sign in with GitHub Copilot OAuth in your browser:
140
+ Login uses GitHub's browser/device flow, verifies that the returned OAuth token can reach the Copilot API, and stores it locally:
87
141
 
88
- ```powershell
89
- npx @openhoo/hoopilot login
142
+ ```sh
143
+ hoopilot login
90
144
  ```
91
145
 
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.
146
+ Default credential paths:
147
+
148
+ - Linux/macOS: `$HOME/.config/hoopilot/auth.json`
149
+ - Windows: `%APPDATA%\hoopilot\auth.json`
93
150
 
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.
151
+ Override the path with `HOOPILOT_AUTH_FILE` or `--auth-file`.
95
152
 
96
- Local CLI, sh:
153
+ Start the server:
97
154
 
98
155
  ```sh
99
- hoopilot login --print-key | sed 's/^/COPILOT_OAUTH_TOKEN=/' >> .env
156
+ hoopilot --port 4141
100
157
  ```
101
158
 
102
- Local CLI, PowerShell:
159
+ By default Hoopilot listens on `127.0.0.1:4141`. If `HOOPILOT_API_KEY` is unset, local requests are accepted without client authentication. Binding to a non-loopback host requires either a strong, unique `HOOPILOT_API_KEY` or the explicit `--allow-unauthenticated` / `HOOPILOT_ALLOW_UNAUTHENTICATED=1` opt-in. Well-known demo keys are always rejected on a non-loopback host, even with the unauthenticated opt-in.
103
160
 
104
- ```powershell
105
- hoopilot login --print-key |
106
- ForEach-Object { "COPILOT_OAUTH_TOKEN=$_" } |
107
- Add-Content -Encoding utf8 .env
108
- ```
161
+ When an API key is configured, clients may send it as either `Authorization: Bearer <key>` or `x-api-key: <key>`.
162
+
163
+ Cross-origin browser requests are always blocked, even when an API key is set, so a malicious web page cannot drive the local proxy. Requests from loopback origins are allowed; to permit specific web origins, list them in `HOOPILOT_ALLOWED_ORIGINS` (comma-separated).
109
164
 
110
- Docker, sh:
165
+ ## Client setup
166
+
167
+ ### OpenAI-compatible clients
111
168
 
112
169
  ```sh
113
- docker run --rm -v hoopilot-data:/data ghcr.io/openhoo/hoopilot login --print-key \
114
- | sed 's/^/COPILOT_OAUTH_TOKEN=/' >> .env
170
+ export OPENAI_BASE_URL=http://127.0.0.1:4141/v1
171
+ export OPENAI_API_KEY=hoopilot
115
172
  ```
116
173
 
117
- Docker, PowerShell:
174
+ The client key value is arbitrary when the server runs without `HOOPILOT_API_KEY`; if you set one, use that value here instead.
118
175
 
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
176
+ Use any model returned by:
177
+
178
+ ```sh
179
+ hoopilot models
123
180
  ```
124
181
 
125
- Then start the proxy:
182
+ ### Claude Code and Anthropic-style clients
126
183
 
127
- ```powershell
128
- npx @openhoo/hoopilot
184
+ ```sh
185
+ export ANTHROPIC_BASE_URL=http://127.0.0.1:4141
186
+ export ANTHROPIC_AUTH_TOKEN=hoopilot
187
+ claude
129
188
  ```
130
189
 
131
- By default Hoopilot listens on `127.0.0.1:4141` and reads the stored OAuth credential from:
190
+ Hoopilot accepts the local key as `x-api-key` too, so `ANTHROPIC_API_KEY` also works for clients that send Anthropic's standard API-key header.
132
191
 
133
- - Linux/macOS: `$HOME/.config/hoopilot/auth.json`
134
- - Windows: `$env:APPDATA\hoopilot\auth.json`
192
+ ### Codex
135
193
 
136
- Override the path with `HOOPILOT_AUTH_FILE` or `--auth-file`.
194
+ Use the bundled `codexx` command after Hoopilot is running:
137
195
 
138
- For a local API key:
139
-
140
- ```powershell
141
- $env:HOOPILOT_API_KEY = "local-key"
142
- npx @openhoo/hoopilot --port 4141
196
+ ```sh
197
+ codexx
143
198
  ```
144
199
 
145
- Point OpenAI-compatible clients at:
200
+ Without a global install:
146
201
 
147
- ```powershell
148
- $env:OPENAI_BASE_URL = "http://127.0.0.1:4141/v1"
149
- $env:OPENAI_API_KEY = "local-key"
202
+ ```sh
203
+ npx --package @openhoo/hoopilot codexx
150
204
  ```
151
205
 
152
- Point Claude Code at the same server through its Anthropic base URL:
206
+ If the server requires an API key, set `HOOPILOT_API_KEY` (or `CODEXX_API_KEY`) in the `codexx` environment to match.
153
207
 
154
- ```powershell
155
- $env:ANTHROPIC_BASE_URL = "http://127.0.0.1:4141"
156
- $env:ANTHROPIC_AUTH_TOKEN = "local-key"
157
- claude
208
+ `codexx` does not start Hoopilot and does not alter your shell environment. It starts `codex` with a temporary `hoopilot` model provider pointed at `http://127.0.0.1:4141/v1`, uses the Responses API wire format, disables Responses WebSockets for that provider, maps `HOOPILOT_API_KEY` (or a random throwaway key when none is set) to `OPENAI_API_KEY` for the child process, passes `--disable network_proxy`, and removes standard proxy variables from the spawned Codex process.
209
+
210
+ `codexx` defaults to `gpt-5.5` with `model_reasoning_effort="xhigh"`. Before starting Codex, it checks `/v1/models` and reports if the logged-in Copilot account does not advertise the requested model. Set `CODEXX_MODEL` to one of the listed models, or log in with a Copilot account that has access to the default model.
211
+
212
+ Codex compaction posts to `/v1/responses/compact` for OpenAI- and Azure-named providers. Hoopilot handles that route with a unary Copilot Responses request and returns the `{ "output": [...] }` summary Codex expects, so compaction works through either `codexx` or a direct OpenAI-compatible base URL override.
213
+
214
+ ## Authentication
215
+
216
+ Hoopilot supports one upstream credential flow: GitHub Copilot OAuth browser login.
217
+
218
+ ```sh
219
+ hoopilot login
220
+ hoopilot
158
221
  ```
159
222
 
160
- Hoopilot accepts the local key as either `Authorization: Bearer <key>` or
161
- `x-api-key: <key>`, so `ANTHROPIC_API_KEY` also works for clients that send
162
- Anthropic's `x-api-key` header.
223
+ Direct bearer tokens, GitHub CLI token fallback, classic GitHub PATs, and fine-grained GitHub PATs are not supported.
163
224
 
164
- Use with Codex CLI after Hoopilot is running, via the bundled `codexx` command. It runs Codex against the local server with the right model provider — selecting `gpt-5.5` over Copilot's Responses API, which a plain `openai_base_url` override does not configure (see the note below):
225
+ Re-run `hoopilot login` after upgrading Hoopilot if Copilot reports a supported model as unavailable. Older stored tokens can have a reduced model set.
165
226
 
166
- ```powershell
167
- $env:HOOPILOT_API_KEY = "local-key"
168
- codexx
227
+ To print the verified OAuth token for another local tool, use `--print-key`. Login status goes to stderr, so stdout contains only the token.
228
+
229
+ ```sh
230
+ hoopilot login --print-key | sed 's/^/COPILOT_OAUTH_TOKEN=/' >> .env
169
231
  ```
170
232
 
171
- Without a global install, run it through npm:
233
+ PowerShell:
172
234
 
173
235
  ```powershell
174
- $env:HOOPILOT_API_KEY = "local-key"
175
- npx --package @openhoo/hoopilot codexx
236
+ hoopilot login --print-key |
237
+ ForEach-Object { "COPILOT_OAUTH_TOKEN=$_" } |
238
+ Add-Content -Encoding utf8 .env
176
239
  ```
177
240
 
178
- `codexx` does not start Hoopilot and does not change your shell environment. It runs
179
- `codex` with a temporary `hoopilot` model provider pointed at
180
- `http://127.0.0.1:4141/v1`, disables Codex Responses WebSockets for that provider,
181
- maps `HOOPILOT_API_KEY` to `OPENAI_API_KEY` for that child process, passes
182
- `--disable network_proxy` to Codex, and removes standard proxy variables from the
183
- spawned Codex process so Codex talks directly to the local server. Override the local
184
- URL with `CODEXX_BASE_URL`, the local key with `CODEXX_API_KEY`, or the Codex
185
- executable with `CODEXX_CODEX_BIN`, the model with `CODEXX_MODEL`, or the reasoning
186
- effort with `CODEXX_MODEL_REASONING_EFFORT`.
187
-
188
- `codexx` defaults to `gpt-5.5` with `model_reasoning_effort="xhigh"`. Codex sends
189
- those requests through its Responses API provider, and Hoopilot forwards them to
190
- Copilot's Responses endpoint because `gpt-5.5` is not available through Copilot's
191
- chat-completions endpoint. Before starting Codex, `codexx` checks
192
- `http://127.0.0.1:4141/v1/models` and reports if the logged-in Copilot account does
193
- not advertise the requested model. Set `CODEXX_MODEL` to one of the listed models,
194
- or log in with a Copilot account that has `gpt-5.5`.
195
-
196
- When Codex compacts a long session it POSTs to `/v1/responses/compact` — a server-side
197
- endpoint it expects from `OpenAI`- and Azure-named providers and for which it has no
198
- local fallback, so an unhandled route would abort compaction. Hoopilot handles it by
199
- running the supplied conversation through Copilot's Responses endpoint as a unary
200
- request and returning the resulting `{ "output": [...] }` summary, so compaction works
201
- whether Codex points at Hoopilot through `codexx` or through a plain `OPENAI_BASE_URL`
202
- override of the built-in `openai` provider.
203
-
204
- If no `HOOPILOT_API_KEY` is configured, Hoopilot accepts local requests without client authentication. Binding to a non-loopback host requires `HOOPILOT_API_KEY` unless `--allow-unauthenticated` is set.
241
+ Docker:
242
+
243
+ ```sh
244
+ docker run --rm -v hoopilot-data:/data ghcr.io/openhoo/hoopilot login --print-key \
245
+ | sed 's/^/COPILOT_OAUTH_TOKEN=/' >> .env
246
+ ```
205
247
 
206
248
  ## Logging
207
249
 
208
- Hoopilot uses Pino for structured logs. Server startup, request completion, upstream Copilot failures, model-list fallback, auth failures, and update-check diagnostics are logged with stable event names and request IDs. Logs never include request bodies, prompt text, completions, stream chunks, OAuth tokens, API keys, authorization headers, cookies, or auth-file contents.
250
+ Hoopilot uses Pino for structured logs. Server startup, request completion, upstream Copilot failures, model-list fallback, auth failures, and update-check diagnostics are logged with stable event names and request IDs.
251
+
252
+ Logs never include request bodies, prompt text, completions, stream chunks, OAuth tokens, API keys, authorization headers, cookies, or auth-file contents.
209
253
 
210
254
  Console logs default to pretty output at `info` level:
211
255
 
212
- ```powershell
213
- npx @openhoo/hoopilot --log-level info --log-format pretty
256
+ ```sh
257
+ hoopilot --log-level info --log-format pretty
214
258
  ```
215
259
 
216
- For machine-readable newline-delimited JSON:
260
+ For newline-delimited JSON:
217
261
 
218
- ```powershell
219
- npx @openhoo/hoopilot --log-level info --log-format json
262
+ ```sh
263
+ hoopilot --log-level info --log-format json
220
264
  ```
221
265
 
222
- Equivalent environment variables:
223
-
224
- - `HOOPILOT_LOG_LEVEL`: `trace`, `debug`, `info`, `warn`, `error`, `fatal`, or `silent`. Default: `info`.
225
- - `HOOPILOT_LOG_FORMAT`: `json` or `pretty`. Default: `pretty`.
226
-
227
266
  Incoming `x-request-id` headers are preserved on responses. If a request has no ID, Hoopilot generates one and returns it as `x-request-id`.
228
267
 
229
268
  ## Metrics and usage
230
269
 
231
- Hoopilot tracks token usage, request counts, and latency in memory while the server runs, and can report your GitHub Copilot account quota (premium-request "credit" usage).
270
+ Hoopilot tracks token usage, request counts, and latency in memory while the server runs. It can also report your GitHub Copilot account quota and premium-request usage, plus your GitHub REST API rate-limit budget.
271
+
272
+ - `GET /metrics` returns Prometheus text (`text/plain; version=0.0.4`). It exposes request counters, upstream call counters, token counters by model and type, a request-duration histogram, an in-flight gauge, Copilot quota gauges, and GitHub REST API rate-limit gauges (`hoopilot_github_ratelimit_limit`, `_remaining`, `_used`, `_reset_timestamp_seconds`, `_retry_after_seconds`, labelled by `resource`) — the quota and rate-limit series appear after `/v1/usage` has been fetched at least once. Counters reset to zero on restart, which Prometheus handles natively.
273
+ - `GET /v1/usage` returns JSON combining the proxy metrics snapshot with live Copilot quota fetched from GitHub and cached for 60 seconds. If quota cannot be read, `copilot` is `null` and `copilot_error` explains why. The snapshot's `proxy.githubRateLimit` field reports the most recent GitHub REST rate-limit budget per resource (`limit`, `remaining`, `used`, `resetAt`, `retryAfterSeconds`, `observedAt`).
274
+ - `hoopilot usage` prints your Copilot plan and quota — and, when GitHub returns them, your GitHub API rate-limit budget — from the command line.
232
275
 
233
- - `GET /metrics` returns Prometheus text (`text/plain; version=0.0.4`). It exposes request counters (`hoopilot_requests_total`), upstream call counters (`hoopilot_upstream_requests_total`), token counters by model and type (`hoopilot_tokens_total{model,type}`), a request-duration histogram (`hoopilot_request_duration_seconds`), an in-flight gauge, and—once `/v1/usage` has been fetched at least once—Copilot quota gauges (`hoopilot_copilot_quota_remaining{category}`, `_entitlement`, `_used`, `_percent_remaining`, `_overage_count`, `_overage_entitlement`, `_unlimited`, `_overage_permitted`, `_has_quota`, `_token_based_billing`, and category reset/snapshot timestamps). Counters reset to zero on restart, which Prometheus handles natively.
234
- - `GET /v1/usage` returns JSON combining the proxy metrics snapshot with live Copilot quota fetched from GitHub (cached for 60 seconds). If the quota cannot be read, `copilot` is `null` and `copilot_error` explains why, but the proxy metrics are still returned.
235
- - `hoopilot usage` prints your Copilot plan and quota from the command line.
276
+ Token usage is read from the upstream `usage` object. For streaming chat completions, usage is only available when the client sends `stream_options: {"include_usage": true}`; Hoopilot does not inject that flag. Responses API streaming always reports usage, so streamed Responses requests are fully accounted.
236
277
 
237
- Token usage is read from the upstream `usage` object. For streaming chat completions, usage is only available when the client sends `stream_options: {"include_usage": true}`; Hoopilot never injects it, so streamed chat requests without that flag contribute request and latency metrics but not token counts. The Responses API always reports usage, so streamed Responses requests are fully accounted.
278
+ GitHub API usage is read from the `x-ratelimit-*` response headers that `api.github.com` returns on the `copilot_internal/user` quota call Hoopilot already makes, so it costs no extra request. (The Copilot completion host `api.githubcopilot.com` does not currently emit these headers, so per-completion rate-limit data is not yet available there.)
238
279
 
239
280
  `/metrics` and `/v1/usage` are subject to the same `HOOPILOT_API_KEY` gate as the other routes.
240
281
 
241
- ## Authentication
282
+ ## Troubleshooting
242
283
 
243
- Hoopilot supports one credential flow: GitHub Copilot OAuth browser login.
284
+ ### Codex auth errors
244
285
 
245
- ```powershell
246
- npx @openhoo/hoopilot login
247
- npx @openhoo/hoopilot
286
+ Hoopilot does not return raw `403` responses to Codex for authentication or Copilot-entitlement failures. Local Hoopilot API-key problems return `401 invalid_api_key`; OAuth credential and upstream Copilot auth failures return `401 copilot_auth_error`.
287
+
288
+ Verify browser login and the local proxy before retrying Codex:
289
+
290
+ ```sh
291
+ hoopilot login
292
+ hoopilot --port 4141
248
293
  ```
249
294
 
250
- Direct bearer tokens, GitHub CLI token fallback, classic GitHub PATs, and fine-grained GitHub PATs are not supported.
295
+ Then, in another shell:
251
296
 
252
- Supported authentication-related settings:
297
+ ```sh
298
+ curl http://127.0.0.1:4141/v1/models
299
+ codexx
300
+ ```
253
301
 
254
- - `HOOPILOT_AUTH_FILE`: OAuth credential store path.
255
- - `HOOPILOT_GITHUB_CLIENT_ID`: GitHub OAuth app client ID override. The default uses the same GitHub Copilot OAuth app as opencode's Copilot provider.
256
- - `HOOPILOT_GITHUB_DOMAIN`: GitHub domain override. Default: `github.com`.
257
- - `COPILOT_API_BASE_URL`: upstream Copilot API base URL override. Default: `https://api.githubcopilot.com`.
258
- - `HOOPILOT_GITHUB_API_BASE_URL`: GitHub REST API base URL used for the Copilot quota lookup. Default: `https://api.github.com`.
259
- - `HOOPILOT_ALLOW_UNSAFE_UPSTREAM=1`: allow sending the stored OAuth token to nonstandard HTTPS Copilot/GitHub API hosts. Use only for trusted test or enterprise endpoints.
302
+ If you started the server with `HOOPILOT_API_KEY`, add `-H "Authorization: Bearer $HOOPILOT_API_KEY"` to the curl command and set the same `HOOPILOT_API_KEY` for `codexx`.
260
303
 
261
- ## Codex Auth Errors
304
+ If `/v1/models` returns `401 copilot_auth_error`, rerun `hoopilot login` and confirm that the GitHub account has active Copilot access.
262
305
 
263
- Hoopilot does not return raw `403` responses to Codex for authentication or Copilot-entitlement failures. Local Hoopilot API-key problems return `401 invalid_api_key`; OAuth credential and upstream Copilot auth failures return `401 copilot_auth_error`.
306
+ ## Configuration
264
307
 
265
- In PowerShell, verify the browser login and local proxy before retrying Codex:
308
+ Server and local-client settings:
266
309
 
267
- ```powershell
268
- npx @openhoo/hoopilot login
269
- $env:HOOPILOT_API_KEY = "local-key"
270
- npx @openhoo/hoopilot --port 4141
271
- ```
310
+ | Setting | Description |
311
+ | --- | --- |
312
+ | `HOST` / `--host` | Host to listen on. Default: `127.0.0.1` for local runs; Docker sets `0.0.0.0`. |
313
+ | `PORT` / `--port` | Port to listen on. Default: `4141`. |
314
+ | `HOOPILOT_API_KEY` / `--api-key` | Require clients to send `Authorization: Bearer <key>` or `x-api-key: <key>`. Must be a strong, unique secret on non-loopback binds; well-known demo keys are rejected. |
315
+ | `--api-key-file` | Read the local API key from a file instead of argv. |
316
+ | `HOOPILOT_ALLOWED_ORIGINS` | Comma-separated browser origins allowed to make cross-origin requests. Loopback origins are always allowed; every other origin is blocked. |
317
+ | `HOOPILOT_ALLOW_UNAUTHENTICATED` / `--allow-unauthenticated` | Allow non-loopback binds without a local API key. |
318
+ | `HOOPILOT_STREAM_MODE` / `--stream-mode` | `auto`, `live`, or `buffer`. `auto` buffers streams for Windows standalone binaries. |
272
319
 
273
- Then, in another PowerShell session:
320
+ Copilot and GitHub settings:
274
321
 
275
- ```powershell
276
- $env:OPENAI_API_KEY = "local-key"
277
- Invoke-RestMethod -Headers @{ Authorization = "Bearer $env:OPENAI_API_KEY" } `
278
- http://127.0.0.1:4141/v1/models
279
- codexx
280
- ```
322
+ | Setting | Description |
323
+ | --- | --- |
324
+ | `HOOPILOT_AUTH_FILE` / `--auth-file` | OAuth credential store path. |
325
+ | `HOOPILOT_GITHUB_CLIENT_ID` | GitHub OAuth app client ID override. |
326
+ | `HOOPILOT_GITHUB_DOMAIN` | GitHub domain override. Default: `github.com`. |
327
+ | `COPILOT_API_BASE_URL` / `--copilot-api-base-url` | Upstream Copilot API base URL. Default: `https://api.githubcopilot.com`. |
328
+ | `HOOPILOT_GITHUB_API_BASE_URL` | GitHub REST API base URL used for quota lookup. Default: `https://api.github.com`. |
329
+ | `HOOPILOT_ALLOW_UNSAFE_UPSTREAM=1` | Allow sending the stored OAuth token to nonstandard HTTPS Copilot/GitHub API hosts. Use only for trusted test or enterprise endpoints. |
281
330
 
282
- If that returns `401 copilot_auth_error`, rerun `npx @openhoo/hoopilot login` and confirm the GitHub account has active Copilot access.
331
+ Logging and update settings:
283
332
 
284
- ## CLI
333
+ | Setting | Description |
334
+ | --- | --- |
335
+ | `HOOPILOT_LOG_LEVEL` / `--log-level` | `trace`, `debug`, `info`, `warn`, `error`, `fatal`, or `silent`. Default: `info`. |
336
+ | `HOOPILOT_LOG_FORMAT` / `--log-format` | `pretty` or `json`. Default: `pretty`. |
337
+ | `HOOPILOT_NO_UPDATE_CHECK` / `--no-update-check` | Disable background update checks. `NO_UPDATE_NOTIFIER` is also honored. |
285
338
 
286
- ```powershell
339
+ `codexx` settings:
340
+
341
+ | Setting | Description |
342
+ | --- | --- |
343
+ | `CODEXX_BASE_URL` | OpenAI-compatible Hoopilot base URL. Default: `http://127.0.0.1:4141/v1`. |
344
+ | `CODEXX_API_KEY` | API key sent to Hoopilot. Falls back to `HOOPILOT_API_KEY`, then a random per-run key for an unauthenticated local server. |
345
+ | `CODEXX_CODEX_BIN` | Codex executable to run. Default: `codex`. |
346
+ | `CODEXX_MODEL` | Codex model to use. Default: `gpt-5.5`. |
347
+ | `CODEXX_MODEL_REASONING_EFFORT` | Codex reasoning effort. Default: `xhigh`. |
348
+ | `CODEXX_SKIP_MODEL_PREFLIGHT=1` | Skip the `/v1/models` availability check before starting Codex. |
349
+
350
+ ## CLI reference
351
+
352
+ ```txt
287
353
  hoopilot [serve] [options]
288
354
  hoopilot codexx [codex options] [prompt]
289
355
  hoopilot login [options]
290
356
  hoopilot models [options]
291
357
  hoopilot usage [options]
358
+ hoopilot update
292
359
  ```
293
360
 
294
361
  Commands:
@@ -314,34 +381,38 @@ Options:
314
381
  --print-key Login: print the received OAuth token to stdout
315
382
  --log-level <level> trace, debug, info, warn, error, fatal, or silent
316
383
  --log-format <format> json or pretty. Default: pretty
384
+ --stream-mode <mode> auto, live, or buffer. Auto buffers Windows standalone streams.
317
385
  --no-update-check Do not check GitHub for a newer release
318
386
  --allow-unauthenticated Allow non-loopback bind without --api-key
387
+ -h, --help Show help
388
+ -v, --version Show version
319
389
  ```
320
390
 
321
391
  ## Endpoints
322
392
 
323
- - `GET /healthz`
393
+ - `GET /` and `GET /healthz`
324
394
  - `GET /metrics`
325
395
  - `GET /v1/models`
326
396
  - `GET /v1/usage`
327
- - `POST /v1/messages`
328
- - `POST /v1/messages/count_tokens`
329
397
  - `POST /v1/chat/completions`
330
398
  - `POST /v1/responses`
399
+ - `POST /v1/responses/compact`
331
400
  - `POST /v1/completions`
401
+ - `POST /v1/messages`
402
+ - `POST /v1/messages/count_tokens`
332
403
 
333
- `/v1/chat/completions` and `/v1/responses` are proxied to the matching Copilot endpoints as directly as possible. `/v1/messages` translates Anthropic Messages requests and responses to Copilot's Responses endpoint for Claude Code and other Anthropic-compatible clients. `/v1/messages/count_tokens` returns a local token estimate for Claude Code preflights because Copilot does not expose Anthropic's count-tokens route. `/v1/completions` translates legacy completion requests and responses to the closest chat completions equivalent. `GET /metrics` and `GET /v1/usage` report proxy metrics and Copilot quota (see [Metrics and usage](#metrics-and-usage)).
404
+ `/v1/chat/completions` and `/v1/responses` are proxied to the matching Copilot endpoints as directly as possible. `/v1/messages` translates Anthropic Messages requests and responses to Copilot's Responses endpoint. `/v1/messages/count_tokens` returns a local token estimate for Claude Code preflights because Copilot does not expose Anthropic's count-tokens route. `/v1/completions` translates legacy completion requests and responses to the closest chat-completions equivalent. `GET /v1/responses` returns an explicit unsupported-WebSocket response; `codexx` configures Codex to use HTTP Responses instead.
334
405
 
335
406
  ## Development
336
407
 
337
- ```powershell
408
+ ```sh
338
409
  bun install
339
410
  bun run check
340
411
  ```
341
412
 
342
413
  Useful scripts:
343
414
 
344
- ```powershell
415
+ ```sh
345
416
  bun run test
346
417
  bun run test:coverage
347
418
  bun run typecheck
@@ -351,7 +422,11 @@ bun run biome:fix
351
422
 
352
423
  ## Release
353
424
 
354
- Commits merged to `main` are evaluated by hooversion after CI passes. When a release is produced, the release workflow creates the release commit, tag, and GitHub release automatically, publishes the package through npm trusted publishing, then cross-compiles standalone binaries for every supported platform (`scripts/build-binaries.sh`) and attaches them plus a `SHA256SUMS` manifest to the GitHub release. Build all binaries locally with `bun run build:binaries`.
425
+ Commits merged to `main` are evaluated by hooversion after CI passes. When a release is produced, the release workflow creates the release commit, tag, and GitHub release automatically, publishes the package through npm trusted publishing, then cross-compiles standalone binaries for every supported platform with `scripts/build-binaries.sh` and attaches them plus a `SHA256SUMS` manifest to the GitHub release. Build all binaries locally with:
426
+
427
+ ```sh
428
+ bun run build:binaries
429
+ ```
355
430
 
356
431
  Configure npm trusted publishing for `@openhoo/hoopilot` on npmjs.com before relying on automatic publication. The workflow uses GitHub Actions OIDC with `npm publish --access public --provenance`.
357
432
 
@@ -52,7 +52,6 @@ function asRecord(value) {
52
52
 
53
53
  // src/codexx.ts
54
54
  var DEFAULT_BASE_URL = "http://127.0.0.1:4141/v1";
55
- var DEFAULT_API_KEY = "local-key";
56
55
  var DEFAULT_CODEX_BIN = "codex";
57
56
  var DEFAULT_MODEL = "gpt-5.5";
58
57
  var DEFAULT_REASONING_EFFORT = "xhigh";
@@ -68,7 +67,7 @@ var PROXY_ENV_KEYS = [
68
67
  ];
69
68
  function buildCodexxInvocation(argv, env = process.env) {
70
69
  const baseUrl = envValue(env.CODEXX_BASE_URL) ?? DEFAULT_BASE_URL;
71
- const apiKey = envValue(env.CODEXX_API_KEY) ?? envValue(env.HOOPILOT_API_KEY) ?? DEFAULT_API_KEY;
70
+ const apiKey = envValue(env.CODEXX_API_KEY) ?? envValue(env.HOOPILOT_API_KEY) ?? generateEphemeralApiKey();
72
71
  const command = envValue(env.CODEXX_CODEX_BIN) ?? DEFAULT_CODEX_BIN;
73
72
  const model = envValue(env.CODEXX_MODEL) ?? DEFAULT_MODEL;
74
73
  const reasoningEffort = envValue(env.CODEXX_MODEL_REASONING_EFFORT) ?? DEFAULT_REASONING_EFFORT;
@@ -102,6 +101,9 @@ function buildCodexxInvocation(argv, env = process.env) {
102
101
  model
103
102
  };
104
103
  }
104
+ function generateEphemeralApiKey() {
105
+ return `codexx-${crypto.randomUUID()}`;
106
+ }
105
107
  function withoutProxyEnv(env) {
106
108
  const next = { ...env };
107
109
  for (const key of PROXY_ENV_KEYS) {
@@ -142,7 +144,7 @@ async function verifyCodexxModel(invocation, fetcher = fetch) {
142
144
  response = await fetcher(modelsUrl, {
143
145
  headers: {
144
146
  accept: "application/json",
145
- authorization: `Bearer ${invocation.env.OPENAI_API_KEY ?? DEFAULT_API_KEY}`
147
+ authorization: `Bearer ${invocation.env.OPENAI_API_KEY ?? generateEphemeralApiKey()}`
146
148
  },
147
149
  method: "GET"
148
150
  });
@@ -174,7 +176,9 @@ Usage:
174
176
  Environment:
175
177
  CODEXX_BASE_URL OpenAI-compatible base URL. Default: ${DEFAULT_BASE_URL}
176
178
  CODEXX_API_KEY API key sent to the local Hoopilot server.
177
- HOOPILOT_API_KEY Used as the API key when CODEXX_API_KEY is unset.
179
+ HOOPILOT_API_KEY Used as the API key when CODEXX_API_KEY is unset. When
180
+ neither is set, a random throwaway key is generated for
181
+ an unauthenticated local server.
178
182
  CODEXX_CODEX_BIN Codex executable to run. Default: ${DEFAULT_CODEX_BIN}
179
183
  CODEXX_MODEL Codex model to use. Default: ${DEFAULT_MODEL}
180
184
  CODEXX_MODEL_REASONING_EFFORT
@@ -218,4 +222,4 @@ export {
218
222
  main,
219
223
  verifyCodexxModel
220
224
  };
221
- //# sourceMappingURL=chunk-7GSQVYYT.js.map
225
+ //# sourceMappingURL=chunk-JU6F5L34.js.map