ohmyapi commited on
Commit
e45e71c
·
verified ·
1 Parent(s): a431c63

Improve HF wrapper landing and proxy docs

Browse files
Files changed (6) hide show
  1. .env.example +50 -0
  2. Dockerfile +9 -2
  3. README.md +20 -2
  4. entrypoint.sh +34 -0
  5. nginx.conf +55 -0
  6. static/index.html +410 -0
.env.example ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ── Server ───────────────────────────────────────────────────────────────────
2
+ # PORT=8080
3
+ # Hugging Face Spaces: the wrapper listens on :7860 and proxies to the upstream app.
4
+ # Minimum HF Secrets: CLAUDE_SESSION_KEYS, CLAUDE_API_KEY, ADMIN_PASS.
5
+ # DB/Redis are not required for HF Simple mode.
6
+
7
+ # ── Mode ─────────────────────────────────────────────────────────────────────
8
+ # Simple mode: set CLAUDE_SESSION_KEYS to skip PostgreSQL entirely.
9
+ # Separate multiple keys with commas.
10
+ # CLAUDE_SESSION_KEYS=sk-ant-sid01-xxx,sk-ant-sid01-yyy
11
+
12
+ # ── PostgreSQL ────────────────────────────────────────────────────────────────
13
+ # Used by both the claude2api service and the postgres container.
14
+ DB_HOST=postgres
15
+ DB_PORT=5432
16
+ DB_USER=claude2api
17
+ DB_PASS=change_me_please # ← REQUIRED: set a strong password
18
+ DB_NAME=claude2api
19
+
20
+ # ── Redis ─────────────────────────────────────────────────────────────────────
21
+ # REDIS_PASS must match the password set in docker-compose.yml (redis container).
22
+ REDIS_PASS=redis_secret # ← REQUIRED: set a strong password
23
+ REDIS_URL=redis://:redis_secret@redis:6379/0 # keep in sync with REDIS_PASS
24
+
25
+ # ── Residential Proxy (leave empty to connect directly) ───────────────────────
26
+ PROXY_HOST=
27
+ PROXY_PORT=3010
28
+ PROXY_USER=
29
+ PROXY_PASS=
30
+ PROXY_REGION=US
31
+
32
+ # ── Admin Dashboard ───────────────────────────────────────────────────────────
33
+ # ADMIN_PASS is a startup input. Even if upstream runtime lets you change some settings
34
+ # in the admin UI, that does not mean Hugging Face Secrets or this file were updated.
35
+ # Verified against the current upstream image in Simple mode: API key changes can take
36
+ # effect in-process, but restart falls back to env/Secrets, and admin password is not
37
+ # currently exposed here as a durable runtime setting.
38
+ ADMIN_USER=admin
39
+ ADMIN_PASS=change_me_please # ← REQUIRED: change before going live
40
+
41
+ # ── Rate Limiting ─────────────────────────────────────────────────────────────
42
+ CLAUDE_DAILY_LIMIT=0 # 0 = unlimited
43
+ MAX_RETRIES=3
44
+ COOLDOWN_MINUTES=5
45
+
46
+ # ── API Key Protection (optional) ─────────────────────────────────────────────
47
+ # Startup default for protected API routes. If changed from the upstream admin UI,
48
+ # the new value may apply only to the current running process unless upstream-backed
49
+ # persistence is configured and verified.
50
+ # CLAUDE_API_KEY=sk-your-api-key
Dockerfile CHANGED
@@ -1,5 +1,12 @@
1
- FROM pushzx/claude2api:latest
2
 
3
- ENV LISTEN_ADDR=:7860
 
 
 
 
 
4
 
5
  EXPOSE 7860
 
 
 
1
+ FROM pushzx/claude2api:latest AS upstream
2
 
3
+ FROM nginx:1.27-alpine
4
+
5
+ COPY --from=upstream /claude2api /usr/local/bin/claude2api
6
+ COPY nginx.conf /etc/nginx/conf.d/default.conf
7
+ COPY static/index.html /usr/share/nginx/html/index.html
8
+ COPY entrypoint.sh /entrypoint.sh
9
 
10
  EXPOSE 7860
11
+
12
+ ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]
README.md CHANGED
@@ -14,6 +14,16 @@ Claude.ai Web → Anthropic API 兼容代理。将 Claude.ai 账号会话转换
14
 
15
  ## Hugging Face Spaces
16
 
 
 
 
 
 
 
 
 
 
 
17
  ### 推荐模式
18
 
19
  Hugging Face Spaces 推荐先使用 **Simple 模式**。Spaces 只运行单容器,当前仓库中的 `postgres`、`redis`、`watchtower` 仅适用于 `docker-compose.yml` 的本地 / VPS 部署,不直接用于 HF。
@@ -26,9 +36,10 @@ Hugging Face Spaces 推荐先使用 **Simple 模式**。Spaces 只运行单容
26
 
27
  ### 推荐 Variables
28
 
29
- - `LISTEN_ADDR=:7860`
30
  - `ADMIN_USER=admin`
31
 
 
 
32
  ### 可选 Secrets
33
 
34
  - `PROXY_HOST`
@@ -47,12 +58,17 @@ Hugging Face Spaces 推荐先使用 **Simple 模式**。Spaces 只运行单容
47
 
48
  - `config.yaml` 不参与此服务配置,运行时参数全部来自环境变量
49
  - 若仅提供 `CLAUDE_SESSION_KEYS`,可跳过 PostgreSQL / Redis
50
- - 若需要管理面板持久化能力,应改用外部 PostgreSQL / Redis而不是复compose sidecar
 
 
 
 
51
  - 对外开放时,建议始终设置 `CLAUDE_API_KEY`
52
 
53
  ### 部署后验证
54
 
55
  ```bash
 
56
  curl https://<space>.hf.space/health
57
 
58
  curl https://<space>.hf.space/v1/messages \
@@ -65,6 +81,8 @@ curl https://<space>.hf.space/v1/messages \
65
  }'
66
  ```
67
 
 
 
68
  ---
69
 
70
  ## 快速部署
 
14
 
15
  ## Hugging Face Spaces
16
 
17
+ ### 当前包装层职责
18
+
19
+ 当前 `Dockerfile` 不再直接把 `pushzx/claude2api:latest` 暴露到 `:7860`,而是改成了:
20
+
21
+ - `/`:wrapper 提供的静态 landing page
22
+ - 除 `/`、`/index.html`、`/favicon.ico` 外的其他路径:反向代理到上游 `claude2api`
23
+ - 容器对外固定监听 `:7860`,上游进程在容器内监听 `127.0.0.1:8080`
24
+
25
+ 这也是旧版根路径看起来像 `404 page not found` 的原因:之前只是原样暴露上游镜像,而上游本身没有自定义 `/` 首页。
26
+
27
  ### 推荐模式
28
 
29
  Hugging Face Spaces 推荐先使用 **Simple 模式**。Spaces 只运行单容器,当前仓库中的 `postgres`、`redis`、`watchtower` 仅适用于 `docker-compose.yml` 的本地 / VPS 部署,不直接用于 HF。
 
36
 
37
  ### 推荐 Variables
38
 
 
39
  - `ADMIN_USER=admin`
40
 
41
+ 通常不需要为 HF Space 手动设置 `LISTEN_ADDR=:7860`;`7860` 现在由 wrapper 前置层负责,上游在容器内固定监听 `127.0.0.1:8080`。
42
+
43
  ### 可选 Secrets
44
 
45
  - `PROXY_HOST`
 
58
 
59
  - `config.yaml` 不参与此服务配置,运行时参数全部来自环境变量
60
  - 若仅提供 `CLAUDE_SESSION_KEYS`,可跳过 PostgreSQL / Redis
61
+ - Private Space Hugging Face 访问控制与应自身的 `CLAUDE_API_KEY` 是两层独立鉴权
62
+ - HF Secrets 是启动时输入,不会被管理页面反向写回
63
+ - 若需要管理面板持久化能力,应改用上游支持的外部 PostgreSQL / Redis,而不是复用 compose sidecar
64
+ - 本地针对 `pushzx/claude2api:latest` 的 Simple 模式验证结果:管理接口可在当前进程内更新 `api_key`,但当前 UI / API 没有生效地修改 `ADMIN_PASS`;容器重启后 `api_key` 会回退到环境变量值
65
+ - 因此“持久化配置优先于 HF Secrets / env”不能由 wrapper 保证,只有在上游运行时验证成立后才能当作正式能力描述
66
  - 对外开放时,建议始终设置 `CLAUDE_API_KEY`
67
 
68
  ### 部署后验证
69
 
70
  ```bash
71
+ curl https://<space>.hf.space/
72
  curl https://<space>.hf.space/health
73
 
74
  curl https://<space>.hf.space/v1/messages \
 
81
  }'
82
  ```
83
 
84
+ 如果 Space 设为 private,先确认请求方已经通过 Hugging Face 侧访问控制;随后再按上游应用的 `CLAUDE_API_KEY` 语义访问 API。
85
+
86
  ---
87
 
88
  ## 快速部署
entrypoint.sh ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/sh
2
+ set -eu
3
+
4
+ nginx -t
5
+
6
+ LISTEN_ADDR="127.0.0.1:8080" /usr/local/bin/claude2api &
7
+ upstream_pid=$!
8
+
9
+ nginx -g 'daemon off;' &
10
+ nginx_pid=$!
11
+
12
+ terminate() {
13
+ kill -TERM "$upstream_pid" "$nginx_pid" 2>/dev/null || true
14
+ }
15
+
16
+ trap terminate INT TERM
17
+
18
+ while :; do
19
+ if ! kill -0 "$upstream_pid" 2>/dev/null; then
20
+ wait "$upstream_pid" || true
21
+ kill -TERM "$nginx_pid" 2>/dev/null || true
22
+ wait "$nginx_pid" || true
23
+ exit 1
24
+ fi
25
+
26
+ if ! kill -0 "$nginx_pid" 2>/dev/null; then
27
+ wait "$nginx_pid" || true
28
+ kill -TERM "$upstream_pid" 2>/dev/null || true
29
+ wait "$upstream_pid" || true
30
+ exit 1
31
+ fi
32
+
33
+ sleep 1
34
+ done
nginx.conf ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ upstream claude2api_upstream {
2
+ server 127.0.0.1:8080;
3
+ keepalive 32;
4
+ }
5
+
6
+ server {
7
+ listen 7860;
8
+ listen [::]:7860;
9
+ server_name _;
10
+
11
+ charset utf-8;
12
+ client_max_body_size 50m;
13
+
14
+ location = / {
15
+ root /usr/share/nginx/html;
16
+ try_files /index.html =404;
17
+ add_header Cache-Control "no-store";
18
+ }
19
+
20
+ location = /index.html {
21
+ root /usr/share/nginx/html;
22
+ add_header Cache-Control "no-store";
23
+ }
24
+
25
+ location = /favicon.ico {
26
+ return 204;
27
+ }
28
+
29
+ location /v1/messages {
30
+ proxy_pass http://claude2api_upstream;
31
+ proxy_http_version 1.1;
32
+ proxy_buffering off;
33
+ proxy_read_timeout 3600s;
34
+ proxy_send_timeout 3600s;
35
+ proxy_set_header Host $host;
36
+ proxy_set_header X-Real-IP $remote_addr;
37
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
38
+ proxy_set_header X-Forwarded-Host $host;
39
+ proxy_set_header X-Forwarded-Proto $scheme;
40
+ proxy_set_header Connection "";
41
+ }
42
+
43
+ location / {
44
+ proxy_pass http://claude2api_upstream;
45
+ proxy_http_version 1.1;
46
+ proxy_read_timeout 3600s;
47
+ proxy_send_timeout 3600s;
48
+ proxy_set_header Host $host;
49
+ proxy_set_header X-Real-IP $remote_addr;
50
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
51
+ proxy_set_header X-Forwarded-Host $host;
52
+ proxy_set_header X-Forwarded-Proto $scheme;
53
+ proxy_set_header Connection "";
54
+ }
55
+ }
static/index.html ADDED
@@ -0,0 +1,410 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Claude2API</title>
7
+ <style>
8
+ :root {
9
+ --bg: #0b0908;
10
+ --bg-soft: #151210;
11
+ --panel: rgba(24, 20, 18, 0.82);
12
+ --panel-strong: rgba(34, 28, 25, 0.92);
13
+ --line: rgba(241, 231, 220, 0.11);
14
+ --line-strong: rgba(241, 231, 220, 0.2);
15
+ --text: #f1e7dc;
16
+ --muted: #b7a898;
17
+ --accent: #e3c9a5;
18
+ --accent-strong: #f3e0c4;
19
+ --shadow: 0 28px 84px rgba(0, 0, 0, 0.42);
20
+ --radius: 30px;
21
+ }
22
+
23
+ * {
24
+ box-sizing: border-box;
25
+ }
26
+
27
+ html {
28
+ color-scheme: dark;
29
+ }
30
+
31
+ body {
32
+ margin: 0;
33
+ min-height: 100vh;
34
+ color: var(--text);
35
+ font-family: "Helvetica Neue", "Segoe UI", ui-sans-serif, sans-serif;
36
+ background:
37
+ radial-gradient(circle at 14% 12%, rgba(227, 201, 165, 0.14), transparent 24%),
38
+ radial-gradient(circle at 88% 10%, rgba(140, 98, 59, 0.18), transparent 18%),
39
+ linear-gradient(180deg, #15110f 0%, var(--bg-soft) 38%, var(--bg) 100%);
40
+ }
41
+
42
+ body::before {
43
+ content: "";
44
+ position: fixed;
45
+ inset: 0;
46
+ pointer-events: none;
47
+ opacity: 0.08;
48
+ background-image: linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px);
49
+ background-size: 100% 3px;
50
+ mix-blend-mode: soft-light;
51
+ }
52
+
53
+ a {
54
+ color: inherit;
55
+ text-decoration: none;
56
+ }
57
+
58
+ code,
59
+ pre {
60
+ font-family: "JetBrains Mono", "SFMono-Regular", ui-monospace, monospace;
61
+ }
62
+
63
+ h1,
64
+ h2 {
65
+ font-family: "Baskerville", "Times New Roman", Georgia, serif;
66
+ letter-spacing: -0.04em;
67
+ }
68
+
69
+ .page {
70
+ width: min(1080px, calc(100vw - 28px));
71
+ margin: 0 auto;
72
+ padding: 22px 0 34px;
73
+ }
74
+
75
+ .frame {
76
+ position: relative;
77
+ overflow: hidden;
78
+ border: 1px solid var(--line);
79
+ border-radius: var(--radius);
80
+ background: linear-gradient(180deg, rgba(35, 29, 25, 0.94), rgba(19, 16, 14, 0.93));
81
+ box-shadow: var(--shadow);
82
+ backdrop-filter: blur(18px);
83
+ }
84
+
85
+ .frame::after {
86
+ content: "";
87
+ position: absolute;
88
+ inset: auto -10% 0;
89
+ height: 1px;
90
+ background: linear-gradient(90deg, transparent, rgba(243, 224, 196, 0.22), transparent);
91
+ }
92
+
93
+ .masthead {
94
+ display: grid;
95
+ grid-template-columns: minmax(0, 1.2fr) minmax(280px, 0.8fr);
96
+ gap: 22px;
97
+ padding: 32px;
98
+ }
99
+
100
+ .tag {
101
+ display: inline-flex;
102
+ align-items: center;
103
+ padding: 8px 14px;
104
+ border-radius: 999px;
105
+ border: 1px solid rgba(227, 201, 165, 0.16);
106
+ background: rgba(227, 201, 165, 0.08);
107
+ color: var(--accent);
108
+ font-size: 11px;
109
+ font-weight: 700;
110
+ letter-spacing: 0.18em;
111
+ text-transform: uppercase;
112
+ }
113
+
114
+ h1 {
115
+ margin: 18px 0 12px;
116
+ font-size: clamp(3rem, 8vw, 5.6rem);
117
+ line-height: 0.92;
118
+ }
119
+
120
+ .lede {
121
+ max-width: 680px;
122
+ margin: 0;
123
+ color: var(--muted);
124
+ font-size: 1.03rem;
125
+ line-height: 1.86;
126
+ }
127
+
128
+ .actions {
129
+ display: flex;
130
+ flex-wrap: wrap;
131
+ gap: 12px;
132
+ margin-top: 28px;
133
+ }
134
+
135
+ .button {
136
+ display: inline-flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ min-width: 150px;
140
+ min-height: 46px;
141
+ padding: 0 18px;
142
+ border-radius: 999px;
143
+ border: 1px solid var(--line);
144
+ background: rgba(255, 255, 255, 0.04);
145
+ color: var(--text);
146
+ font-size: 14px;
147
+ font-weight: 600;
148
+ transition:
149
+ transform 0.16s ease,
150
+ border-color 0.16s ease,
151
+ background 0.16s ease;
152
+ }
153
+
154
+ .button:hover {
155
+ transform: translateY(-1px);
156
+ border-color: var(--line-strong);
157
+ }
158
+
159
+ .button.primary {
160
+ border-color: transparent;
161
+ background: linear-gradient(135deg, var(--accent-strong), var(--accent));
162
+ color: #1a140f;
163
+ }
164
+
165
+ .rail {
166
+ display: grid;
167
+ gap: 12px;
168
+ align-content: start;
169
+ }
170
+
171
+ .glance {
172
+ padding: 18px;
173
+ border-radius: 22px;
174
+ border: 1px solid var(--line);
175
+ background: rgba(255, 255, 255, 0.03);
176
+ }
177
+
178
+ .glance span {
179
+ display: block;
180
+ margin-bottom: 8px;
181
+ color: var(--muted);
182
+ font-size: 11px;
183
+ font-weight: 700;
184
+ letter-spacing: 0.14em;
185
+ text-transform: uppercase;
186
+ }
187
+
188
+ .glance strong {
189
+ display: block;
190
+ font-size: 1rem;
191
+ line-height: 1.65;
192
+ }
193
+
194
+ .content {
195
+ display: grid;
196
+ gap: 18px;
197
+ margin-top: 18px;
198
+ }
199
+
200
+ .section {
201
+ padding: 26px 28px;
202
+ }
203
+
204
+ .section-head {
205
+ display: flex;
206
+ flex-wrap: wrap;
207
+ align-items: flex-end;
208
+ justify-content: space-between;
209
+ gap: 12px;
210
+ margin-bottom: 18px;
211
+ }
212
+
213
+ .section-head h2 {
214
+ margin: 0;
215
+ font-size: 2rem;
216
+ }
217
+
218
+ .section-head p,
219
+ .note,
220
+ .route p,
221
+ .aside p {
222
+ margin: 0;
223
+ color: var(--muted);
224
+ line-height: 1.78;
225
+ }
226
+
227
+ .routes {
228
+ display: grid;
229
+ grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
230
+ gap: 14px;
231
+ }
232
+
233
+ .route,
234
+ .aside {
235
+ padding: 18px;
236
+ border-radius: 22px;
237
+ border: 1px solid var(--line);
238
+ background: rgba(255, 255, 255, 0.03);
239
+ }
240
+
241
+ .route h3 {
242
+ margin: 0 0 10px;
243
+ font-size: 1.06rem;
244
+ }
245
+
246
+ .route code,
247
+ .aside code {
248
+ color: var(--accent-strong);
249
+ }
250
+
251
+ .split {
252
+ display: grid;
253
+ grid-template-columns: minmax(0, 1.08fr) minmax(0, 0.92fr);
254
+ gap: 18px;
255
+ }
256
+
257
+ .code {
258
+ margin: 0;
259
+ padding: 18px;
260
+ overflow: auto;
261
+ border-radius: 22px;
262
+ border: 1px solid var(--line);
263
+ background: #0e0a08;
264
+ color: #f6ecdf;
265
+ line-height: 1.72;
266
+ }
267
+
268
+ .aside-stack {
269
+ display: grid;
270
+ gap: 14px;
271
+ }
272
+
273
+ .note {
274
+ margin-top: 16px;
275
+ }
276
+
277
+ @media (max-width: 900px) {
278
+ .masthead,
279
+ .split {
280
+ grid-template-columns: 1fr;
281
+ }
282
+ }
283
+
284
+ @media (max-width: 720px) {
285
+ .page {
286
+ width: min(100vw - 18px, 1080px);
287
+ padding-top: 14px;
288
+ }
289
+
290
+ .masthead,
291
+ .section {
292
+ padding: 22px;
293
+ }
294
+
295
+ h1 {
296
+ font-size: clamp(2.6rem, 13vw, 4.2rem);
297
+ }
298
+ }
299
+ </style>
300
+ </head>
301
+ <body>
302
+ <main class="page">
303
+ <section class="frame masthead">
304
+ <div>
305
+ <div class="tag">Hugging Face wrapper</div>
306
+ <h1>Claude2API</h1>
307
+ <p class="lede">
308
+ Claude.ai sessions, surfaced as an Anthropic-compatible API. This root page exists to
309
+ make the Space legible at first glance; the actual admin dashboard, health probe, and
310
+ API behavior still come from the upstream <code>claude2api</code> runtime.
311
+ </p>
312
+ <div class="actions">
313
+ <a class="button primary" href="/admin">Open Admin</a>
314
+ <a class="button" href="/health">Health</a>
315
+ </div>
316
+ </div>
317
+
318
+ <aside class="rail">
319
+ <div class="glance">
320
+ <span>Root</span>
321
+ <strong>Static landing page served by the wrapper.</strong>
322
+ </div>
323
+ <div class="glance">
324
+ <span>Runtime</span>
325
+ <strong>All non-root routes pass through to the upstream service.</strong>
326
+ </div>
327
+ <div class="glance">
328
+ <span>Access</span>
329
+ <strong>Private Space auth and app-level API keys can both apply.</strong>
330
+ </div>
331
+ </aside>
332
+ </section>
333
+
334
+ <div class="content">
335
+ <section class="frame section">
336
+ <div class="section-head">
337
+ <div>
338
+ <h2>Where to go</h2>
339
+ </div>
340
+ <p>
341
+ The wrapper fixes the root experience only. It does not replace upstream routing,
342
+ persistence, or configuration semantics.
343
+ </p>
344
+ </div>
345
+
346
+ <div class="routes">
347
+ <article class="route">
348
+ <h3><code>/admin</code></h3>
349
+ <p>Upstream dashboard for accounts, model mapping, proxy settings, and API key changes.</p>
350
+ </article>
351
+ <article class="route">
352
+ <h3><code>/health</code></h3>
353
+ <p>Direct passthrough to the upstream health endpoint, kept simple for probes and checks.</p>
354
+ </article>
355
+ <article class="route">
356
+ <h3><code>/v1/messages</code></h3>
357
+ <p>Anthropic-compatible request path. Auth and streaming semantics remain upstream-defined.</p>
358
+ </article>
359
+ </div>
360
+ </section>
361
+
362
+ <section class="frame section">
363
+ <div class="section-head">
364
+ <div>
365
+ <h2>Quick request</h2>
366
+ </div>
367
+ <p>
368
+ Use the Space URL as the base URL. If the Space is private, Hugging Face must allow the
369
+ request before the app can evaluate its own token checks.
370
+ </p>
371
+ </div>
372
+
373
+ <div class="split">
374
+ <pre class="code"><code>curl https://YOUR-SPACE.hf.space/v1/messages \
375
+ -H "Content-Type: application/json" \
376
+ -H "Authorization: Bearer $CLAUDE_API_KEY" \
377
+ -d '{
378
+ "model": "claude-sonnet-4-6",
379
+ "max_tokens": 128,
380
+ "messages": [
381
+ {"role": "user", "content": "hello"}
382
+ ]
383
+ }'</code></pre>
384
+
385
+ <div class="aside-stack">
386
+ <div class="aside">
387
+ <p>
388
+ Runtime edits made in <code>/admin</code> do not mean Hugging Face Secrets were
389
+ updated. Treat wrapper config, Secrets, and any upstream persistence layer as
390
+ separate concerns.
391
+ </p>
392
+ </div>
393
+ <div class="aside">
394
+ <p>
395
+ This page intentionally avoids live status, mutable controls, and any secret-derived
396
+ values. It is an entrance, not a console.
397
+ </p>
398
+ </div>
399
+ </div>
400
+ </div>
401
+
402
+ <p class="note">
403
+ For stable persisted settings after restart, rely on upstream-supported storage and verify
404
+ the behavior explicitly instead of assuming the wrapper can override startup precedence.
405
+ </p>
406
+ </section>
407
+ </div>
408
+ </main>
409
+ </body>
410
+ </html>