@bitkyc08/opencodex 1.9.5 → 2.0.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.ko.md +95 -71
- package/README.md +93 -46
- package/README.zh-CN.md +101 -70
- package/gui/dist/assets/index-DRr-3yL3.css +1 -0
- package/gui/dist/assets/index-LGqpEmI5.js +9 -0
- package/gui/dist/index.html +13 -3
- package/package.json +1 -3
- package/src/adapters/openai-chat.ts +34 -20
- package/src/bridge.ts +13 -5
- package/src/cli.ts +11 -9
- package/src/codex-catalog.ts +147 -31
- package/src/config.ts +2 -1
- package/src/oauth/index.ts +28 -12
- package/src/oauth/key-providers.ts +27 -0
- package/src/providers/derive.ts +35 -0
- package/src/providers/registry.ts +130 -7
- package/src/reasoning-effort.ts +102 -0
- package/src/responses/parser.ts +1 -1
- package/src/server.ts +19 -2
- package/src/service.ts +26 -2
- package/src/star-prompt.ts +5 -4
- package/src/types.ts +22 -0
- package/src/ws-bridge.ts +5 -2
- package/gui/dist/assets/index-C1wlp1SM.css +0 -1
- package/gui/dist/assets/index-CDhJ0DI7.js +0 -9
- package/scripts/postinstall.mjs +0 -57
package/README.zh-CN.md
CHANGED
|
@@ -1,27 +1,16 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
<source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.png">
|
|
5
|
-
<img alt="opencodex" src="assets/logo-light.png" width="96" height="96">
|
|
6
|
-
</picture>
|
|
7
|
-
|
|
8
|
-
# opencodex (`ocx`)
|
|
9
|
-
|
|
10
|
-
**面向 [OpenAI Codex](https://openai.com/codex) 的通用 provider proxy —— 在 Codex CLI、App 和 SDK 中使用任意 LLM。**
|
|
11
|
-
|
|
12
|
-
[English](README.md) · [한국어](README.ko.md) · **简体中文**
|
|
13
|
-
|
|
14
|
-
📖 **[完整文档 →](https://lidge-jun.github.io/opencodex/zh-cn/)**
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/banner.png" alt="opencodex — 让 Codex 接入任意 LLM" width="820">
|
|
3
|
+
</p>
|
|
15
4
|
|
|
16
|
-
|
|
5
|
+
<p align="center">
|
|
6
|
+
<a href="README.md">English</a> · <a href="README.ko.md">한국어</a> · <b>简体中文</b> · 📖 <a href="https://lidge-jun.github.io/opencodex/zh-cn/"><b>完整文档 →</b></a>
|
|
7
|
+
</p>
|
|
17
8
|
|
|
18
9
|
<p align="center">
|
|
19
|
-
<img src="assets/
|
|
10
|
+
<img src="assets/architecture.png" alt="opencodex 架构 — Codex CLI 通过 opencodex 代理路由到任意 LLM 提供商" width="820">
|
|
20
11
|
</p>
|
|
21
12
|
|
|
22
|
-
Codex
|
|
23
|
-
provider 之间,实时翻译两者之间的协议 —— 包括 streaming、工具调用、推理(reasoning)和图像
|
|
24
|
-
—— 并且是双向的。
|
|
13
|
+
Codex 只认 Responses API(`/v1/responses`)。opencodex 做的事情很简单:架在 Codex 和你的 LLM provider 中间,把协议实时翻译过去——streaming、tool 调用、reasoning、图片,全都覆盖,双向通信。
|
|
25
14
|
|
|
26
15
|
```
|
|
27
16
|
Codex CLI / App / SDK ──/v1/responses──▶ opencodex ──▶ Any provider
|
|
@@ -30,24 +19,34 @@ Codex CLI / App / SDK ──/v1/responses──▶ opencodex ──▶ Any provi
|
|
|
30
19
|
OpenRouter · Azure · DeepSeek · GLM · …and OpenAI itself
|
|
31
20
|
```
|
|
32
21
|
|
|
22
|
+
## 支持平台
|
|
23
|
+
|
|
24
|
+
| 操作系统 | 状态 | 服务管理 |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| macOS (arm64 / x64) | 完整支持 | launchd |
|
|
27
|
+
| Linux (x64 / arm64) | 完整支持 | systemd(用户级) |
|
|
28
|
+
| Windows (x64) | 完整支持 | Task Scheduler |
|
|
29
|
+
|
|
30
|
+
需要 [Bun](https://bun.sh) 1.1+。三个平台都原生运行(Windows 不需要 WSL)。
|
|
31
|
+
|
|
33
32
|
## 快速开始
|
|
34
33
|
|
|
35
34
|
```bash
|
|
36
|
-
#
|
|
37
|
-
npm install -g @bitkyc08/opencodex #
|
|
35
|
+
# 安装
|
|
36
|
+
npm install -g @bitkyc08/opencodex # 或者: bun install -g @bitkyc08/opencodex
|
|
38
37
|
|
|
39
|
-
#
|
|
38
|
+
# 交互式初始化(写入配置 + 注入 Codex)
|
|
40
39
|
ocx init
|
|
41
40
|
|
|
42
|
-
#
|
|
41
|
+
# 启动代理
|
|
43
42
|
ocx start
|
|
44
43
|
|
|
45
|
-
#
|
|
44
|
+
# 正常使用 Codex —— 请求已经通过 opencodex 路由
|
|
46
45
|
codex "Write a hello world in Rust"
|
|
47
46
|
```
|
|
48
47
|
|
|
49
48
|
<details>
|
|
50
|
-
<summary><b
|
|
49
|
+
<summary><b>还没装 <a href="https://bun.sh">bun</a>?</b> —— 先装一下(opencodex 跑在 bun 上)</summary>
|
|
51
50
|
|
|
52
51
|
<br/>
|
|
53
52
|
|
|
@@ -59,37 +58,51 @@ curl -fsSL https://bun.sh/install | bash
|
|
|
59
58
|
powershell -c "irm bun.sh/install.ps1 | iex"
|
|
60
59
|
```
|
|
61
60
|
|
|
62
|
-
|
|
61
|
+
装完之后重新跑 `npm install -g @bitkyc08/opencodex`。(`ocx` 是 bun 原生二进制,所以 bun 必须在你的 `PATH` 里。)
|
|
63
62
|
|
|
64
63
|
</details>
|
|
65
64
|
|
|
66
|
-
|
|
65
|
+
## 亮点
|
|
66
|
+
|
|
67
|
+
- **一个代理,20+ provider。** Anthropic、Google、xAI、Kimi、Ollama Cloud、Groq、Azure、DeepSeek、OpenRouter……装一次就全通了。
|
|
68
|
+
- **5 种 adapter 覆盖一切。** Anthropic Messages、Google Gemini、Azure、OpenAI Responses 直通,以及**所有 OpenAI 兼容 Chat Completions** 端点——不管你用什么 LLM,总有一个 adapter 能接上。
|
|
69
|
+
- **三种认证方式,随你挑。** OAuth 登录(xAI / Anthropic / Kimi,token 自动刷新)、转发 `codex login`、或直接粘贴 API key(支持 `${ENV_VARS}`)。内置 18 家 provider 的 API key 目录(含 **Ollama Cloud**)。
|
|
70
|
+
- **即插即用 Codex 全家桶。** 自动向 `~/.codex/config.toml` 注入 `[model_providers.opencodex]`,并写入共享模型目录——路由模型直接出现在 Codex 的模型选择器里,CLI、TUI、App、SDK 全部适用。
|
|
71
|
+
- **Subagent 控制。** 在 `subagentModels` 或 Web 仪表盘中,把最多 5 个路由/原生模型置顶到 Codex 的 `spawn_agent` 选择器。
|
|
72
|
+
- **Sidecar 能力加持。** 非 OpenAI 模型也能拥有真正的**网页搜索**和**图片理解**——通过你的 ChatGPT 登录借用一个 `gpt-5.4-mini` 来实现。
|
|
73
|
+
- **Web 仪表盘。** 管理 provider、OAuth 登录、模型选择、请求日志,都在浏览器里完成。
|
|
74
|
+
- **HTTP/SSE 为默认,WebSocket 按需开启。** 只有显式设置 `"websockets": true` 时,代理才会广告 `supports_websockets`。
|
|
75
|
+
- **干净退出,零残留。** `ocx stop`(或仪表盘的 Stop 按钮)会关闭代理、停止后台服务(如果有的话)、并将 Codex 恢复为原始配置。之后 `codex` 命令就像从未安装过 opencodex 一样正常工作。
|
|
76
|
+
|
|
77
|
+
## 添加 Provider
|
|
78
|
+
|
|
79
|
+
最简单的方式:用 Web 仪表盘。
|
|
67
80
|
|
|
68
81
|
```bash
|
|
69
|
-
|
|
70
|
-
codex -m "ollama-cloud/glm-5.2" "Write a SQL migration"
|
|
82
|
+
ocx gui # 在浏览器中打开 localhost:10100
|
|
71
83
|
```
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
仪表盘提供 20+ 内置 provider 模板(Anthropic、Google、xAI、Kimi、Ollama Cloud、Groq、DeepSeek、OpenRouter 等等)。选一个,填入 API key 或用 OAuth 登录,保存即可。opencodex 会自动发现该 provider 支持的模型,并同步到 Codex 的模型选择器中。
|
|
86
|
+
|
|
87
|
+
如果你更习惯手动配置,直接编辑 `~/.opencodex/config.json`,在 `providers` 对象中添加一项即可。详见下方[配置](#配置)章节。
|
|
88
|
+
|
|
89
|
+
## 模型路由
|
|
74
90
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
## Providers 与 adapters
|
|
91
|
-
|
|
92
|
-
| Provider | Adapter | Auth |
|
|
91
|
+
通过 `provider/model` 格式指定路由模型,在 Codex 中直接使用:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
codex -m "anthropic/claude-opus-4-8" "解释这个 stack trace"
|
|
95
|
+
codex -m "google/gemini-2.5-pro" "重构这段代码"
|
|
96
|
+
codex -m "xai/grok-4" "写一个 SQL migration"
|
|
97
|
+
codex -m "ollama-cloud/glm-5.2" "生成单元测试"
|
|
98
|
+
codex -m "deepseek/deepseek-r1" "分析这个性能瓶颈"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
不指定 provider 前缀时,Codex 使用你配置的 `defaultProvider` 和 `defaultModel`。
|
|
102
|
+
|
|
103
|
+
## Provider 与 adapter
|
|
104
|
+
|
|
105
|
+
| Provider | Adapter | 认证方式 |
|
|
93
106
|
|---|---|---|
|
|
94
107
|
| OpenAI(ChatGPT 登录) | `openai-responses` | 转发(无需 key) |
|
|
95
108
|
| OpenAI(API key) | `openai-responses` | key |
|
|
@@ -105,22 +118,24 @@ codex -m "ollama-cloud/glm-5.2" "Write a SQL migration"
|
|
|
105
118
|
## CLI
|
|
106
119
|
|
|
107
120
|
```bash
|
|
108
|
-
ocx init #
|
|
109
|
-
ocx start [--port 10100] #
|
|
110
|
-
ocx stop #
|
|
111
|
-
ocx restore #
|
|
112
|
-
ocx sync #
|
|
113
|
-
ocx status #
|
|
114
|
-
ocx login <xai|anthropic|kimi> # OAuth
|
|
115
|
-
ocx logout <provider> #
|
|
116
|
-
ocx gui #
|
|
117
|
-
ocx service <install|start|stop|status|uninstall> #
|
|
118
|
-
ocx update #
|
|
121
|
+
ocx init # 交互式初始化
|
|
122
|
+
ocx start [--port 10100] # 启动代理
|
|
123
|
+
ocx stop # 停止并恢复原生 Codex 配置
|
|
124
|
+
ocx restore # 仅恢复,不停止(别名:ocx eject)
|
|
125
|
+
ocx sync # 刷新模型列表 + 重新注入 Codex
|
|
126
|
+
ocx status # 查看代理是否在运行
|
|
127
|
+
ocx login <xai|anthropic|kimi> # OAuth 登录
|
|
128
|
+
ocx logout <provider> # 移除已保存的登录
|
|
129
|
+
ocx gui # 打开 Web 仪表盘
|
|
130
|
+
ocx service <install|start|stop|status|uninstall> # 后台服务(launchd/systemd/schtasks)
|
|
131
|
+
ocx update # 更新到最新版
|
|
119
132
|
```
|
|
120
133
|
|
|
121
134
|
## 配置
|
|
122
135
|
|
|
123
|
-
|
|
136
|
+
配置文件路径:`~/.opencodex/config.json`。
|
|
137
|
+
|
|
138
|
+
**云端 provider 示例:**
|
|
124
139
|
|
|
125
140
|
```json
|
|
126
141
|
{
|
|
@@ -143,18 +158,34 @@ ocx update # update opencodex to the latest published versio
|
|
|
143
158
|
}
|
|
144
159
|
```
|
|
145
160
|
|
|
146
|
-
|
|
147
|
-
|
|
161
|
+
**本地 provider 示例(Ollama / vLLM / LM Studio):**
|
|
162
|
+
|
|
163
|
+
```json
|
|
164
|
+
{
|
|
165
|
+
"port": 10100,
|
|
166
|
+
"defaultProvider": "local",
|
|
167
|
+
"providers": {
|
|
168
|
+
"local": {
|
|
169
|
+
"adapter": "openai-chat",
|
|
170
|
+
"baseUrl": "http://localhost:11434/v1",
|
|
171
|
+
"apiKey": "",
|
|
172
|
+
"defaultModel": "qwen3:32b"
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
本地 provider 的 `apiKey` 通常留空。只要你的本地服务暴露了 OpenAI 兼容的 Chat Completions 端点,opencodex 就能直接对接。
|
|
179
|
+
|
|
180
|
+
WebSocket 传输默认关闭。只有当你希望 Codex 使用 Responses WebSocket 而不是 HTTP/SSE 时,才需要设置 `"websockets": true`。
|
|
148
181
|
|
|
149
|
-
|
|
182
|
+
每个字段的详细说明参阅 **[配置参考](https://lidge-jun.github.io/opencodex/zh-cn/reference/configuration/)**。
|
|
150
183
|
|
|
151
184
|
## 文档
|
|
152
185
|
|
|
153
|
-
|
|
154
|
-
以及 CLI/配置参考 —— 是位于 [`docs-site/`](./docs-site) 下的一个 Astro 站点,
|
|
155
|
-
并发布于 **[lidge-jun.github.io/opencodex](https://lidge-jun.github.io/opencodex/zh-cn/)**。
|
|
186
|
+
完整文档——安装、provider 配置、路由、sidecar、Codex 集成、Codex App 模型选择器、CLI/配置参考——由 [`docs-site/`](./docs-site) 目录下的 Astro 站点构建,发布在 **[lidge-jun.github.io/opencodex](https://lidge-jun.github.io/opencodex/zh-cn/)**。
|
|
156
187
|
|
|
157
|
-
维护者 source of truth 位于 [`structure/`](./structure)
|
|
188
|
+
维护者 source of truth 位于 [`structure/`](./structure),历史调查和诊断笔记保留在 [`docs/`](./docs)。
|
|
158
189
|
|
|
159
190
|
## 开发
|
|
160
191
|
|
|
@@ -162,11 +193,11 @@ WebSocket 传输默认关闭。只有当你希望 Codex 广告并使用 Response
|
|
|
162
193
|
git clone https://github.com/lidge-jun/opencodex.git
|
|
163
194
|
cd opencodex
|
|
164
195
|
bun install
|
|
165
|
-
bun run dev #
|
|
166
|
-
bun x tsc --noEmit #
|
|
196
|
+
bun run dev # 以开发模式启动代理
|
|
197
|
+
bun x tsc --noEmit # 类型检查
|
|
167
198
|
```
|
|
168
199
|
|
|
169
|
-
|
|
200
|
+
参阅 **[贡献指南](https://lidge-jun.github.io/opencodex/zh-cn/contributing/)**。
|
|
170
201
|
|
|
171
202
|
## 许可证
|
|
172
203
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--bg:var(--lightningcss-light,#f6f7f9)var(--lightningcss-dark,#0c0d11);--rail:var(--lightningcss-light,#fff)var(--lightningcss-dark,#101218);--surface:var(--lightningcss-light,#fff)var(--lightningcss-dark,#15171d);--raised:var(--lightningcss-light,#f1f2f5)var(--lightningcss-dark,#1c1f27);--raised-hover:var(--lightningcss-light,#e8eaee)var(--lightningcss-dark,#242833);--border:var(--lightningcss-light,#e2e4e9)var(--lightningcss-dark,#2a2e39);--border-soft:var(--lightningcss-light,#ededf1)var(--lightningcss-dark,#20242d);--hover:var(--lightningcss-light,#11131c09)var(--lightningcss-dark,#ffffff06);--text:var(--lightningcss-light,#16181d)var(--lightningcss-dark,#edeef2);--muted:var(--lightningcss-light,#5b6270)var(--lightningcss-dark,#a3a9b5);--faint:var(--lightningcss-light,#868d9b)var(--lightningcss-dark,#6b7280);--accent:var(--lightningcss-light,#4f46e5)var(--lightningcss-dark,#6366f1);--accent-hover:var(--lightningcss-light,#4338ca)var(--lightningcss-dark,#818cf8);--accent-ink:#fff;--accent-soft:var(--lightningcss-light,#4f46e51a)var(--lightningcss-dark,#6366f129);--accent-ring:var(--lightningcss-light,#4f46e566)var(--lightningcss-dark,#6366f180);--green:var(--lightningcss-light,#047857)var(--lightningcss-dark,#34d399);--green-soft:var(--lightningcss-light,#0596691a)var(--lightningcss-dark,#34d39921);--red:var(--lightningcss-light,#b91c1c)var(--lightningcss-dark,#f87171);--red-soft:var(--lightningcss-light,#b91c1c17)var(--lightningcss-dark,#f8717121);--amber:var(--lightningcss-light,#b45309)var(--lightningcss-dark,#fbbf24);--amber-soft:var(--lightningcss-light,#b453091a)var(--lightningcss-dark,#fbbf2421);--radius:8px;--radius-sm:5px;--radius-xs:4px;--font:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, "Helvetica Neue", sans-serif;--mono:ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code", Menlo, Consolas, monospace;--shadow:0 1px 2px var(--lightningcss-light,#1018280f)var(--lightningcss-dark,#00000080), 0 10px 28px var(--lightningcss-light,#10182812)var(--lightningcss-dark,#0000004d);--shadow-sm:0 1px 2px var(--lightningcss-light,#1018280f)var(--lightningcss-dark,#0006)}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}:root[data-theme=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}:root[data-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}*{box-sizing:border-box}html,body,#root{height:100%}body{background:var(--bg);color:var(--text);font-family:var(--font);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;margin:0;font-size:14px;line-height:1.5}a{color:var(--accent-hover);text-decoration:none}a:hover{text-decoration:underline}code,.mono{font-family:var(--mono);font-size:.92em}h1,h2,h3,h4{letter-spacing:-.01em;margin:0;font-weight:650}::selection{background:var(--accent-soft)}input[type=checkbox],input[type=radio]{accent-color:var(--accent)}::-webkit-scrollbar{width:10px;height:10px}::-webkit-scrollbar-thumb{background:var(--border);border:2px solid var(--bg);border-radius:99px}::-webkit-scrollbar-thumb:hover{background:var(--faint)}:focus-visible{outline:2px solid var(--accent-ring);outline-offset:2px;border-radius:4px}.app{grid-template-columns:232px 1fr;min-height:100dvh;display:grid}.sidebar{border-right:1px solid var(--border);background:var(--rail);flex-direction:column;align-self:start;gap:4px;height:100dvh;padding:18px 14px;display:flex;position:sticky;top:0}.brand{align-items:center;gap:10px;padding:6px 8px 14px;display:flex}.brand-logo{background:var(--text);flex-shrink:0;width:26px;height:26px;-webkit-mask:url(/logo.png) 50%/contain no-repeat;mask:url(/logo.png) 50%/contain no-repeat}.brand .name{letter-spacing:-.02em;font-size:15px;font-weight:700;line-height:26px}.brand .ver{font-family:var(--mono);color:var(--muted);background:var(--raised);border:1px solid var(--border);border-radius:99px;align-self:center;padding:2px 6px;font-size:10px;line-height:1}.nav-item{border-radius:var(--radius-sm);text-align:left;cursor:pointer;width:100%;color:var(--muted);font:inherit;background:0 0;border:none;align-items:center;gap:10px;padding:8px 10px;font-size:13.5px;font-weight:500;transition:background .12s,color .12s;display:flex}.nav-item:hover{background:var(--raised);color:var(--text)}.nav-item.active{background:var(--accent-soft);color:var(--text)}.nav-item svg{width:17px;height:17px;color:var(--faint);flex-shrink:0}.nav-item.active svg{color:var(--accent)}.sidebar-foot{flex-direction:column;gap:2px;margin-top:auto;padding-top:12px;display:flex}.sidebar-link{color:var(--muted);border-radius:var(--radius-sm);align-items:center;gap:9px;padding:8px 10px;font-size:13px;display:flex}.sidebar-link:hover{background:var(--raised);color:var(--text);text-decoration:none}.sidebar-link svg{width:16px;height:16px}.theme-toggle{text-align:left;cursor:pointer;width:100%;color:var(--muted);font:inherit;border-radius:var(--radius-sm);background:0 0;border:none;align-items:center;gap:9px;padding:8px 10px;font-size:13px;transition:background .12s,color .12s;display:flex}.theme-toggle:hover{background:var(--raised);color:var(--text)}.theme-toggle svg{flex-shrink:0;width:16px;height:16px}.theme-toggle .mode{text-transform:capitalize}.stop-toggle{color:var(--red)}.stop-toggle:hover{background:var(--red-soft);color:var(--red)}.stop-toggle:disabled{opacity:.5;cursor:default}.main{min-width:0}.main-inner{max-width:980px;margin:0 auto;padding:32px 36px 64px}.page-head{justify-content:space-between;align-items:center;gap:16px;margin-bottom:6px;display:flex}.page-head h2{font-size:19px}.page-sub{color:var(--muted);max-width:70ch;margin:4px 0 22px;font-size:13.5px}.page-sub b{color:var(--text);font-weight:600}.btn{border-radius:var(--radius-sm);font:inherit;cursor:pointer;white-space:nowrap;border:1px solid #0000;justify-content:center;align-items:center;gap:7px;padding:7px 14px;font-size:13px;font-weight:550;transition:background .12s,border-color .12s,opacity .12s;display:inline-flex}.btn svg{width:15px;height:15px}.btn:disabled{opacity:.55;cursor:default}.btn-primary{background:var(--accent);color:var(--accent-ink)}.btn-primary:hover:not(:disabled){background:var(--accent-hover)}.btn-ghost{background:var(--raised);color:var(--text);border-color:var(--border)}.btn-ghost:hover:not(:disabled){background:var(--raised-hover)}.btn-danger{color:var(--red);background:0 0;border-color:#f871714d}.btn-danger:hover:not(:disabled){background:var(--red-soft)}.btn-sm{border-radius:var(--radius-xs);padding:4px 9px;font-size:12px}.btn-icon{padding:5px}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px}.panel-accent{background:linear-gradient(180deg, var(--accent-soft), transparent 120%), var(--surface);border-color:#7c5cff47}.stat-row{grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:28px;display:grid}.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:14px 16px;transition:border-color .12s}.stat:hover{border-color:var(--accent-ring)}.stat .label{color:var(--muted);text-transform:uppercase;letter-spacing:.05em;align-items:center;gap:6px;margin-bottom:9px;font-size:11px;font-weight:600;display:flex}.stat .label svg{width:14px;height:14px}.stat .value{letter-spacing:-.02em;font-size:24px;font-weight:700;line-height:1.1}.stat .value.mono{font-family:var(--mono);font-size:19px}.model-group-head{color:var(--muted);text-transform:uppercase;letter-spacing:.04em;align-items:baseline;gap:8px;margin:0 0 8px;font-size:12px;font-weight:600;display:flex}.model-group-head .count{font-family:var(--mono);text-transform:none;letter-spacing:0;color:var(--faint);font-weight:500}.model-grid{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:8px;display:grid}.model-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);padding:10px 12px;transition:border-color .12s,background .12s}.model-card:hover{border-color:var(--accent-ring);background:var(--hover)}.model-card .id{font-family:var(--mono);letter-spacing:-.01em;color:var(--text);font-size:13px;font-weight:600}.badge{font-size:11px;font-weight:600;font-family:var(--mono);letter-spacing:.01em;border-radius:99px;align-items:center;gap:5px;padding:2px 8px;display:inline-flex}.badge-accent{background:var(--accent-soft);color:var(--accent-hover)}.badge-green{background:var(--green-soft);color:var(--green)}.badge-amber{background:var(--amber-soft);color:var(--amber)}.badge-muted{background:var(--raised);color:var(--muted);border:1px solid var(--border)}.dot{border-radius:50%;flex-shrink:0;width:7px;height:7px}.dot-green{background:var(--green);box-shadow:0 0 0 3px var(--green-soft)}.dot-red{background:var(--red);box-shadow:0 0 0 3px var(--red-soft)}.tbl{border-collapse:collapse;width:100%;font-size:13px}.tbl thead th{text-align:left;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--border);padding:9px 12px;font-size:11.5px;font-weight:600}.tbl tbody td{border-bottom:1px solid var(--border-soft);padding:10px 12px}.tbl tbody tr:last-child td{border-bottom:none}.tbl tbody tr:hover td{background:var(--hover)}.tbl .num{text-align:right;font-family:var(--mono)}.tbl-wrap{border:1px solid var(--border);border-radius:var(--radius);overflow-x:auto}.input,textarea.input{border-radius:var(--radius-sm);background:var(--raised);border:1px solid var(--border);width:100%;color:var(--text);font:inherit;padding:8px 11px;font-size:13px;transition:border-color .12s}.input::placeholder{color:var(--faint)}.input:focus{border-color:var(--accent);outline:none}textarea.input{resize:vertical;font-family:var(--mono);line-height:1.55}.field-label{color:var(--muted);margin-bottom:5px;font-size:12px;font-weight:500;display:block}select.input{appearance:none}.switch{cursor:pointer;background:var(--lightningcss-light,#c5c9d2)var(--lightningcss-dark,#3a3f4b);border:none;border-radius:99px;flex-shrink:0;width:34px;height:19px;padding:0;transition:background .15s;position:relative}.switch.on{background:var(--accent)}.switch:disabled{opacity:.6;cursor:default}.switch .knob{background:#fff;border-radius:50%;width:15px;height:15px;transition:left .15s;position:absolute;top:2px;left:2px;box-shadow:0 1px 2px #1018284d}.switch.on .knob{left:17px}.muted{color:var(--muted)}.faint{color:var(--faint)}.row{align-items:center;gap:10px;display:flex}.spread{justify-content:space-between;align-items:center;gap:12px;display:flex}.stack{flex-direction:column;display:flex}.chip{font-family:var(--mono);background:var(--raised);border:1px solid var(--border);border-radius:var(--radius-xs);color:var(--text);padding:1px 7px;font-size:12px}.empty{text-align:center;border:1px dashed var(--border);border-radius:var(--radius);color:var(--muted);padding:56px 20px}.empty svg{width:30px;height:30px;color:var(--faint);margin-bottom:12px}.empty .title{color:var(--text);margin-bottom:6px;font-weight:600}.notice{border-radius:var(--radius-sm);align-items:center;gap:8px;margin-bottom:14px;padding:9px 12px;font-size:13px;display:flex}.notice svg{flex-shrink:0;width:15px;height:15px}.notice-ok{background:var(--green-soft);color:var(--green)}.notice-err{background:var(--red-soft);color:var(--red)}.h-section{color:var(--text);align-items:center;gap:8px;margin:30px 0 12px;font-size:13px;font-weight:600;display:flex}.h-section .count{color:var(--muted);font-weight:500;font-family:var(--mono);font-size:12px}.spin{border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;width:14px;height:14px;animation:.7s linear infinite spin;display:inline-block}@keyframes spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}}.modal-overlay{-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);z-index:50;background:var(--lightningcss-light,#11131c73)var(--lightningcss-dark,#0009);justify-content:center;align-items:flex-start;padding:8vh 16px;display:flex;position:fixed;inset:0}.modal-card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);width:100%;max-width:520px;box-shadow:var(--shadow);max-height:84vh;padding:20px;overflow-y:auto}.modal-head{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.modal-head h3{font-size:16px}.list-row{text-align:left;border-radius:var(--radius-sm);border:1px solid var(--border);background:var(--raised);cursor:pointer;width:100%;color:var(--text);font:inherit;justify-content:space-between;align-items:center;gap:10px;padding:11px 13px;transition:background .12s,border-color .12s;display:flex}.list-row:hover{background:var(--raised-hover);border-color:var(--accent-ring)}.list-row .title{font-size:14px;font-weight:600}.list-row .sub{color:var(--muted);margin-top:2px;font-size:12px}.prov-card{justify-content:space-between;align-items:flex-start;gap:12px;padding:15px 16px;display:flex}.link-btn{color:var(--accent-hover);font:inherit;cursor:pointer;background:0 0;border:none;padding:6px 2px;font-size:13px;text-decoration:underline}@media (width<=760px){.app{grid-template-columns:1fr}.sidebar{z-index:20;border-right:none;border-bottom:1px solid var(--border);background:var(--rail);flex-flow:wrap;align-items:center;gap:0;min-width:0;height:auto;padding:0 10px;position:sticky;top:0}.brand{flex:auto;order:1;width:auto;padding:10px 4px}.sidebar-foot{flex-direction:row;flex:none;order:2;gap:4px;margin:0;padding:0}.sidebar-foot .sidebar-link{display:none}.theme-toggle{justify-content:center;min-width:44px;min-height:44px;padding:8px}.theme-toggle .mode{display:none}.sidebar nav{overscroll-behavior-x:contain;border-top:1px solid var(--border-soft);scrollbar-width:none;flex-direction:row;flex:100%;order:3;gap:2px;min-width:0;margin:0;padding:4px 0 8px;display:flex;overflow-x:auto}.sidebar nav::-webkit-scrollbar{display:none}.nav-item{white-space:nowrap;width:auto;min-height:44px;padding:9px 14px;font-size:14px}.main-inner{padding:22px 18px 48px}.stat-row{grid-template-columns:repeat(2,minmax(0,1fr))}.tbl{min-width:460px}}
|