glmproxy 2.5.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 eequaled
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,434 @@
1
+ # GLM Proxy
2
+
3
+ <p align="center">
4
+ <b>A lightweight local proxy that exposes AutoClaw's AI models through<br>OpenAI-compatible and Anthropic-compatible APIs.</b>
5
+ </p>
6
+
7
+ <p align="center">
8
+ <img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="Node.js >=18">
9
+ <img src="https://img.shields.io/badge/license-MIT-blue" alt="License MIT">
10
+ <img src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS%20%7C%20Linux-lightgrey" alt="Platform">
11
+ <img src="https://img.shields.io/npm/v/glmproxy" alt="npm version">
12
+ <img src="https://img.shields.io/npm/dm/glmproxy" alt="npm downloads">
13
+ <img src="https://github.com/eequaled/GLM_proxy/actions/workflows/ci.yml/badge.svg" alt="CI">
14
+ </p>
15
+
16
+ > **v2.5.1** — one interactive CLI, shared core in `lib/`, zero dependencies. Install with `npm i -g glmproxy` or run with `npx glmproxy`.
17
+
18
+ ---
19
+
20
+ ## Models are fetched automatically
21
+
22
+ Point any OpenAI-compatible harness at `http://127.0.0.1:18791/v1` and it can pull the **live model catalog** through `GET /v1/models` — no config to maintain. The list is re-read from AutoClaw's runtime catalog on every request, so models AutoClaw adds or removes show up without a proxy restart. The Anthropic entrypoint (`/v1/models` on port `18792`) serves the same catalog in Anthropic's list shape.
23
+
24
+ **GLM-5.3-Flash (known as "OX-alpha")** is in the proxy too — served through the local-agent route (see [Models](#models) for the exact caveats).
25
+
26
+ ---
27
+
28
+ Two API formats, one launcher. `node bin/cli.js` asks which one you want with an arrow-key menu and starts the right proxy. Flags skip the menu.
29
+
30
+ | Format | Flag | Default port | Use with |
31
+ |--------|------|--------------|----------|
32
+ | OpenAI (`/v1/chat/completions`) | `--openai` (default) | `18791` | OpenCode, Cursor, Continue, LiteLLM, Python/JS SDKs |
33
+ | Anthropic (`/v1/messages`) | `--anthropic` | `18792` | Claude Code CLI, Anthropic SDK |
34
+
35
+ ## Screenshots
36
+
37
+ ### 1. Prerequisites — AutoClaw running as your background service
38
+
39
+ Make sure **AutoClaw is running and you're logged in**. The proxy reads auth from AutoClaw's local token file — as long as the AutoClaw desktop app is open, the proxy works.
40
+
41
+ <p align="center">
42
+ <i>Screenshot: AutoClaw desktop app running and logged in (background service)</i>
43
+ <br>
44
+ <img src="./screenshots/autoclaw-background.png" alt="AutoClaw running as background service" width="700">
45
+ </p>
46
+
47
+ ### 2. Proxy in action — it works
48
+
49
+ Start the proxy and watch it handle requests from your tool of choice.
50
+
51
+ <p align="center">
52
+ <i>ignore claude code here</i>
53
+ <br>
54
+ <img src="./screenshots/image.png" alt="Proxy terminal showing successful operation" width="700">
55
+ </p>
56
+
57
+ ## How it works
58
+
59
+ ```
60
+ Your App AutoClaw Proxy CLI AutoClaw Backend
61
+ (OpenAI SDK) ───▶ localhost:18791 (menu) ───▶ autoglm-api.autoglm.ai
62
+ localhost:18792 (menu)
63
+ ```
64
+
65
+ AutoClaw handles authentication automatically. As long as AutoClaw is running and you're logged in, the proxy will work — no manual token setup needed.
66
+
67
+ The proxy speaks AutoClaw's native upstream dialect (client headers, bare model ids, and the app's system-prompt banner injected into every request — without that banner the cloud returns 400 `"invalid request"`). When the cloud fails, requests fall back to AutoClaw's own desktop agent over a local WebSocket (`127.0.0.1:18789`).
68
+
69
+ ## Prerequisites
70
+
71
+ - [AutoClaw](https://autoclaw.com) installed, running, and logged in (Windows / macOS only)
72
+ - Node.js 18+
73
+
74
+ ## Quick Start
75
+
76
+ ```bash
77
+ npm start
78
+ # or: node bin/cli.js
79
+ ```
80
+
81
+ You get an interactive menu: arrow keys to move, Enter to pick. Choose the format, port, host, and auth key, and the proxy starts. Ctrl+C quits cleanly and restores your terminal. The menu also offers **Model Doctor** (catalog + credit-tier routing) and **Test Models** (live health check) without starting a proxy.
82
+
83
+ Skip the menu with flags:
84
+
85
+ ```bash
86
+ node bin/cli.js --anthropic --port 3001 --key mykey
87
+ ```
88
+
89
+ Or feed config through env vars — the CLI leaves existing env vars alone:
90
+
91
+ ```bash
92
+ PORT=3001 PROXY_KEY=mykey RATE_LIMIT=50 node bin/cli.js
93
+ ```
94
+
95
+ Without a TTY (piped stdin, CI), the CLI skips the menu and starts the OpenAI format on port 18791 using your env vars or the defaults.
96
+
97
+ ### npm commands
98
+
99
+ | Command | What it does |
100
+ |---------|--------------|
101
+ | `npm start` | Launch the interactive CLI (`node bin/cli.js`) |
102
+ | `npm run anthropic` | Start the Anthropic proxy directly (`node anthropic.js`), bypassing the menu |
103
+ | `npm test` | Run the pen-test suite (`tests/pen-test-p1` through `p5`), plus the error-taxonomy tests (`tests/taxonomy.mjs`) and runtime-catalog refresh test (`tests/catalog-refresh.mjs`) |
104
+
105
+ ### Direct entry points (optional)
106
+
107
+ You can still run either proxy directly without the CLI:
108
+
109
+ ```bash
110
+ node openai.js # OpenAI format, port 18791
111
+ node anthropic.js # Anthropic format, port 18792
112
+ ```
113
+
114
+ They read the same env vars and respect `HOST`, `PORT`, `PROXY_KEY`, `RATE_LIMIT`, etc.
115
+
116
+ ### Options
117
+
118
+ | Variable / Flag | Default | Description |
119
+ |-----------------|---------|-------------|
120
+ | `PORT` / `--port` | `18791` (OpenAI), `18792` (Anthropic) | Port this proxy listens on |
121
+ | `HOST` / `--host` | `127.0.0.1` | Bind address |
122
+ | `PROXY_KEY` / `--key` | `mewmew` | API key clients must send |
123
+ | `RATE_LIMIT` / `--rate-limit` | `30` | Max requests per second per client IP |
124
+ | `MAX_MESSAGES` / `--max-messages` | unlimited (`0`/unset) | Max message / entity limit in request payload (explicit values: 128, 256, 512, 1024). Leave unlimited if your harness compresses or batches history — raise it if you hit `413 / payload too large` |
125
+ | `LOG_LEVEL` | `info` | `debug` / `info` / `silent` |
126
+ | `PREFER_LOCAL` | off | Set to `1` to use the local AutoClaw gateway first, skipping cloud attempts |
127
+ | `TRUSTED_PROXIES` | empty | Comma-separated IPs whose `X-Forwarded-For` header is trusted for rate limiting |
128
+ | `MAX_BODY_BYTES` | `52428800` | Max request body (50 MB) |
129
+ | `JSONL_LOG` | off | Write structured JSONL request log when `true` |
130
+ | `JSONL_FILE` | `proxy_requests.jsonl` (Anthropic: `proxy_requests_anthropic.jsonl`) | JSONL output path |
131
+ | `JSONL_MAX_BYTES` | `10485760` | Rotate JSONL log when it exceeds this (10 MB) |
132
+ | `UPSTREAM_TIMEOUT_MS` | `120000` | Per-attempt upstream budget (idle-based; the vendor allows up to 20 min — raise this for slow thinking models) |
133
+ | `GATEWAY_MIN_PROTOCOL` / `GATEWAY_MAX_PROTOCOL` | `3` / `4` | Local-gateway WS protocol range offered on connect (the proxy self-heals to the gateway's expected protocol on mismatch) |
134
+ | `LOCAL_GATEWAY_HOST` / `LOCAL_GATEWAY_PORT` | `127.0.0.1` / `18789` | Where the AutoClaw desktop gateway is expected |
135
+ | `FALLBACK_MODELS_PATH` | empty | Path to an external fallback model catalog JSON (`{"models":[...]}`) — defaults to the shipped `lib/fallback-models.json` |
136
+ | `AUTOCLAW_SYSTEM_BANNER` | built-in | Override the system-prompt banner injected into cloud requests (keep the `## Tooling` line intact) |
137
+ | `--anthropic` | — | Run in Anthropic API format |
138
+ | `--openai` | — | Run in OpenAI API format (default) |
139
+ | `--doctor` | — | Scan AutoClaw's current runtime model catalog and show Anthropic routing |
140
+ | `--test-models` / `--test` | — | Live health check: test every catalog model through the full pipeline |
141
+ | `--help`, `-h` | — | Show CLI help |
142
+
143
+ ### JSONL Request Logging
144
+
145
+ Set `JSONL_LOG=true` (or `LOG_LEVEL=debug`) to write one JSON line per request:
146
+
147
+ ```json
148
+ {"ts":"2026-07-29T03:41:00.000Z","model":"zai_auto","status":200,"ip":"127.0.0.1","latencyMs":423}
149
+ ```
150
+
151
+ The Anthropic variant writes to `proxy_requests_anthropic.jsonl`.
152
+
153
+ Alongside the JSONL stream, a compact ring log (`proxy_requests.json`, last 50 entries) records every terminal outcome — including `via: "local"` and the cloud verdict (`cloud_status` / `cloud_error`) when the cloud rejected a request that the local agent ended up serving.
154
+
155
+ ### Model doctor
156
+
157
+ Run the doctor to scan AutoClaw's live model catalog with **credit tiers** fetched from its remote model-config (falling back to the runtime file, then built-ins), and print the Claude alias routing map computed by the same resolver the Anthropic proxy uses:
158
+
159
+ ```bash
160
+ node bin/cli.js --doctor
161
+ ```
162
+
163
+ Anthropic routing follows credit tiers: opus → High, sonnet → Medium, haiku → Low. UI display names can differ from API ids (the API's `zaicoding_glm-5.3` shows as "GLM-5.2" in AutoClaw's UI).
164
+
165
+ ### Model health test
166
+
167
+ ```bash
168
+ node bin/cli.js --test-models
169
+ ```
170
+
171
+ Spawns a throwaway proxy on a test port and fires a minimal prompt at **every model in the catalog**, reporting live status per model:
172
+
173
+ ```
174
+ ✔ working [cloud ok] (1.2s) → PONG
175
+ ✔ working [cloud 402 → local agent] (38.4s) → PONG 🦞
176
+ ✗ failed (404) (0.9s) → Model ... is not recognized by AutoClaw upstream
177
+ ```
178
+
179
+ The cloud verdict comes from the isolated test ring log: a `[cloud ok]` tag means the cloud served it; `[cloud NNN → local agent]` means the cloud rejected it (HTTP NNN) and AutoClaw's desktop-agent fallback answered. Zero-usage responses are the local-agent signature. The test uses isolated log files so it never clobbers your running proxy's records.
180
+
181
+ ## Self-hosting behind a reverse proxy
182
+
183
+ The proxy binds to `127.0.0.1` by default. To run it on a server (e.g. a VPS) and expose it with TLS, bind to all interfaces and put a reverse proxy in front:
184
+
185
+ ```bash
186
+ node bin/cli.js --host 0.0.0.0 --port 18791 --key change-me
187
+ ```
188
+
189
+ **Caddy** (automatic HTTPS):
190
+
191
+ ```caddy
192
+ glm.example.com {
193
+ reverse_proxy 127.0.0.1:18791
194
+ }
195
+ ```
196
+
197
+ **nginx**:
198
+
199
+ ```nginx
200
+ server {
201
+ listen 443 ssl;
202
+ server_name glm.example.com;
203
+ # ssl_certificate / ssl_certificate_key ...
204
+
205
+ location / {
206
+ proxy_pass http://127.0.0.1:18791;
207
+ proxy_set_header X-Forwarded-For $remote_addr;
208
+ proxy_set_header Host $host;
209
+ proxy_http_version 1.1;
210
+ proxy_set_header Connection ""; # keep streaming (SSE) working
211
+ }
212
+ }
213
+ ```
214
+
215
+ Rate limiting keys off the client IP. When proxied, pass `X-Forwarded-For` and list the proxy's address in `TRUSTED_PROXIES` (comma-separated) so the real client IP is used — otherwise every client shares one bucket:
216
+
217
+ ```bash
218
+ TRUSTED_PROXIES=127.0.0.1 node bin/cli.js --host 0.0.0.0
219
+ ```
220
+
221
+ > The proxy needs a logged-in AutoClaw account running on the same machine (it reads the local token file), so a public endpoint is effectively a shared account — only expose it to people you trust.
222
+
223
+ ## API
224
+
225
+ ### `GET /healthz`
226
+
227
+ Returns token status and upstream info.
228
+
229
+ ```json
230
+ {
231
+ "ok": true,
232
+ "status": "live",
233
+ "upstream": "https://autoglm-api.autoglm.ai/autoclaw-proxy/proxy/autoclaw",
234
+ "port": 18791
235
+ }
236
+ ```
237
+
238
+ ### `GET /v1/models`
239
+
240
+ Lists available models in OpenAI format (OpenAI proxy) or Anthropic format (Anthropic proxy). The catalog is **re-read from AutoClaw's runtime file on every call** — no restart needed when AutoClaw's model list changes:
241
+
242
+ ```json
243
+ {
244
+ "object": "list",
245
+ "data": [
246
+ {
247
+ "id": "zai_auto",
248
+ "object": "model",
249
+ "owned_by": "autoclaw",
250
+ "name": "Auto",
251
+ "context_window": 1048576,
252
+ "max_tokens": 393216
253
+ }
254
+ ]
255
+ }
256
+ ```
257
+
258
+ Any OpenAI-compatible harness pointed at `http://127.0.0.1:18791/v1` will pick this list up automatically.
259
+
260
+ ### `POST /v1/chat/completions` — OpenAI proxy
261
+
262
+ OpenAI-compatible chat completions. Supports both streaming (`stream: true`) and non-streaming.
263
+
264
+ **Headers:**
265
+ ```
266
+ Authorization: Bearer mewmew
267
+ Content-Type: application/json
268
+ ```
269
+
270
+ ### `POST /v1/messages` — Anthropic proxy
271
+
272
+ Anthropic-compatible Messages API. Supports both streaming and non-streaming. Claude model names are automatically mapped to the best available AutoClaw model:
273
+
274
+ | Claude model | Routes to |
275
+ |---|---|
276
+ | `claude-opus-*` | First available GLM-5.3 / GLM-5 model |
277
+ | `claude-sonnet-*` | `zai_auto` (or next available GLM-5 model) |
278
+ | `claude-haiku-*` | `zai_glm-5-turbo` (or DeepSeek / Auto fallback) |
279
+
280
+ ## Error handling
281
+
282
+ Every failure maps to a semantically correct status with a machine-readable `code` — no more generic blobs:
283
+
284
+ | Situation | HTTP | `code` |
285
+ |-----------|------|--------|
286
+ | Bad client input (bad JSON / oversized / wrong Content-Type) | `400` / `413` / `415` | `invalid_request` |
287
+ | Model out of credits or free quota (upstream 402/403/810000) | `402` | `quota_exhausted` |
288
+ | AutoClaw token expired | `401` | `token_expired` |
289
+ | Model unknown upstream | `404` | `model_not_found` |
290
+ | Upstream rate limit | `429` | `rate_limited_by_upstream` |
291
+ | Upstream returned garbage or died | `502` | `upstream_failure` |
292
+ | AutoClaw not running (no token file) | `503` | `no_token` |
293
+ | Upstream timeout (2 min) | `504` | `upstream_timeout` |
294
+
295
+ Quota errors are remembered for 60s per model: repeat requests fail instantly instead of replaying doomed cloud + fallback attempts.
296
+
297
+ ## Local gateway fallback
298
+
299
+ When the cloud upstream fails (and it's not a plain 404/429), the proxy re-runs your prompt through **AutoClaw's own desktop agent** over a local WebSocket (`127.0.0.1:18789`). Responses served this way are logged with `via: "local"` in the JSONL log. Caveats: it's a full agentic run (slower, tools included), and it shares your account's credits — quota walls stop it too. Set `PREFER_LOCAL=1` to skip the cloud attempt entirely while credits are exhausted.
300
+
301
+ ## Models
302
+
303
+ | ID | Name | Context | Max Output | Notes |
304
+ |----|------|---------|------------|-------|
305
+ | `zai_auto` | Auto | 1M | 393K | Routes to AutoClaw's optimal model |
306
+ | `zaicoding_glm-5.3` | GLM-5.3 | 1M | 307K | Latest GLM coding model |
307
+ | `zai_glm-5-turbo` | GLM-5-Turbo | 200K | 131K | Zhipu AI GLM-5 Turbo |
308
+ | `zai_glm-5.3-flash` | GLM-5.3-Flash ("OX-alpha") | 1M | 131K | Newest GLM flash model. The cloud upstream 400s it, so it's served via the local-agent fallback; it now appears in `/v1/models` and in the built-in fallback catalog |
309
+ | `tdpsk_deepseek-v4-flash-202605` | Deepseek-V4-Flash | 1M | 393K | Fast DeepSeek model |
310
+ | `tdpsk_deepseek-v4-pro-202606` | DeepSeek-V4-Pro | 1M | 393K | Deep reasoning model |
311
+
312
+ > GLM 5.3 new in the proxy? Maybe. Supposedly in the UI it's 5.2 but in the API it's 5.3. We'll never know, but it's a win-win xd.
313
+
314
+ All models include `reasoning_content` in responses when the upstream model reasons. The model list is loaded dynamically from AutoClaw's `openclaw.runtime.json` (re-read on every `/v1/models` call, so the catalog stays live), with a built-in fallback if that file isn't readable. Run `node bin/cli.js --doctor` to inspect the current catalog after an AutoClaw update.
315
+
316
+ ## Integrations
317
+
318
+ ### OpenCode
319
+
320
+ ```json
321
+ {
322
+ "provider": {
323
+ "autoclaw": {
324
+ "npm": "@ai-sdk/openai-compatible",
325
+ "name": "AutoClaw",
326
+ "options": {
327
+ "baseURL": "http://localhost:18791/v1",
328
+ "apiKey": "mewmew"
329
+ },
330
+ "models": {
331
+ "zai_auto": { "name": "AutoClaw Auto" },
332
+ "zaicoding_glm-5.3": { "name": "AutoClaw GLM-5.3" },
333
+ "zai_glm-5-turbo": { "name": "AutoClaw GLM-5 Turbo" },
334
+ "tdpsk_deepseek-v4-flash-202605": { "name": "AutoClaw Deepseek-V4-Flash" },
335
+ "tdpsk_deepseek-v4-pro-202606": { "name": "AutoClaw DeepSeek-V4-Pro" }
336
+ }
337
+ }
338
+ }
339
+ }
340
+ ```
341
+
342
+ Or add it as a custom model directly in the UI:
343
+ - **API Format**: OpenAI Chat Completions
344
+ - **URL**: `http://localhost:18791/v1`
345
+ - **Model ID**: `zai_auto` (or any model from the table above)
346
+ - **API Key**: `mewmew`
347
+
348
+ ### Claude Code CLI
349
+
350
+ Add to `~/.claude/settings.json`:
351
+
352
+ ```json
353
+ {
354
+ "env": {
355
+ "ANTHROPIC_BASE_URL": "http://localhost:18792",
356
+ "ANTHROPIC_AUTH_TOKEN": "mewmew"
357
+ }
358
+ }
359
+ ```
360
+
361
+ ### Python
362
+
363
+ ```python
364
+ from openai import OpenAI
365
+
366
+ client = OpenAI(base_url="http://localhost:18791/v1", api_key="mewmew")
367
+
368
+ # Streaming
369
+ with client.chat.completions.stream(
370
+ model="zai_auto",
371
+ messages=[{"role": "user", "content": "Hello!"}],
372
+ ) as stream:
373
+ for text in stream.text_stream:
374
+ print(text, end="", flush=True)
375
+
376
+ # Non-streaming
377
+ response = client.chat.completions.create(
378
+ model="zai_auto",
379
+ messages=[{"role": "user", "content": "What is 2+2?"}],
380
+ stream=False,
381
+ )
382
+ print(response.choices[0].message.content)
383
+ ```
384
+
385
+ ### JavaScript
386
+
387
+ ```javascript
388
+ import OpenAI from "openai";
389
+
390
+ const client = new OpenAI({
391
+ baseURL: "http://localhost:18791/v1",
392
+ apiKey: "mewmew",
393
+ });
394
+
395
+ const stream = await client.chat.completions.create({
396
+ model: "zai_auto",
397
+ messages: [{ role: "user", content: "Hello!" }],
398
+ stream: true,
399
+ });
400
+
401
+ for await (const chunk of stream) {
402
+ process.stdout.write(chunk.choices[0]?.delta?.content ?? "");
403
+ }
404
+ ```
405
+
406
+ ### Cursor / Continue / Other Tools
407
+
408
+ Any tool that supports OpenAI-compatible providers works. Point it at `http://localhost:18791/v1` with API key `mewmew` and you're set. Harnesses that probe for available models will get the live list from `/v1/models` automatically.
409
+
410
+ ## Notes
411
+
412
+ - Only one AutoClaw account can be active at a time — multi-account pooling isn't supported
413
+ - `PROXY_KEY` is just a local password for this proxy, not your AutoClaw credentials — set it to whatever you want. The default key `mewmew` is for **localhost-only use**: change it via `--key` / `PROXY_KEY` if you bind beyond `127.0.0.1` (see [Self-hosting](#self-hosting-behind-a-reverse-proxy))
414
+ - On a 401, the proxy invalidates its cached token and you can retry immediately
415
+ - Upstream 400 "invalid request" gets one retry after a 2s delay (a known upstream hiccup); quota/plan errors are never retried
416
+ - The cloud upstream requires AutoClaw's app system-prompt banner in every request. their new verification — the proxy injects it automatically (and never duplicates it). but it doesnt change much in practice/ my own testing and others testing. since it will be overridden by the harnesses own system prompt.
417
+ - When cloud fails, requests fall back to AutoClaw's local desktop agent (`via: "local"` in logs) unless the model just failed permanently there too
418
+ - The token file is watched for changes — AutoClaw can rotate auth mid-session without a restart
419
+ - AutoClaw's client identity (`X-Version` app version, platform, channel) is loaded dynamically from its runtime file — the same file that feeds the model catalog — so an AutoClaw app update is picked up without editing or restarting the proxy
420
+ - Rate limit is enforced per client IP (default 30 req/s); X-Forwarded-For is only honored from `TRUSTED_PROXIES`
421
+ - The fallback model catalog lives in `lib/fallback-models.json` (override with `FALLBACK_MODELS_PATH`) — the built-in list is only a last resort when AutoClaw's runtime file is unreadable
422
+ - The local-gateway connect self-heals: if the gateway bumps its WS protocol, the proxy reconnects with the expected version automatically
423
+ - The ring log records cloud verdicts alongside local fallbacks, so every response is attributable
424
+ - No dependencies at all: the interactive menu is hand-rolled on Node's built-in `readline`, so there's zero `node_modules` and zero install step
425
+
426
+ ## Special Thanks
427
+
428
+ <p align="center">
429
+ <img src="./screenshots/jarona.png" alt="Special thanks to Jarona" width="200">
430
+ </p>
431
+
432
+ ## License
433
+
434
+ MIT License + Jarona Rights™ (sorry to keep u waiting)