@8-/gemini-web-api 1.0.1 → 1.0.2

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
@@ -84,7 +84,7 @@ The server will automatically:
84
84
  2. Read the encryption key from macOS Keychain.
85
85
  3. Decrypt your `__Secure-1PSID` and `__Secure-1PSIDTS` cookies.
86
86
  4. Discover available models from Gemini.
87
- 5. Start listening on `http://0.0.0.0:7860`.
87
+ 5. Start listening on `http://127.0.0.1:7860`.
88
88
 
89
89
  #### 2. Optional Environment Variables
90
90
 
@@ -98,7 +98,7 @@ PORT=8000 API_KEY=sk-mysecret ENABLE_THINKING=true gemini-web-api
98
98
  | Variable | Default | Description |
99
99
  | :--- | :--- | :--- |
100
100
  | `PORT` | `7860` | Server listening port |
101
- | `HOST` | `0.0.0.0` | Server listening host |
101
+ | `HOST` | `127.0.0.1` | Server listening host |
102
102
  | `API_KEY` | *(empty)* | Optional Bearer API key for authorization |
103
103
  | `ENABLE_THINKING` | `false` | Enable reasoning thoughts (`<think>` blocks & `reasoning_content`) |
104
104
  | `SECURE_1PSID` | *(auto-detected)* | Manual override for `__Secure-1PSID` cookie (only needed if no browser is logged in) |
@@ -254,7 +254,7 @@ gemini-web-api
254
254
  2. 自动从 macOS Keychain 安全读取加密密码并推导密钥;
255
255
  3. 解密 `__Secure-1PSID` 与 `__Secure-1PSIDTS` 凭据;
256
256
  4. 动态同步 Gemini 网页端模型配置;
257
- 5. 在 `http://0.0.0.0:7860` 启动 OpenAI 兼容接口。
257
+ 5. 在 `http://127.0.0.1:7860` 启动 OpenAI 兼容接口。
258
258
 
259
259
  #### 2. 可选环境变量配置
260
260
 
@@ -268,7 +268,7 @@ PORT=8000 API_KEY=sk-mysecret ENABLE_THINKING=true gemini-web-api
268
268
  | 环境变量 | 默认值 | 说明 |
269
269
  | :--- | :--- | :--- |
270
270
  | `PORT` | `7860` | 服务的监听端口 |
271
- | `HOST` | `0.0.0.0` | 监听的主机地址 |
271
+ | `HOST` | `127.0.0.1` | 监听的主机地址 |
272
272
  | `API_KEY` | *(留空)* | 可选的 Bearer 访问密码鉴权 |
273
273
  | `ENABLE_THINKING` | `false` | 是否开启思考模式(输出 `<think>` 与 `reasoning_content`) |
274
274
  | `SECURE_1PSID` | *(自动探测)* | 手动指定 `__Secure-1PSID`(仅在未登录浏览器时需要) |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@8-/gemini-web-api",
3
- "version": "1.0.1",
4
- "description": "OpenAI-compatible Gemini Web API reverse proxy running seamlessly on both Bun and Node.js with zero external dependencies.",
3
+ "version": "1.0.2",
4
+ "description": "OpenAI-compatible Gemini Web API reverse proxy running on Bun & Node.js / 兼容 OpenAI 格式的 Gemini Web 反向代理服务",
5
5
  "type": "module",
6
6
  "main": "index.js",
7
7
  "bin": {
package/src/constant.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { homedir } from "node:os";
2
2
  import { join } from "node:path";
3
3
 
4
- export const HOST = process.env.HOST ?? "0.0.0.0",
4
+ export const HOST = process.env.HOST ?? "127.0.0.1",
5
5
  PORT = parseInt(process.env.PORT ?? "7860", 10),
6
6
  API_KEY = process.env.API_KEY ?? "",
7
7
  ENABLE_THINKING = process.env.ENABLE_THINKING === "true",
@@ -55,97 +55,27 @@ modelListFormat = (model_li = null, default_model = null) => {
55
55
  if (b.id === target_default?.id) return 1;
56
56
  return 0;
57
57
  }),
58
- is_tty = Boolean(process.stdout?.isTTY),
58
+ is_tty = Boolean(process.stdout?.isTTY || process.env.FORCE_COLOR),
59
59
  color = {
60
60
  reset: is_tty ? "\x1b[0m" : "",
61
61
  bold: is_tty ? "\x1b[1m" : "",
62
62
  dim: is_tty ? "\x1b[2m" : "",
63
63
  cyan: is_tty ? "\x1b[36m" : "",
64
64
  green: is_tty ? "\x1b[32m" : "",
65
- yellow: is_tty ? "\x1b[33m" : "",
66
- gray: is_tty ? "\x1b[90m" : "",
67
65
  },
68
- headers = ["模型 ID (Model ID)", "显示名称 (Name)", "别名 (Aliases)"],
69
- raw_rows = sorted_model_li.map((item) => {
70
- const is_def = item.id === target_default?.id,
71
- clean_aliases = (item.alias_li ?? [])
72
- .filter((a) => a && a !== item.id && !/^[0-9a-f]{16}$/i.test(a))
73
- .filter((a, idx, arr) => arr.indexOf(a) === idx)
74
- .slice(0, 5)
75
- .join(", ");
76
- return [
77
- (is_def ? "★ " : " ") + item.id + (is_def ? " (默认)" : ""),
78
- item.disp ?? item.id,
79
- clean_aliases || "-",
80
- ];
81
- }),
82
- col_widths = headers.map((h, i) =>
83
- Math.max(stringVisualWidth(h), ...raw_rows.map((r) => stringVisualWidth(r[i]))),
84
- ),
85
- border_color = color.gray,
86
- top_border =
87
- border_color +
88
- "┌─" +
89
- col_widths.map((w) => "─".repeat(w)).join("─┬─") +
90
- "─┐" +
91
- color.reset,
92
- mid_border =
93
- border_color +
94
- "├─" +
95
- col_widths.map((w) => "─".repeat(w)).join("─┼─") +
96
- "─┤" +
97
- color.reset,
98
- bot_border =
99
- border_color +
100
- "└─" +
101
- col_widths.map((w) => "─".repeat(w)).join("─┴─") +
102
- "─┘" +
103
- color.reset,
104
- header_line =
105
- border_color +
106
- "│ " +
107
- headers
108
- .map((h, i) => color.bold + color.cyan + stringPadEnd(h, col_widths[i]) + color.reset)
109
- .join(border_color + " │ ") +
110
- border_color +
111
- " │" +
112
- color.reset,
113
- row_lines = raw_rows.map((r, idx) => {
114
- const item = sorted_model_li[idx],
115
- is_def = item.id === target_default?.id,
116
- c0_pad = stringPadEnd(r[0], col_widths[0]),
117
- c1_pad = stringPadEnd(r[1], col_widths[1]),
118
- c2_pad = stringPadEnd(r[2], col_widths[2]),
119
- c0 = is_def
120
- ? color.green + color.bold + c0_pad + color.reset
121
- : color.yellow + c0_pad + color.reset,
122
- c1 = color.bold + c1_pad + color.reset,
123
- c2 = color.dim + c2_pad + color.reset;
124
- return (
125
- border_color +
126
- "│ " +
127
- c0 +
128
- border_color +
129
- " │ " +
130
- c1 +
131
- border_color +
132
- " │ " +
133
- c2 +
134
- border_color +
135
- " │" +
136
- color.reset
137
- );
138
- }),
139
- output_parts = [
140
- color.bold + color.cyan + "支持的模型列表 (Supported Models):" + color.reset,
141
- top_border,
142
- header_line,
143
- mid_border,
144
- ...row_lines,
145
- bot_border,
146
- color.dim + " ★ = 默认模型 | 支持在请求中传入模型 ID 或任意别名" + color.reset,
66
+ lines = [
67
+ color.bold + color.cyan + "模型列表" + color.reset,
68
+ ...sorted_model_li.map((item) => {
69
+ const is_def = item.id === target_default?.id;
70
+ return (
71
+ " " +
72
+ (is_def
73
+ ? color.green + color.bold + item.id + " 当前模型" + color.reset
74
+ : color.reset + item.id)
75
+ );
76
+ }),
147
77
  ];
148
- return output_parts.join("\n");
78
+ return lines.join("\n");
149
79
  },
150
80
 
151
81
  modelListPrint = (model_li = null, default_model = null) => {
@@ -6,7 +6,7 @@ import { reqHandle } from "./router.js";
6
6
 
7
7
  export const serverStart = (custom_handler = null) => {
8
8
  const handler = custom_handler ?? reqHandle,
9
- is_tty = Boolean(process.stdout?.isTTY),
9
+ is_tty = Boolean(process.stdout?.isTTY || process.env.FORCE_COLOR),
10
10
  color = {
11
11
  reset: is_tty ? "\x1b[0m" : "",
12
12
  bold: is_tty ? "\x1b[1m" : "",
@@ -14,56 +14,32 @@ export const serverStart = (custom_handler = null) => {
14
14
  cyan: is_tty ? "\x1b[36m" : "",
15
15
  green: is_tty ? "\x1b[32m" : "",
16
16
  },
17
- serverBannerPrint = (runtime_name) => {
18
- const url = "http://" + (HOST === "0.0.0.0" ? "localhost" : HOST) + ":" + PORT;
17
+ serverBannerPrint = () => {
18
+ const url = "http://" + (HOST === "0.0.0.0" ? "127.0.0.1" : HOST) + ":" + PORT;
19
19
  console.log(
20
- "\n " +
20
+ "\n" +
21
21
  color.bold +
22
22
  color.green +
23
- "Gemini Web API" +
23
+ "服务已启动" +
24
24
  color.reset +
25
- " " +
26
- color.dim +
27
- "服务已启动 (" +
28
- runtime_name +
29
- ")" +
30
- color.reset,
31
- );
32
- console.log(
33
- " " +
25
+ "\n" +
34
26
  color.cyan +
35
- "" +
36
- color.reset +
37
- " " +
38
- color.bold +
39
- "本地服务:" +
27
+ "本地服务" +
40
28
  color.reset +
41
- " " +
42
- url,
43
- );
44
- console.log(
45
- " " +
29
+ " " +
30
+ url +
31
+ "\n" +
46
32
  color.cyan +
47
- "" +
33
+ "对话接口" +
48
34
  color.reset +
49
- " " +
50
- color.bold +
51
- "对话接口:" +
52
- color.reset +
53
- " " +
35
+ " " +
54
36
  url +
55
- "/v1/chat/completions",
56
- );
57
- console.log(
58
- " " +
37
+ "/v1/chat/completions" +
38
+ "\n" +
59
39
  color.cyan +
60
- "" +
61
- color.reset +
62
- " " +
63
- color.bold +
64
- "模型列表:" +
40
+ "模型接口" +
65
41
  color.reset +
66
- " " +
42
+ " " +
67
43
  url +
68
44
  "/v1/models\n",
69
45
  );
@@ -33,7 +33,7 @@ test("versionCompare correctly orders semantic versions", () => {
33
33
  assert.ok(versionCompare("3.8 Flash", "3.1 Pro") > 0);
34
34
  });
35
35
 
36
- test("modelListFormat formats table with headers and models", () => {
36
+ test("modelListFormat formats models cleanly with colors and without tables or tag symbols", () => {
37
37
  const custom_model_li = [
38
38
  {
39
39
  id: "gemini-3.8-flash",
@@ -55,16 +55,16 @@ test("modelListFormat formats table with headers and models", () => {
55
55
  default_model = custom_model_li[0],
56
56
  output = modelListFormat(custom_model_li, default_model);
57
57
 
58
- assert.ok(output.includes("支持的模型列表 (Supported Models):"));
59
- assert.ok(output.includes("模型 ID (Model ID)"));
60
- assert.ok(output.includes("显示名称 (Name)"));
61
- assert.ok(output.includes("别名 (Aliases)"));
62
- assert.ok(output.includes("gemini-3.8-flash"));
63
- assert.ok(output.includes("★"));
64
- assert.ok(output.includes("(默认)"));
58
+ assert.ok(output.includes("模型列表"));
59
+ assert.ok(output.includes("gemini-3.8-flash 当前模型"));
65
60
  assert.ok(output.includes("gemini-3.1-pro"));
66
- assert.ok(output.includes("gemini-flash, flash, 3.8-flash"));
61
+ assert.ok(!output.includes("gemini-flash"));
67
62
  assert.ok(!output.includes("56fdd199312815e2"));
63
+ assert.ok(!output.includes("┌"));
64
+ assert.ok(!output.includes("│"));
65
+ assert.ok(!output.includes("★"));
66
+ assert.ok(!output.includes("(默认)"));
67
+ assert.ok(!output.includes("支持在请求中传"));
68
68
  });
69
69
 
70
70
  test("modelListFormat sorts default model first even when not first in array", () => {
@@ -86,7 +86,7 @@ test("modelListFormat sorts default model first even when not first in array", (
86
86
  lite_idx = output.indexOf("gemini-3.5-flash-lite");
87
87
 
88
88
  assert.ok(flash_idx < lite_idx);
89
- assert.ok(output.includes("gemini-3.8-flash (默认)"));
89
+ assert.ok(output.includes("gemini-3.8-flash 当前模型"));
90
90
  });
91
91
 
92
92
  test("modelListFormat falls back to DEFAULT_MODEL_LI when empty", () => {