@dianshuv/copilot-api 0.10.1 → 0.11.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 +16 -10
- package/dist/main.mjs +43 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ make down
|
|
|
57
57
|
| Option | Description | Default |
|
|
58
58
|
|--------|-------------|---------|
|
|
59
59
|
| `--port`, `-p` | Port to listen on | 4141 |
|
|
60
|
-
| `--host`, `-H` | Host/interface to bind to
|
|
60
|
+
| `--host`, `-H` | Host/interface to bind to (`0.0.0.0` = all interfaces) | `127.0.0.1` |
|
|
61
61
|
| `--verbose`, `-v` | Enable verbose logging | false |
|
|
62
62
|
| `--account-type`, `-a` | Account type (individual, business, enterprise) | individual |
|
|
63
63
|
| `--manual` | Manual request approval mode | false |
|
|
@@ -200,17 +200,23 @@ The startup banner prints whether auth is on/off and, when on, the source
|
|
|
200
200
|
|
|
201
201
|
### Network binding
|
|
202
202
|
|
|
203
|
-
The proxy **binds
|
|
204
|
-
instance is reachable from
|
|
205
|
-
|
|
203
|
+
The proxy **binds loopback (`127.0.0.1`) by default**, so an unconfigured
|
|
204
|
+
instance is reachable only from the local machine. To expose it on your network
|
|
205
|
+
— behind auth, a tunnel, or inside a container — opt into an all-interfaces
|
|
206
|
+
bind explicitly:
|
|
206
207
|
|
|
207
208
|
```sh
|
|
208
|
-
copilot-api start --host
|
|
209
|
+
copilot-api start --host 0.0.0.0
|
|
209
210
|
```
|
|
210
211
|
|
|
211
|
-
The
|
|
212
|
-
|
|
213
|
-
|
|
212
|
+
The bind host resolves with **`--host` flag > `HOST` env > `127.0.0.1`**. Note
|
|
213
|
+
that a `HOST` env var — inherited from your shell or a container — also widens
|
|
214
|
+
the bind: `HOST=0.0.0.0` opens all interfaces just like the flag. A *blank*
|
|
215
|
+
`HOST` (`HOST=`, or `HOST=$UNSET`) is ignored and falls back to the loopback
|
|
216
|
+
default, so an accidentally-empty env var can't silently expose the proxy. The
|
|
217
|
+
startup banner always reports the **real** bind address (`127.0.0.1` for the
|
|
218
|
+
loopback default, `0.0.0.0` for an all-interfaces bind, or the narrowed host you
|
|
219
|
+
passed), so a wide-open bind is never hidden.
|
|
214
220
|
|
|
215
221
|
### Choosing a key
|
|
216
222
|
|
|
@@ -277,8 +283,8 @@ a custom header, so when auth is on they stop working:
|
|
|
277
283
|
|
|
278
284
|
Workarounds:
|
|
279
285
|
|
|
280
|
-
- Run a **separate local, unauthenticated instance** (`--
|
|
281
|
-
|
|
286
|
+
- Run a **separate local, unauthenticated instance** (no `--api-key`; the
|
|
287
|
+
default `127.0.0.1` bind keeps it local) for the browser UI, or
|
|
282
288
|
- Hit the history JSON API directly with a key, e.g.
|
|
283
289
|
`curl -H "Authorization: Bearer $COPILOT_API_KEY" http://127.0.0.1:4141/history/api/entries`.
|
|
284
290
|
|
package/dist/main.mjs
CHANGED
|
@@ -1348,7 +1348,7 @@ const patchClaude = defineCommand({
|
|
|
1348
1348
|
|
|
1349
1349
|
//#endregion
|
|
1350
1350
|
//#region package.json
|
|
1351
|
-
var version = "0.
|
|
1351
|
+
var version = "0.11.0";
|
|
1352
1352
|
|
|
1353
1353
|
//#endregion
|
|
1354
1354
|
//#region src/lib/adaptive-rate-limiter.ts
|
|
@@ -1762,6 +1762,44 @@ function resolveProxyApiKey(sources) {
|
|
|
1762
1762
|
};
|
|
1763
1763
|
}
|
|
1764
1764
|
/**
|
|
1765
|
+
* Resolve the hostname the server will *actually* bind to, decided at the CLI
|
|
1766
|
+
* edge with flag-over-env precedence and a **safe loopback default** — the same
|
|
1767
|
+
* flag-over-env shape this codebase already uses to reconcile a CLI flag with
|
|
1768
|
+
* its env twin (`--api-key`/`COPILOT_API_KEY`, `--github-token`/`GH_TOKEN`).
|
|
1769
|
+
*
|
|
1770
|
+
* - `--host` flag wins when present; otherwise the `HOST` env; otherwise the
|
|
1771
|
+
* default `127.0.0.1`.
|
|
1772
|
+
* - The default is **loopback, not all-interfaces**: an unconfigured instance
|
|
1773
|
+
* must not expose `/token` (which echoes the plaintext Copilot token) and the
|
|
1774
|
+
* otherwise-unauthenticated API to the whole network. Binding every interface
|
|
1775
|
+
* is now an explicit opt-in — pass `--host 0.0.0.0` (or `HOST=0.0.0.0`).
|
|
1776
|
+
* - **flag vs env asymmetry on a blank value** (the security-critical part): an
|
|
1777
|
+
* explicit `--host` flag is the operator's deliberate choice, so a blank flag
|
|
1778
|
+
* (`--host ""` / whitespace) is taken as the wildcard-bind escape hatch and
|
|
1779
|
+
* canonicalized to an explicit `0.0.0.0` (rather than left as `""` to lean on
|
|
1780
|
+
* srvx's undocumented empty-string handling). But a *set-but-blank* `HOST` env
|
|
1781
|
+
* (`HOST=`, or `HOST=$UNSET` in a shell / compose where the var is unset →
|
|
1782
|
+
* empty — NOT a deliberate keystroke) is accidental plumbing, so it is treated
|
|
1783
|
+
* as **not provided** and falls through to the loopback default. This mirrors
|
|
1784
|
+
* `resolveProxyApiKey` trimming `""` to not-provided, so an empty `HOST` can't
|
|
1785
|
+
* silently reopen the all-interfaces-unauthenticated exposure the loopback
|
|
1786
|
+
* default exists to prevent.
|
|
1787
|
+
* - **Both sources are trimmed**: a padded `--host " 10.0.0.5 "` or
|
|
1788
|
+
* `HOST=" 10.0.0.5 "` would otherwise reach the socket bind verbatim and fail
|
|
1789
|
+
* with `ENOTFOUND`. Trimming also decides blank-ness for the rules above.
|
|
1790
|
+
*
|
|
1791
|
+
* `env` is passed in (not read here) to keep the function pure and unit-testable.
|
|
1792
|
+
*/
|
|
1793
|
+
function resolveBindHost(flag, env) {
|
|
1794
|
+
if (flag !== void 0) {
|
|
1795
|
+
const trimmed = flag.trim();
|
|
1796
|
+
return trimmed === "" ? "0.0.0.0" : trimmed;
|
|
1797
|
+
}
|
|
1798
|
+
const envTrimmed = env?.trim() ?? "";
|
|
1799
|
+
if (envTrimmed !== "") return envTrimmed;
|
|
1800
|
+
return "127.0.0.1";
|
|
1801
|
+
}
|
|
1802
|
+
/**
|
|
1765
1803
|
* Resolve the address the server will *actually* bind to for the startup banner
|
|
1766
1804
|
* (Issue 04).
|
|
1767
1805
|
*
|
|
@@ -9756,7 +9794,7 @@ async function runServer(options) {
|
|
|
9756
9794
|
const visibleModels = allModels.filter((m) => !isHiddenModel(m.id, state.showAllModels));
|
|
9757
9795
|
if (visibleModels.length === 0) consola.warn("All upstream models are filtered by the hardcoded blacklist. /v1/models will return an empty list, but explicit POSTs with a hidden id still pass through to upstream. Restart with --show-all-models to see the full catalogue.");
|
|
9758
9796
|
else consola.info(`Available models:\n${visibleModels.map((m) => formatModelInfo(m)).join("\n")}`);
|
|
9759
|
-
const serverUrl = `http://${resolveClientHost(options.host,
|
|
9797
|
+
const serverUrl = `http://${resolveClientHost(options.host, void 0)}:${options.port}`;
|
|
9760
9798
|
if (options.claudeCode) {
|
|
9761
9799
|
if (visibleModels.length === 0) {
|
|
9762
9800
|
consola.error("--claude-code interactive setup needs at least one visible model. Restart with --show-all-models or update src/lib/hidden-models.ts.");
|
|
@@ -9792,7 +9830,7 @@ async function runServer(options) {
|
|
|
9792
9830
|
consola.box(`🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage${options.history ? `\n📜 History UI: ${serverUrl}/history` : ""}`);
|
|
9793
9831
|
for (const line of buildStartupAuthLines({
|
|
9794
9832
|
source: options.apiKeySource,
|
|
9795
|
-
bindAddress: resolveBindAddress(options.host,
|
|
9833
|
+
bindAddress: resolveBindAddress(options.host, void 0)
|
|
9796
9834
|
})) process.stdout.write(`${line}\n`);
|
|
9797
9835
|
setupShutdownHandlers();
|
|
9798
9836
|
setServerInstance(serve({
|
|
@@ -9842,7 +9880,7 @@ const start = defineCommand({
|
|
|
9842
9880
|
host: {
|
|
9843
9881
|
alias: "H",
|
|
9844
9882
|
type: "string",
|
|
9845
|
-
description: "Host/interface to bind to
|
|
9883
|
+
description: "Host/interface to bind to. Default: 127.0.0.1 (loopback only); pass 0.0.0.0 to bind all interfaces. Falls back to the HOST env var when the flag is omitted."
|
|
9846
9884
|
},
|
|
9847
9885
|
verbose: {
|
|
9848
9886
|
alias: "v",
|
|
@@ -9968,7 +10006,7 @@ const start = defineCommand({
|
|
|
9968
10006
|
});
|
|
9969
10007
|
return runServer({
|
|
9970
10008
|
port: Number.parseInt(args.port, 10),
|
|
9971
|
-
host: args.host,
|
|
10009
|
+
host: resolveBindHost(args.host, process.env.HOST),
|
|
9972
10010
|
verbose: args.verbose,
|
|
9973
10011
|
accountType: args["account-type"],
|
|
9974
10012
|
manual: args.manual,
|