flexgate 0.5.1__tar.gz → 0.7.0__tar.gz

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.
Files changed (41) hide show
  1. {flexgate-0.5.1 → flexgate-0.7.0}/CLAUDE.md +24 -13
  2. {flexgate-0.5.1 → flexgate-0.7.0}/PKG-INFO +20 -11
  3. {flexgate-0.5.1 → flexgate-0.7.0}/README.md +19 -10
  4. {flexgate-0.5.1 → flexgate-0.7.0}/docs/cli.md +45 -16
  5. {flexgate-0.5.1 → flexgate-0.7.0}/docs/configuration.md +1 -1
  6. {flexgate-0.5.1 → flexgate-0.7.0}/docs/development.md +9 -7
  7. {flexgate-0.5.1 → flexgate-0.7.0}/docs/quickstart.md +1 -1
  8. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/__init__.py +1 -1
  9. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/cli.py +179 -115
  10. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/config.py +12 -0
  11. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/doctor.py +40 -12
  12. flexgate-0.7.0/flexgate/fallback.py +99 -0
  13. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/healthcheck.py +2 -23
  14. flexgate-0.7.0/flexgate/images.py +192 -0
  15. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/main.py +0 -1
  16. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/proxy.py +5 -0
  17. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/router.py +28 -2
  18. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/server.py +13 -1
  19. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/service.py +180 -140
  20. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/settings.py +114 -67
  21. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/sync.py +14 -17
  22. flexgate-0.7.0/flexgate/ui.py +99 -0
  23. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/update.py +19 -18
  24. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/usage.py +2 -3
  25. flexgate-0.5.1/flexgate/guardian.py +0 -252
  26. {flexgate-0.5.1 → flexgate-0.7.0}/.github/workflows/docs.yml +0 -0
  27. {flexgate-0.5.1 → flexgate-0.7.0}/.github/workflows/release.yml +0 -0
  28. {flexgate-0.5.1 → flexgate-0.7.0}/.gitignore +0 -0
  29. {flexgate-0.5.1 → flexgate-0.7.0}/config.yaml.template +0 -0
  30. {flexgate-0.5.1 → flexgate-0.7.0}/docs/Makefile +0 -0
  31. {flexgate-0.5.1 → flexgate-0.7.0}/docs/_static/.gitkeep +0 -0
  32. {flexgate-0.5.1 → flexgate-0.7.0}/docs/conf.py +0 -0
  33. {flexgate-0.5.1 → flexgate-0.7.0}/docs/index.md +0 -0
  34. {flexgate-0.5.1 → flexgate-0.7.0}/docs/requirements.txt +0 -0
  35. {flexgate-0.5.1 → flexgate-0.7.0}/docs/usage.md +0 -0
  36. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/__main__.py +0 -0
  37. {flexgate-0.5.1 → flexgate-0.7.0}/flexgate/migrate.py +0 -0
  38. {flexgate-0.5.1 → flexgate-0.7.0}/pyproject.toml +0 -0
  39. {flexgate-0.5.1 → flexgate-0.7.0}/test.png +0 -0
  40. {flexgate-0.5.1 → flexgate-0.7.0}/test_multimodal.py +0 -0
  41. {flexgate-0.5.1 → flexgate-0.7.0}/test_multimodal_gateway.py +0 -0
@@ -21,8 +21,8 @@ flexgate service install # install + enable + start systemd u
21
21
  # Lifecycle
22
22
  flexgate service {install|start|stop|restart|reload|status|uninstall}
23
23
  flexgate run # foreground/debug only
24
- flexgate check # provider connectivity diagnostics
25
24
  flexgate status # providers, fallback chains, per-key usage, active routes
25
+ flexgate log # service journal (-f follow, -r route lines only)
26
26
 
27
27
  # Config
28
28
  flexgate config {init|show|set|path|edit}
@@ -31,8 +31,9 @@ flexgate config set sonnet minimax MiniMax-M3
31
31
  flexgate config edit # interactive curses TUI: pick provider/model per tier
32
32
 
33
33
  # Claude Code settings bridge
34
- flexgate settings import # read from ~/.claude/settings.json
35
- flexgate settings apply # write ANTHROPIC_BASE_URL → localhost
34
+ flexgate settings import # read from ~/.claude/settings.json* (merges keys into api_keys)
35
+ flexgate settings apply # rewrite the flexgate env keys → localhost (other fields preserved)
36
+ flexgate settings apply --dry-run # preview the env diff without writing
36
37
 
37
38
  # confsync config sync
38
39
  flexgate sync # pull: replace local config.yaml with the remote document (backup first)
@@ -41,7 +42,9 @@ flexgate sync --dry-run # preview changes without writing
41
42
 
42
43
  # Versioning / upgrades
43
44
  flexgate --version # print package version
44
- flexgate doctor # diagnose install/config problems (exit 1 on failure)
45
+ flexgate doctor # diagnose install/config + upstream connectivity (exit 1 on failure)
46
+ flexgate doctor --offline # skip all network checks (PyPI + upstream probing)
47
+ flexgate check # DEPRECATED alias: delegates to doctor
45
48
  flexgate update # upgrade package via pip/pipx/uv + migrate config schema
46
49
  flexgate update --check # report what would change, modify nothing
47
50
  flexgate update --config-only # only migrate the config schema
@@ -70,35 +73,41 @@ without a systemd user instance.
70
73
 
71
74
  ```
72
75
  Claude Code → POST /v1/messages (model="claude-sonnet-4-6")
73
- → server.py (Starlette app, single route)
76
+ → server.py (Starlette app, /v1/messages + /v1/images/* routes)
74
77
  → router.py (resolve(): regex match model, check schedule windows first, then default routes)
75
78
  → proxy.py (rewrite model field if override, swap x-api-key, SSE streaming pass-through)
76
79
  → Upstream provider (z.ai, minimax, xiaomi, etc.)
80
+
81
+ OpenAI-style client → POST /v1/images/generations
82
+ → images.py (translate to MiniMax /v1/image_generation; provider auto-detected by base_url)
77
83
  ```
78
84
 
79
85
  ### Source files (`flexgate/`)
80
86
 
81
87
  | File | Role |
82
88
  |------|------|
83
- | `cli.py` | argparse CLI; service lifecycle commands, foreground `run`/`check`, config/settings/sync commands |
84
- | `config.py` | Pydantic-like dataclasses (`GatewayConfig`, `ProviderConfig`, `RouteConfig`, `ScheduleEntry`), YAML load/save, `TIER_PATTERNS` regex map |
85
- | `router.py` | `resolve(config, model)` — schedule-first then default routes, first regex match wins |
89
+ | `cli.py` | argparse CLI; service lifecycle commands, foreground `run`, config/settings/sync commands, `flexgate log` journal viewer |
90
+ | `ui.py` | Shared terminal styling: ANSI helpers (`bold`/`dim`/`red`/...), `ok`/`warn`/`fail`/`err` output helpers, `FlexgateHelpFormatter` + `FlexgateParser` (colored help on every Python version; all gated by `NO_COLOR` + stdout isatty) |
91
+ | `config.py` | Pydantic-like dataclasses (`GatewayConfig`, `ProviderConfig`, `RouteConfig`, `ScheduleEntry`), YAML load/save, `TIER_PATTERNS` regex map, `is_placeholder_key` |
92
+ | `router.py` | `resolve(config, model)` — schedule-first then default routes, first regex match wins; model aliases normalized before matching |
86
93
  | `proxy.py` | `handle_request()` — httpx async proxy, per-key fallback retry loop, SSE streaming + JSON pass-through |
87
94
  | `usage.py` | `flexgate status` usage queries — per-platform adapters (MiniMax coding_plan API, Kimi Code usages API, z.ai quota API, LiteLLM `/key/info`) plus a minimal chat probe fallback |
88
- | `server.py` | Starlette app creation, `POST /v1/messages` endpoint, `SIGUSR1` lifespan reload |
95
+ | `server.py` | Starlette app creation, `POST /v1/messages` + `POST /v1/images/{generations,edits}` endpoints, `SIGUSR1` lifespan reload |
96
+ | `images.py` | OpenAI Images API → MiniMax `/v1/image_generation` translation (`image-01`, size→width/height clamped to 512–2048 mult of 8, `data.image_base64[]`→`data[].b64_json`); edits endpoint returns 501; unknown image model names get 501 `model_not_supported` |
97
+ | `fallback.py` | Catch-all route: merged Anthropic/OpenAI-shaped JSON errors for every unserved path — 404 `route_not_found` (lists valid routes), 405 `method_not_allowed`, 501 `endpoint_not_supported` per recognized API family with an actionable reason |
89
98
  | `main.py` | Thin bootstrap: load config → create app → run uvicorn |
90
99
  | `service.py` | Authoritative systemd user-service install/start/stop/restart/reload/status and legacy PID migration |
91
- | `guardian.py` | Legacy port/PID helper; no longer owns persistent process supervision |
92
- | `healthcheck.py` | Pre-flight `POST /v1/messages` (max_tokens=1) to each referenced (provider, model) pair |
93
- | `settings.py` | Bridges `config.yaml` ↔ `~/.claude/settings.json` (import credentials, apply config) |
100
+ | `healthcheck.py` | Upstream connectivity probing (`POST /v1/messages`, max_tokens=1) for each referenced (provider, model) pair; called by `doctor` (`flexgate check` is a deprecated alias of doctor) |
101
+ | `settings.py` | Bridges `config.yaml` ↔ `~/.claude/settings.json`: import merges credentials into `api_keys`; apply rewrites only the managed env keys, preserving every other settings.json field |
94
102
  | `sync.py` | `flexgate sync` — pushes/pulls the whole config.yaml as an encrypted document on a confsync server (lazily imports the `confsync` client package) |
95
103
  | `migrate.py` | Config schema versioning: `config_version` marker, per-step `MIGRATIONS` chain (N → N+1), backup + atomic rewrite |
96
- | `doctor.py` | `flexgate doctor` — read-only diagnostics (Python, PyPI update, config schema/semantics, port, systemd, Claude settings) |
104
+ | `doctor.py` | `flexgate doctor` — read-only diagnostics (Python, PyPI update, config schema/semantics, port, systemd, Claude settings) plus upstream connectivity via `healthcheck` (skipped with `--offline`) |
97
105
  | `update.py` | `flexgate update` — PyPI version check, package upgrade via detected installer (pipx/uv/pip), config migration, service reload; after a successful package upgrade the config migration is re-run in a fresh process so it uses the new code's schema version (`FLEXGATE_UPDATE_DELEGATED` guards against re-delegation); also the cached (24h) new-version notice shown by bare `flexgate` / `service status` |
98
106
 
99
107
  ### Key design points
100
108
 
101
109
  - **Regex-first routing**: Routes are regex patterns matched against the `model` field in the request body. First match wins. A catch-all `".*"` pattern at the end handles fallback.
110
+ - **Model alias normalization** (`router.py`): before matching, bare aliases (`sonnet`/`opus`/`haiku`/`default`, case-insensitive, optional `[plan]` suffix) map to `claude-<tier>` prefixes (`default` → sonnet, Claude Code's default tier), and legacy numbered names (`claude-3-7-sonnet-latest`) reduce to their tier prefix; everything else passes through unchanged, so custom patterns and the catch-all behave exactly as before.
102
111
  - **Model resolution & `available_models` fallback**: A route may omit `model`; `router.resolve()` then falls back to the provider's first `available_models` entry, so `model_override` handed to the proxy is always a concrete name. `config._parse_routes` rejects routes that omit `model` on a provider with no `available_models` — so adding a provider without models requires an explicit `model` on every route using it.
103
112
  - **Proxy rewrite contract** (`proxy.py`): the upstream request gets the provider's `x-api-key` plus a fixed header set, and the JSON `model` field is rewritten only when the route set an override. Streaming responses are forwarded as raw bytes (`aiter_bytes`), never parsed.
104
113
  - **Key fallback** (`proxy.py`): a provider's `api_keys` list holds one or more keys for the same upstream (entries are key strings or `{key, note}` mappings; `note` is a free-form label shown by `flexgate status` and in fallback logs). Multiple accounts on one upstream — e.g. several MiniMax subscriptions — belong in ONE provider, not separate ones. Each *route* has an active-key pointer (`active_key` in YAML, 1-based; `RouteConfig.key_index` 0-based, default first key): requests on that route start from the pointed key, and on a retryable failure (HTTP 401/402/403/429/500/502/503/529, connect error, or timeout) the next key is tried and the pointer advances with it (in memory only, wrapping around — it is not written back to the file); every key is tried at most once and a full circle of failures returns the last error. The pointer is set interactively via `flexgate config edit` → "api keys" (pick a route, with live per-key usage) or by editing the route's `active_key` directly. `resolve()` (`router.py`) returns the matched `RouteConfig` so the proxy can read/advance its pointer. Streaming requests can only fall back before the upstream returns 200. `_regular_proxy`/`_stream_proxy` return `(response, retryable)`; `handle_request` owns the loop and returns the last error when all keys fail. The legacy `api_key` + `fallback_keys` schema is still parsed (`config._parse_api_keys`); migration v3 → v4 rewrites it to `api_keys`. `ProviderConfig.api_key` remains as a first-key property (getter/setter) for the settings import code.
@@ -110,6 +119,8 @@ Claude Code → POST /v1/messages (model="claude-sonnet-4-6")
110
119
  - **Conflict prevention**: Service startup removes stale legacy PID files, stops verified legacy Flexgate daemons, validates the configured port, and rejects temporary config paths.
111
120
  - **Tier patterns** in `config.py`: `opus`, `sonnet`, `haiku` map to regex patterns for CLI shorthand (`config set sonnet ...`).
112
121
  - **Single-source versioning**: the package version lives only in `flexgate/__init__.py` (`__version__`); hatchling reads it via `[tool.hatch.version]`. `--version`, `service status` and the bare `flexgate` command all print it.
122
+ - **Settings apply is non-destructive** (`settings.py`): only the six managed `env` keys are rewritten (BASE_URL, AUTH_TOKEN, API_TIMEOUT_MS, three `ANTHROPIC_DEFAULT_*_MODEL`); every other settings.json field (hooks, permissions, unrelated env vars) is preserved. Token policy is one code path shared with `service install`: explicit arg → token already in the file → `"gateway"`. `settings import` merges into the provider's `api_keys` list (appends new keys, never overwrites key #1 only).
123
+ - **Terminal styling** (`ui.py`): every colored output goes through `ui.bold`/`dim`/`ok`/`err`/... — all gated on `NO_COLOR` + stdout isatty, so pipes never receive escape codes. `err()` writes to stderr. Help screens use `FlexgateParser` (subparsers inherit it automatically); on Python 3.14+ argparse's own help theming is disabled so the look matches older versions.
113
124
  - **Config schema versioning**: `config.yaml` carries `config_version` (current: `migrate.CURRENT_CONFIG_VERSION`). Each schema change adds one rule to `migrate.MIGRATIONS` upgrading N → N+1; upgrades walk the chain step by step. `save_config` always stamps the current version; `load_config` rejects configs written by a newer flexgate; `flexgate update` applies pending migrations with a timestamped backup.
114
125
 
115
126
  ### Config location
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: flexgate
3
- Version: 0.5.1
3
+ Version: 0.7.0
4
4
  Summary: Flexible local API gateway for Claude Code — route models to different providers
5
5
  Project-URL: Homepage, https://github.com/Agony5757/flexible-gateway
6
6
  Project-URL: Repository, https://github.com/Agony5757/flexible-gateway
@@ -106,13 +106,13 @@ flexgate service uninstall # 停止、禁用并删除 unit
106
106
  - 若升级时检测到旧版后台 gateway 仍在运行,会先准备好 systemd unit,但不会强杀正在服务的进程;按提示手动 `kill <PID>` 停掉旧进程,再执行 `flexgate service start` 完成切换。
107
107
  - unit 设置了启动速率限制,永久配置错误不会再无限快速重启。
108
108
  - `service reload` 和 `config set/edit` 会在仅路由变化时发送 SIGUSR1;如果 endpoint 变化,则先检查再 restart。若只改 host、仍复用当前 port,为避免误停服务会要求先执行 `service stop`,再执行 `service start`。
109
- - 查看日志:`journalctl --user -u flexgate -e`。
109
+ - 查看日志:`flexgate log`(`-f` 跟随、`--since`/`--grep` 过滤、`-r` 只看路由决策行)。
110
110
 
111
111
  ### 版本与升级
112
112
 
113
113
  ```bash
114
114
  flexgate --version # 打印版本号(service status / 裸 flexgate 也会显示)
115
- flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings
115
+ flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings、上游连通性
116
116
  flexgate doctor --offline # 跳过 PyPI 检查
117
117
  flexgate update # 一键升级:pip/pipx/uv 升级包 + 迁移配置 schema + 热重载服务
118
118
  flexgate update --check # 只报告将要做什么,不改动
@@ -149,7 +149,7 @@ Trusted Publisher(repo: `Agony5757/flexible-gateway`,workflow: `release.yml`
149
149
 
150
150
  ### 上游连通性预检
151
151
 
152
- 运行 `flexgate check` 会向每个 **被路由引用的 `(provider, model)` 组合**
152
+ 运行 `flexgate doctor` 会向每个 **被路由引用的 `(provider, model)` 组合**
153
153
  发送一次 `POST /v1/messages`(`max_tokens=1`,消耗约 1~2 token),用于主动检查:
154
154
 
155
155
  - DNS / TCP / TLS 不可达(`base_url` 写错、网络不通)
@@ -157,7 +157,10 @@ Trusted Publisher(repo: `Agony5757/flexible-gateway`,workflow: `release.yml`
157
157
  - 仍是默认占位符(如 `your-zai-api-key`)
158
158
  - Provider 侧 5xx 故障
159
159
 
160
- 可通过 `--verify-timeout N` 调整每个 provider 的超时时间(默认 15 秒)。
160
+ 可通过 `--verify-timeout N` 调整每个 provider 的超时时间(默认 15 秒),
161
+ `--offline` 跳过全部网络检查(PyPI 新版检查 + 上游探测)。
162
+ 旧的 `flexgate check` 命令仍可用,但已弃用:它会打印提示并委托给 `doctor`
163
+ (注意 doctor 还检查本地安装,退出码语义更宽)。
161
164
 
162
165
  ### 状态总览与用量查询(`flexgate status` / `flexgate usage`)
163
166
 
@@ -187,13 +190,12 @@ fallback 链、当前生效的路由,并逐一查询**每个 key** 的用量/
187
190
  如果某平台的专用接口调用失败,flexgate 会自动退化为 minimal probe 再试一次。
188
191
  用量查询接口多为平台内部接口,可能随时变动;查询结果仅供参考。
189
192
 
190
- ### 前台调试与连通性检查
193
+ ### 前台调试
191
194
 
192
- `run` / `check` 是独立的顶层调试命令,不属于持久化服务模式:
195
+ `run` 是独立的顶层调试命令,不属于持久化服务模式:
193
196
 
194
197
  ```bash
195
198
  flexgate run # 单个前台进程,仅用于开发/调试
196
- flexgate check # 上游 provider 连通性检测
197
199
  ```
198
200
 
199
201
  非 systemd 环境只能使用 `flexgate run` 前台运行。`--port PORT` 也只对
@@ -259,10 +261,17 @@ Flexgate config — ~/.flexgate/config.yaml
259
261
  ### Settings 管理
260
262
 
261
263
  ```bash
262
- flexgate settings import # 从 ~/.claude/settings.json* 导入凭证到 config.yaml
263
- flexgate settings apply # 将 config.yaml 配置写入 ~/.claude/settings.json
264
+ flexgate settings import # 从 ~/.claude/settings.json* 导入凭证到 config.yaml(多 key 追加)
265
+ flexgate settings apply # 将网关 env 写入 ~/.claude/settings.json(非破坏性)
266
+ flexgate settings apply --dry-run # 预览将要修改的 env 键,不写文件
264
267
  ```
265
268
 
269
+ `apply` 只重写 flexgate 托管的 6 个 env 键(BASE_URL、AUTH_TOKEN、
270
+ API_TIMEOUT_MS、三个 `ANTHROPIC_DEFAULT_*_MODEL`),settings.json 中的
271
+ 其他字段(hooks、permissions、自定义 env 等)全部保留;写入前自动备份。
272
+ `import` 会把新 key 追加到 provider 的 `api_keys` 列表(已存在则跳过),
273
+ 不会覆盖已有 key。
274
+
266
275
  ### 全局参数
267
276
 
268
277
  - `--config PATH` 指定配置文件(默认 `~/.flexgate/config.yaml`)
@@ -278,7 +287,7 @@ flexgate settings apply # 将 config.yaml 配置写入 ~/.claude/settin
278
287
  | `~/.flexgate/service-state.json` | 最近一次成功启动所应用的 config 路径与 endpoint |
279
288
  | `~/.flexgate/update-check.json` | PyPI 新版本检查的缓存(24h 有效期) |
280
289
  | `~/.config/systemd/user/flexgate.service` | 唯一的持久化服务 unit |
281
- | systemd journal | 服务日志(`journalctl --user -u flexgate`) |
290
+ | systemd journal | 服务日志(`flexgate log`,即 `journalctl --user -u flexgate`) |
282
291
 
283
292
  旧版本的 `~/.flexgate/flexgate.pid`、`flexgate.guardian.pid` 和
284
293
  `flexgate.log` 不再属于当前运行架构;service 启动时会安全清理 PID 残留,
@@ -89,13 +89,13 @@ flexgate service uninstall # 停止、禁用并删除 unit
89
89
  - 若升级时检测到旧版后台 gateway 仍在运行,会先准备好 systemd unit,但不会强杀正在服务的进程;按提示手动 `kill <PID>` 停掉旧进程,再执行 `flexgate service start` 完成切换。
90
90
  - unit 设置了启动速率限制,永久配置错误不会再无限快速重启。
91
91
  - `service reload` 和 `config set/edit` 会在仅路由变化时发送 SIGUSR1;如果 endpoint 变化,则先检查再 restart。若只改 host、仍复用当前 port,为避免误停服务会要求先执行 `service stop`,再执行 `service start`。
92
- - 查看日志:`journalctl --user -u flexgate -e`。
92
+ - 查看日志:`flexgate log`(`-f` 跟随、`--since`/`--grep` 过滤、`-r` 只看路由决策行)。
93
93
 
94
94
  ### 版本与升级
95
95
 
96
96
  ```bash
97
97
  flexgate --version # 打印版本号(service status / 裸 flexgate 也会显示)
98
- flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings
98
+ flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings、上游连通性
99
99
  flexgate doctor --offline # 跳过 PyPI 检查
100
100
  flexgate update # 一键升级:pip/pipx/uv 升级包 + 迁移配置 schema + 热重载服务
101
101
  flexgate update --check # 只报告将要做什么,不改动
@@ -132,7 +132,7 @@ Trusted Publisher(repo: `Agony5757/flexible-gateway`,workflow: `release.yml`
132
132
 
133
133
  ### 上游连通性预检
134
134
 
135
- 运行 `flexgate check` 会向每个 **被路由引用的 `(provider, model)` 组合**
135
+ 运行 `flexgate doctor` 会向每个 **被路由引用的 `(provider, model)` 组合**
136
136
  发送一次 `POST /v1/messages`(`max_tokens=1`,消耗约 1~2 token),用于主动检查:
137
137
 
138
138
  - DNS / TCP / TLS 不可达(`base_url` 写错、网络不通)
@@ -140,7 +140,10 @@ Trusted Publisher(repo: `Agony5757/flexible-gateway`,workflow: `release.yml`
140
140
  - 仍是默认占位符(如 `your-zai-api-key`)
141
141
  - Provider 侧 5xx 故障
142
142
 
143
- 可通过 `--verify-timeout N` 调整每个 provider 的超时时间(默认 15 秒)。
143
+ 可通过 `--verify-timeout N` 调整每个 provider 的超时时间(默认 15 秒),
144
+ `--offline` 跳过全部网络检查(PyPI 新版检查 + 上游探测)。
145
+ 旧的 `flexgate check` 命令仍可用,但已弃用:它会打印提示并委托给 `doctor`
146
+ (注意 doctor 还检查本地安装,退出码语义更宽)。
144
147
 
145
148
  ### 状态总览与用量查询(`flexgate status` / `flexgate usage`)
146
149
 
@@ -170,13 +173,12 @@ fallback 链、当前生效的路由,并逐一查询**每个 key** 的用量/
170
173
  如果某平台的专用接口调用失败,flexgate 会自动退化为 minimal probe 再试一次。
171
174
  用量查询接口多为平台内部接口,可能随时变动;查询结果仅供参考。
172
175
 
173
- ### 前台调试与连通性检查
176
+ ### 前台调试
174
177
 
175
- `run` / `check` 是独立的顶层调试命令,不属于持久化服务模式:
178
+ `run` 是独立的顶层调试命令,不属于持久化服务模式:
176
179
 
177
180
  ```bash
178
181
  flexgate run # 单个前台进程,仅用于开发/调试
179
- flexgate check # 上游 provider 连通性检测
180
182
  ```
181
183
 
182
184
  非 systemd 环境只能使用 `flexgate run` 前台运行。`--port PORT` 也只对
@@ -242,10 +244,17 @@ Flexgate config — ~/.flexgate/config.yaml
242
244
  ### Settings 管理
243
245
 
244
246
  ```bash
245
- flexgate settings import # 从 ~/.claude/settings.json* 导入凭证到 config.yaml
246
- flexgate settings apply # 将 config.yaml 配置写入 ~/.claude/settings.json
247
+ flexgate settings import # 从 ~/.claude/settings.json* 导入凭证到 config.yaml(多 key 追加)
248
+ flexgate settings apply # 将网关 env 写入 ~/.claude/settings.json(非破坏性)
249
+ flexgate settings apply --dry-run # 预览将要修改的 env 键,不写文件
247
250
  ```
248
251
 
252
+ `apply` 只重写 flexgate 托管的 6 个 env 键(BASE_URL、AUTH_TOKEN、
253
+ API_TIMEOUT_MS、三个 `ANTHROPIC_DEFAULT_*_MODEL`),settings.json 中的
254
+ 其他字段(hooks、permissions、自定义 env 等)全部保留;写入前自动备份。
255
+ `import` 会把新 key 追加到 provider 的 `api_keys` 列表(已存在则跳过),
256
+ 不会覆盖已有 key。
257
+
249
258
  ### 全局参数
250
259
 
251
260
  - `--config PATH` 指定配置文件(默认 `~/.flexgate/config.yaml`)
@@ -261,7 +270,7 @@ flexgate settings apply # 将 config.yaml 配置写入 ~/.claude/settin
261
270
  | `~/.flexgate/service-state.json` | 最近一次成功启动所应用的 config 路径与 endpoint |
262
271
  | `~/.flexgate/update-check.json` | PyPI 新版本检查的缓存(24h 有效期) |
263
272
  | `~/.config/systemd/user/flexgate.service` | 唯一的持久化服务 unit |
264
- | systemd journal | 服务日志(`journalctl --user -u flexgate`) |
273
+ | systemd journal | 服务日志(`flexgate log`,即 `journalctl --user -u flexgate`) |
265
274
 
266
275
  旧版本的 `~/.flexgate/flexgate.pid`、`flexgate.guardian.pid` 和
267
276
  `flexgate.log` 不再属于当前运行架构;service 启动时会安全清理 PID 残留,
@@ -2,7 +2,7 @@
2
2
 
3
3
  ```text
4
4
  usage: flexgate [-h] [--config CONFIG] [--version]
5
- {service,run,check,status,usage,settings,sync,help,doctor,update,config} ...
5
+ {service,run,check,status,usage,log,settings,sync,help,doctor,update,config} ...
6
6
  ```
7
7
 
8
8
  全局参数:
@@ -45,7 +45,7 @@ flexgate service uninstall # 停止、禁用并删除 unit
45
45
  - `service reload` 和 `config set/edit` 会在仅路由变化时发送 SIGUSR1;
46
46
  如果 endpoint 变化,则先检查再 restart。若只改 host、仍复用当前 port,
47
47
  为避免误停服务会要求先执行 `service stop`,再执行 `service start`。
48
- - 查看日志:`journalctl --user -u flexgate -e`。
48
+ - 查看日志:`flexgate log`(`-f` 跟随、`--since`/`--grep` 过滤、`-r` 只看路由决策行)。
49
49
 
50
50
  ## 状态总览与用量查询
51
51
 
@@ -59,9 +59,32 @@ flexgate usage --usage-timeout 30
59
59
 
60
60
  各平台的用量查询适配器详见[用量查询](usage.md)。
61
61
 
62
+ ## 查看日志(log)
63
+
64
+ ```bash
65
+ flexgate log # 最近 50 行服务日志(systemd user journal)
66
+ flexgate log -n 200 # 最近 200 行
67
+ flexgate log -f # 实时跟随(Ctrl-C 退出)
68
+ flexgate log --since "10 min ago"
69
+ flexgate log --grep 429 # 只显示包含 429 的行(大小写不敏感)
70
+ flexgate log -r # 只看路由决策行
71
+ flexgate log -f -r # 实时观察每个请求被路由到哪个 provider/模型
72
+ ```
73
+
74
+ `flexgate log` 读取 `flexgate.service` 的 systemd 用户日志(等价于
75
+ `journalctl --user -u flexgate`,输出为单时间戳的 cat 格式)。`-r` 只显示
76
+ 每个请求完成时的路由决策行:
77
+
78
+ ```text
79
+ [default] claude-sonnet-4-6 -> zai (glm-5.3) | 200 | 6676ms
80
+ ```
81
+
82
+ 想知道"刚才那条回复到底是哪个模型服务的",看这一行即可。前台调试用的
83
+ `flexgate run` 日志直接打印在终端,不进 journal。
84
+
62
85
  ## 上游连通性预检
63
86
 
64
- `flexgate check` 会向每个**被路由引用的 `(provider, model)` 组合**发送一次
87
+ `flexgate doctor` 会向每个**被路由引用的 `(provider, model)` 组合**发送一次
65
88
  `POST /v1/messages`(`max_tokens=1`,消耗约 1~2 token),用于主动检查:
66
89
 
67
90
  - DNS / TCP / TLS 不可达(`base_url` 写错、网络不通)
@@ -69,15 +92,17 @@ flexgate usage --usage-timeout 30
69
92
  - 仍是默认占位符(如 `your-zai-api-key`)
70
93
  - Provider 侧 5xx 故障
71
94
 
72
- 可通过 `--verify-timeout N` 调整每个 provider 的超时时间(默认 15 秒)。
95
+ 可通过 `--verify-timeout N` 调整每个 provider 的探测超时(默认 15 秒);
96
+ `--offline` 跳过全部网络检查(PyPI 新版检查 + 上游探测)。旧的
97
+ `flexgate check` 命令已弃用:仍可运行,但会打印提示并委托给 `doctor`
98
+ (注意 doctor 还检查本地安装,退出码语义更宽)。
73
99
 
74
100
  ## 前台调试
75
101
 
76
- `run` / `check` 是独立的顶层调试命令,不属于持久化服务模式:
102
+ `run` 是独立的顶层调试命令,不属于持久化服务模式:
77
103
 
78
104
  ```bash
79
105
  flexgate run # 单个前台进程,仅用于开发/调试
80
- flexgate check # 上游 provider 连通性检测
81
106
  ```
82
107
 
83
108
  非 systemd 环境只能使用 `flexgate run` 前台运行。`--port PORT` 也只对
@@ -157,22 +182,26 @@ Flexgate config — ~/.flexgate/config.yaml
157
182
  ## Settings 管理
158
183
 
159
184
  ```bash
160
- flexgate settings import # 从 ~/.claude/settings.json* 导入凭证到 config.yaml
161
- flexgate settings apply # 将 config.yaml 配置写入 ~/.claude/settings.json
185
+ flexgate settings import # 从 ~/.claude/settings.json* 导入凭证到 config.yaml(多 key 追加)
186
+ flexgate settings apply # 将网关 env 写入 ~/.claude/settings.json(非破坏性)
187
+ flexgate settings apply --dry-run # 预览将要修改的 env 键,不写文件
162
188
  ```
163
189
 
164
190
  **import** 会扫描 `~/.claude/settings.json*`,从每个文件中提取
165
- `ANTHROPIC_BASE_URL` 和 `ANTHROPIC_AUTH_TOKEN`,自动写入 config.yaml 的
166
- providers 部分。文件名与 provider 名称的映射规则:
191
+ `ANTHROPIC_BASE_URL` 和 `ANTHROPIC_AUTH_TOKEN`,合并进 config.yaml 的
192
+ providers:provider 已存在时**追加**到它的 `api_keys` 列表(已存在同一把
193
+ key 则跳过),不存在则新建。文件名与 provider 名称的映射规则:
167
194
 
168
195
  - `settings.json` → 根据域名自动推断(如含 `z.ai` → `zai`)
169
196
  - `settings.json.zai` → provider 名 `zai`
170
197
  - `settings.json.bak.*` → 跳过(备份文件)
171
198
 
172
- **apply** 会读取 config.yaml 中的 `server` 和 `claude_settings`,备份当前
173
- `~/.claude/settings.json` 为 `settings.json.bak.{timestamp}`,然后生成新的
174
- settings.json,将 `ANTHROPIC_BASE_URL` 指向本地网关,并保留原有的
175
- `permissions` 等非 env 字段:
199
+ **apply** 是非破坏性的:备份当前 `~/.claude/settings.json` 为
200
+ `settings.json.bak.{timestamp}` 后,**只重写 flexgate 托管的 6 个 env 键**
201
+ (`ANTHROPIC_BASE_URL`、`ANTHROPIC_AUTH_TOKEN`、`API_TIMEOUT_MS`、三个
202
+ `ANTHROPIC_DEFAULT_*_MODEL`),settings.json 中的其余字段(hooks、
203
+ permissions、自定义 env 等)全部原样保留。token 策略与 `service install`
204
+ 共用一条路径:显式参数 → 文件里已有的 token → 兜底 `"gateway"`:
176
205
 
177
206
  ```json
178
207
  {
@@ -211,8 +240,8 @@ flexgate sync --dry-run # 只预览,不写配置
211
240
 
212
241
  ```bash
213
242
  flexgate --version # 打印版本号
214
- flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings
215
- flexgate doctor --offline # 跳过 PyPI 检查
243
+ flexgate doctor # 只读体检:Python、PyPI 新版、配置 schema、端口、systemd、Claude settings、上游连通性
244
+ flexgate doctor --offline # 跳过全部网络检查(PyPI 新版检查 + 上游探测)
216
245
  flexgate update # 一键升级:pip/pipx/uv 升级包 + 迁移配置 schema + 热重载服务
217
246
  flexgate update --check # 只报告将要做什么,不改动
218
247
  flexgate update --config-only # 只迁移配置,不升级包
@@ -8,7 +8,7 @@
8
8
  | `~/.flexgate/service-state.json` | 最近一次成功启动所应用的 config 路径与 endpoint |
9
9
  | `~/.flexgate/update-check.json` | PyPI 新版本检查的缓存(24h 有效期) |
10
10
  | `~/.config/systemd/user/flexgate.service` | 唯一的持久化服务 unit |
11
- | systemd journal | 服务日志(`journalctl --user -u flexgate`) |
11
+ | systemd journal | 服务日志(`flexgate log`,即 `journalctl --user -u flexgate`) |
12
12
 
13
13
  旧版本的 `~/.flexgate/flexgate.pid`、`flexgate.guardian.pid` 和
14
14
  `flexgate.log` 不再属于当前运行架构;service 启动时会安全清理 PID 残留,
@@ -4,17 +4,19 @@
4
4
 
5
5
  | 文件 | 作用 |
6
6
  |------|------|
7
- | `flexgate/cli.py` | argparse CLI;service 生命周期命令、前台 `run`/`check`、config/settings/sync 命令 |
8
- | `flexgate/config.py` | dataclass 配置模型(`GatewayConfig`、`ProviderConfig`、`RouteConfig` 等),YAML 加载/保存 |
9
- | `flexgate/router.py` | `resolve(config, model)` — 先定时路由后默认路由,首个正则命中生效 |
10
- | `flexgate/proxy.py` | `handle_request()` — httpx 异步代理,per-key fallback 重试,SSE 流式 + JSON 透传 |
7
+ | `flexgate/cli.py` | argparse CLI;service 生命周期命令、前台 `run`、config/settings/sync 命令、`flexgate log` 日志查看 |
8
+ | `flexgate/ui.py` | 共享终端样式:ANSI 辅助函数、`ok`/`warn`/`fail`/`err` 输出辅助、`FlexgateHelpFormatter`(各版本一致的彩色 help;统一受 `NO_COLOR` + isatty 门控) |
9
+ | `flexgate/config.py` | dataclass 配置模型(`GatewayConfig`、`ProviderConfig`、`RouteConfig` 等),YAML 加载/保存,`is_placeholder_key` |
10
+ | `flexgate/router.py` | `resolve(config, model)` — 先定时路由后默认路由,首个正则命中生效;匹配前归一化模型别名(`sonnet`/`claude-3-7-sonnet-latest` → `^claude-<tier>`) |
11
+ | `flexgate/proxy.py` | `handle_request()` — httpx 异步代理,per-key fallback 重试,SSE 流式 + JSON 透传;记录每个请求由哪把 key 服务 |
11
12
  | `flexgate/usage.py` | `flexgate status` / `flexgate usage` 的用量查询 — 各平台适配器 + minimal chat probe 兜底 |
12
13
  | `flexgate/server.py` | Starlette 应用,`POST /v1/messages` 端点,SIGUSR1 热重载 |
13
- | `flexgate/service.py` | systemd 用户服务的安装/启停/重载/状态(持久化运行的唯一入口) |
14
- | `flexgate/settings.py` | config.yaml ↔ `~/.claude/settings.json` 双向桥接 |
14
+ | `flexgate/service.py` | systemd 用户服务的安装/启停/重载/状态(持久化运行的唯一入口);journal 日志查看 |
15
+ | `flexgate/healthcheck.py` | 上游连通性探测(`POST /v1/messages`,`max_tokens=1`),由 `doctor` 调用(`flexgate check` 为弃用别名) |
16
+ | `flexgate/settings.py` | config.yaml ↔ `~/.claude/settings.json` 双向桥接(apply 非破坏性:只改托管 env 键) |
15
17
  | `flexgate/sync.py` | `flexgate sync` — 通过 confsync 服务器加密同步整份配置 |
16
18
  | `flexgate/migrate.py` | 配置 schema 版本化:逐级迁移链 + 备份 + 原子重写 |
17
- | `flexgate/doctor.py` | `flexgate doctor` — 只读诊断 |
19
+ | `flexgate/doctor.py` | `flexgate doctor` — 只读诊断 + 上游连通性(`--offline` 跳过网络检查) |
18
20
  | `flexgate/update.py` | `flexgate update` — PyPI 版本检查、按安装方式升级、配置迁移、服务重载 |
19
21
 
20
22
  ## 关键设计
@@ -49,5 +49,5 @@ provider。日常管理常用:
49
49
  ```bash
50
50
  flexgate status # providers + fallback 链 + 每个 key 的用量 + 当前路由
51
51
  flexgate config edit # 交互式调整每个 tier 的 provider/model
52
- journalctl --user -u flexgate -e # 查看服务日志
52
+ flexgate log # 查看服务日志(-r 只看路由决策行)
53
53
  ```
@@ -1,3 +1,3 @@
1
1
  """Flexgate — Flexible API Gateway for Claude Code."""
2
2
 
3
- __version__ = "0.5.1"
3
+ __version__ = "0.7.0"