@bitkyc08/opencodex 0.1.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/LICENSE +21 -0
- package/README.ko.md +164 -0
- package/README.md +165 -0
- package/README.zh-CN.md +162 -0
- package/gui/README.md +73 -0
- package/gui/dist/assets/index-C1wlp1SM.css +1 -0
- package/gui/dist/assets/index-C9y3iMF1.js +9 -0
- package/gui/dist/favicon.png +0 -0
- package/gui/dist/icons.svg +24 -0
- package/gui/dist/index.html +15 -0
- package/gui/dist/logo.png +0 -0
- package/package.json +56 -0
- package/scripts/postinstall.mjs +57 -0
- package/src/adapters/anthropic.ts +306 -0
- package/src/adapters/azure.ts +31 -0
- package/src/adapters/base.ts +20 -0
- package/src/adapters/google.ts +195 -0
- package/src/adapters/image.ts +23 -0
- package/src/adapters/openai-chat.ts +265 -0
- package/src/adapters/openai-responses.ts +43 -0
- package/src/bridge.ts +296 -0
- package/src/cli.ts +183 -0
- package/src/codex-catalog.ts +318 -0
- package/src/codex-inject.ts +186 -0
- package/src/config.ts +108 -0
- package/src/index.ts +20 -0
- package/src/init.ts +163 -0
- package/src/model-cache.ts +42 -0
- package/src/oauth/anthropic.ts +151 -0
- package/src/oauth/callback-server.ts +249 -0
- package/src/oauth/index.ts +235 -0
- package/src/oauth/key-providers.ts +126 -0
- package/src/oauth/kimi.ts +160 -0
- package/src/oauth/local-token-detect.ts +71 -0
- package/src/oauth/login-cli.ts +90 -0
- package/src/oauth/pkce.ts +15 -0
- package/src/oauth/store.ts +39 -0
- package/src/oauth/types.ts +22 -0
- package/src/oauth/xai.ts +234 -0
- package/src/responses/parser.ts +402 -0
- package/src/responses/schema.ts +145 -0
- package/src/router.ts +86 -0
- package/src/server.ts +522 -0
- package/src/service.ts +130 -0
- package/src/star-prompt.ts +50 -0
- package/src/types.ts +228 -0
- package/src/update.ts +64 -0
- package/src/vision/describe.ts +98 -0
- package/src/vision/index.ts +141 -0
- package/src/web-search/executor.ts +75 -0
- package/src/web-search/format-result.ts +45 -0
- package/src/web-search/index.ts +62 -0
- package/src/web-search/loop.ts +188 -0
- package/src/web-search/parse.ts +128 -0
- package/src/web-search/synthetic-tool.ts +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 opencodex contributors
|
|
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.ko.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
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)를 위한 범용 프로바이더 proxy — Codex CLI, App, SDK에서 어떤 LLM이든 사용하세요.**
|
|
11
|
+
|
|
12
|
+
[English](README.md) · **한국어** · [简体中文](README.zh-CN.md)
|
|
13
|
+
|
|
14
|
+
📖 **[전체 문서 →](https://lidge-jun.github.io/opencodex/ko/)**
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="assets/dashboard.png" alt="opencodex 대시보드 — 프록시 상태·프로바이더·모델을 보여주는 다크 컨트롤 패널" width="820">
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
Codex는 오직 Responses API(`/v1/responses`)만 사용합니다. opencodex는 Codex와 여러분의 LLM
|
|
23
|
+
프로바이더 사이에 위치하여, 프로토콜을 실시간으로 변환합니다 — streaming, tool 호출, reasoning, 이미지까지
|
|
24
|
+
모두 포함해서 — 양방향으로 동작합니다.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Codex CLI / App / SDK ──/v1/responses──▶ opencodex ──▶ Any provider
|
|
28
|
+
│
|
|
29
|
+
Anthropic · Google · xAI · Kimi · Ollama Cloud · Groq
|
|
30
|
+
OpenRouter · Azure · DeepSeek · GLM · …and OpenAI itself
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 빠른 시작
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Install
|
|
37
|
+
npm install -g @bitkyc08/opencodex # or: bun install -g @bitkyc08/opencodex
|
|
38
|
+
|
|
39
|
+
# Interactive setup (writes config + injects into Codex)
|
|
40
|
+
ocx init
|
|
41
|
+
|
|
42
|
+
# Start the proxy
|
|
43
|
+
ocx start
|
|
44
|
+
|
|
45
|
+
# Use Codex normally — it now routes through opencodex
|
|
46
|
+
codex "Write a hello world in Rust"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
<details>
|
|
50
|
+
<summary><b><a href="https://bun.sh">bun</a>이 없으신가요?</b> — 먼저 설치하세요 (opencodex는 bun에서 실행됩니다)</summary>
|
|
51
|
+
|
|
52
|
+
<br/>
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# macOS / Linux / WSL
|
|
56
|
+
curl -fsSL https://bun.sh/install | bash
|
|
57
|
+
|
|
58
|
+
# Windows (PowerShell)
|
|
59
|
+
powershell -c "irm bun.sh/install.ps1 | iex"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
설치 후 `npm install -g @bitkyc08/opencodex`를 다시 실행하세요. (`ocx` 바이너리는 bun 기반이라 bun이 `PATH`에 있어야 합니다.)
|
|
63
|
+
|
|
64
|
+
</details>
|
|
65
|
+
|
|
66
|
+
`provider/model` 형식으로 라우팅된 특정 모델을 지정할 수 있습니다:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
codex -m "anthropic/claude-opus-4-8" "Explain this stack trace"
|
|
70
|
+
codex -m "ollama-cloud/glm-5.2" "Write a SQL migration"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 주요 기능
|
|
74
|
+
|
|
75
|
+
- **다섯 가지 adapter**로 Anthropic Messages, Google Gemini, Azure, OpenAI Responses passthrough,
|
|
76
|
+
그리고 **모든 OpenAI 호환 Chat Completions** 엔드포인트를 지원합니다.
|
|
77
|
+
- **OAuth, API 키, 또는 ChatGPT forward.** xAI / Anthropic / Kimi 계정으로 로그인하거나(토큰은
|
|
78
|
+
자동 갱신됩니다), `codex login`을 forward 하거나, 키를 붙여넣으세요(`${ENV_VARS}` 지원). 18개 프로바이더의
|
|
79
|
+
API 키 카탈로그(**Ollama Cloud** 포함)가 기본 내장되어 있습니다.
|
|
80
|
+
- **Codex에 바로 통합.** `~/.codex/config.toml`에 `[model_providers.opencodex]` 테이블을 주입하고
|
|
81
|
+
라우팅된 모델을 Codex의 카탈로그와 서브에이전트 선택기에 병합합니다 — 완전히 되돌릴 수 있습니다.
|
|
82
|
+
- **Sidecar.** OpenAI가 아닌 모델에도 ChatGPT 로그인을 통한 `gpt-5.4-mini`를 사용해 실제 **웹 검색**과
|
|
83
|
+
**이미지 이해** 기능을 제공합니다.
|
|
84
|
+
- 프로바이더, OAuth 로그인, 모델 선택, 요청 로그를 위한 **웹 대시보드**.
|
|
85
|
+
|
|
86
|
+
## 프로바이더 및 adapter
|
|
87
|
+
|
|
88
|
+
| Provider | Adapter | Auth |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| OpenAI (ChatGPT login) | `openai-responses` | forward (키 불필요) |
|
|
91
|
+
| OpenAI (API key) | `openai-responses` | key |
|
|
92
|
+
| Anthropic Claude | `anthropic` | oauth / key |
|
|
93
|
+
| xAI Grok | `openai-chat` | oauth / key |
|
|
94
|
+
| Kimi (Moonshot) | `openai-chat` | oauth / key |
|
|
95
|
+
| Google Gemini | `google` | key |
|
|
96
|
+
| Azure OpenAI | `azure` | key |
|
|
97
|
+
| Ollama Cloud + 17개 프로바이더 카탈로그 | `openai-chat` | key |
|
|
98
|
+
| Ollama / vLLM / LM Studio (로컬) | `openai-chat` | key (보통 비워둠) |
|
|
99
|
+
| 모든 OpenAI 호환 엔드포인트 | `openai-chat` | key |
|
|
100
|
+
|
|
101
|
+
## CLI
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
ocx init # interactive setup
|
|
105
|
+
ocx start [--port 10100] # start the proxy
|
|
106
|
+
ocx stop # stop + restore native Codex
|
|
107
|
+
ocx restore # restore without stopping (alias: ocx eject)
|
|
108
|
+
ocx sync # refresh models + re-inject into Codex
|
|
109
|
+
ocx status # is the proxy running?
|
|
110
|
+
ocx login <xai|anthropic|kimi> # OAuth login
|
|
111
|
+
ocx logout <provider> # remove a stored login
|
|
112
|
+
ocx gui # open the web dashboard
|
|
113
|
+
ocx service <install|start|stop|status|uninstall> # run as a background service
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 설정
|
|
117
|
+
|
|
118
|
+
설정은 `~/.opencodex/config.json`에 저장됩니다. 최소 예시:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"port": 10100,
|
|
123
|
+
"defaultProvider": "anthropic",
|
|
124
|
+
"providers": {
|
|
125
|
+
"anthropic": {
|
|
126
|
+
"adapter": "anthropic",
|
|
127
|
+
"baseUrl": "https://api.anthropic.com",
|
|
128
|
+
"authMode": "oauth",
|
|
129
|
+
"defaultModel": "claude-sonnet-4-6"
|
|
130
|
+
},
|
|
131
|
+
"ollama-cloud": {
|
|
132
|
+
"adapter": "openai-chat",
|
|
133
|
+
"baseUrl": "https://ollama.com/v1",
|
|
134
|
+
"apiKey": "${OLLAMA_API_KEY}",
|
|
135
|
+
"defaultModel": "glm-5.2"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
모든 필드에 대한 자세한 내용은 **[설정 레퍼런스](https://lidge-jun.github.io/opencodex/ko/reference/configuration/)**
|
|
142
|
+
를 참고하세요.
|
|
143
|
+
|
|
144
|
+
## 문서
|
|
145
|
+
|
|
146
|
+
전체 개발자 문서 — 아키텍처, 모든 adapter, 요청 라이프사이클, sidecar,
|
|
147
|
+
Codex 통합, CLI/설정 레퍼런스 — 는 [`docs-site/`](./docs-site) 아래의 Astro 사이트이며
|
|
148
|
+
**[lidge-jun.github.io/opencodex](https://lidge-jun.github.io/opencodex/ko/)**에 게시됩니다.
|
|
149
|
+
|
|
150
|
+
## 개발
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
git clone https://github.com/lidge-jun/opencodex.git
|
|
154
|
+
cd opencodex
|
|
155
|
+
bun install
|
|
156
|
+
bun run dev # start the proxy in dev mode
|
|
157
|
+
bun x tsc --noEmit # typecheck
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**[기여하기](https://lidge-jun.github.io/opencodex/ko/contributing/)**를 참고하세요.
|
|
161
|
+
|
|
162
|
+
## 라이선스
|
|
163
|
+
|
|
164
|
+
MIT
|
package/README.md
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
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
|
+
**Universal provider proxy for [OpenAI Codex](https://openai.com/codex) — use any LLM with Codex CLI, App, and SDK.**
|
|
11
|
+
|
|
12
|
+
[English](README.md) · [한국어](README.ko.md) · [简体中文](README.zh-CN.md)
|
|
13
|
+
|
|
14
|
+
📖 **[Full documentation →](https://lidge-jun.github.io/opencodex/)**
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="assets/dashboard.png" alt="opencodex dashboard — a dark provider control panel showing live proxy status, routed providers, and available models" width="820">
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
Codex only speaks the Responses API (`/v1/responses`). opencodex sits between Codex and your LLM
|
|
23
|
+
provider, translating the protocol on the fly — streaming, tool calls, reasoning, and images included
|
|
24
|
+
— in both directions.
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Codex CLI / App / SDK ──/v1/responses──▶ opencodex ──▶ Any provider
|
|
28
|
+
│
|
|
29
|
+
Anthropic · Google · xAI · Kimi · Ollama Cloud · Groq
|
|
30
|
+
OpenRouter · Azure · DeepSeek · GLM · …and OpenAI itself
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Install
|
|
37
|
+
npm install -g @bitkyc08/opencodex # or: bun install -g @bitkyc08/opencodex
|
|
38
|
+
|
|
39
|
+
# Interactive setup (writes config + injects into Codex)
|
|
40
|
+
ocx init
|
|
41
|
+
|
|
42
|
+
# Start the proxy
|
|
43
|
+
ocx start
|
|
44
|
+
|
|
45
|
+
# Use Codex normally — it now routes through opencodex
|
|
46
|
+
codex "Write a hello world in Rust"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
<details>
|
|
50
|
+
<summary><b>Don't have <a href="https://bun.sh">bun</a>?</b> — install it first (opencodex runs on bun)</summary>
|
|
51
|
+
|
|
52
|
+
<br/>
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# macOS / Linux / WSL
|
|
56
|
+
curl -fsSL https://bun.sh/install | bash
|
|
57
|
+
|
|
58
|
+
# Windows (PowerShell)
|
|
59
|
+
powershell -c "irm bun.sh/install.ps1 | iex"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Then re-run `npm install -g @bitkyc08/opencodex`. (The `ocx` binary is bun-native, so bun must be on your `PATH`.)
|
|
63
|
+
|
|
64
|
+
</details>
|
|
65
|
+
|
|
66
|
+
Target a specific routed model with the `provider/model` form:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
codex -m "anthropic/claude-opus-4-8" "Explain this stack trace"
|
|
70
|
+
codex -m "ollama-cloud/glm-5.2" "Write a SQL migration"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Highlights
|
|
74
|
+
|
|
75
|
+
- **Five adapters** cover Anthropic Messages, Google Gemini, Azure, the OpenAI Responses passthrough,
|
|
76
|
+
and **every OpenAI-compatible Chat Completions** endpoint.
|
|
77
|
+
- **OAuth, API key, or ChatGPT forward.** Log in with your xAI / Anthropic / Kimi account (tokens
|
|
78
|
+
auto-refresh), forward your `codex login`, or paste a key (`${ENV_VARS}` supported). An 18-provider
|
|
79
|
+
API-key catalog (incl. **Ollama Cloud**) is built in.
|
|
80
|
+
- **Drops into Codex.** Injects a `[model_providers.opencodex]` table into `~/.codex/config.toml` and
|
|
81
|
+
merges routed models into Codex's catalog and subagent picker — fully reversible.
|
|
82
|
+
- **Sidecars.** Give non-OpenAI models real **web search** and **image understanding** via a
|
|
83
|
+
`gpt-5.4-mini` over your ChatGPT login.
|
|
84
|
+
- **Web dashboard** for providers, OAuth login, model selection, and request logs.
|
|
85
|
+
|
|
86
|
+
## Providers & adapters
|
|
87
|
+
|
|
88
|
+
| Provider | Adapter | Auth |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| OpenAI (ChatGPT login) | `openai-responses` | forward (no key) |
|
|
91
|
+
| OpenAI (API key) | `openai-responses` | key |
|
|
92
|
+
| Anthropic Claude | `anthropic` | oauth / key |
|
|
93
|
+
| xAI Grok | `openai-chat` | oauth / key |
|
|
94
|
+
| Kimi (Moonshot) | `openai-chat` | oauth / key |
|
|
95
|
+
| Google Gemini | `google` | key |
|
|
96
|
+
| Azure OpenAI | `azure` | key |
|
|
97
|
+
| Ollama Cloud + 17-provider catalog | `openai-chat` | key |
|
|
98
|
+
| Ollama / vLLM / LM Studio (local) | `openai-chat` | key (usually blank) |
|
|
99
|
+
| Any OpenAI-compatible endpoint | `openai-chat` | key |
|
|
100
|
+
|
|
101
|
+
## CLI
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
ocx init # interactive setup
|
|
105
|
+
ocx start [--port 10100] # start the proxy
|
|
106
|
+
ocx stop # stop + restore native Codex
|
|
107
|
+
ocx restore # restore without stopping (alias: ocx eject)
|
|
108
|
+
ocx sync # refresh models + re-inject into Codex
|
|
109
|
+
ocx status # is the proxy running?
|
|
110
|
+
ocx login <xai|anthropic|kimi> # OAuth login
|
|
111
|
+
ocx logout <provider> # remove a stored login
|
|
112
|
+
ocx gui # open the web dashboard
|
|
113
|
+
ocx service <install|start|stop|status|uninstall> # run as a background service
|
|
114
|
+
ocx update # update opencodex to the latest published version
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Configuration
|
|
118
|
+
|
|
119
|
+
Config lives at `~/.opencodex/config.json`. Minimal example:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"port": 10100,
|
|
124
|
+
"defaultProvider": "anthropic",
|
|
125
|
+
"providers": {
|
|
126
|
+
"anthropic": {
|
|
127
|
+
"adapter": "anthropic",
|
|
128
|
+
"baseUrl": "https://api.anthropic.com",
|
|
129
|
+
"authMode": "oauth",
|
|
130
|
+
"defaultModel": "claude-sonnet-4-6"
|
|
131
|
+
},
|
|
132
|
+
"ollama-cloud": {
|
|
133
|
+
"adapter": "openai-chat",
|
|
134
|
+
"baseUrl": "https://ollama.com/v1",
|
|
135
|
+
"apiKey": "${OLLAMA_API_KEY}",
|
|
136
|
+
"defaultModel": "glm-5.2"
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
See the **[Configuration reference](https://lidge-jun.github.io/opencodex/reference/configuration/)**
|
|
143
|
+
for every field.
|
|
144
|
+
|
|
145
|
+
## Documentation
|
|
146
|
+
|
|
147
|
+
The full developer documentation — architecture, every adapter, the request lifecycle, the sidecars,
|
|
148
|
+
Codex integration, and the CLI/config reference — is an Astro site under [`docs-site/`](./docs-site)
|
|
149
|
+
and published to **[lidge-jun.github.io/opencodex](https://lidge-jun.github.io/opencodex/)**.
|
|
150
|
+
|
|
151
|
+
## Development
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
git clone https://github.com/lidge-jun/opencodex.git
|
|
155
|
+
cd opencodex
|
|
156
|
+
bun install
|
|
157
|
+
bun run dev # start the proxy in dev mode
|
|
158
|
+
bun x tsc --noEmit # typecheck
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
See **[Contributing](https://lidge-jun.github.io/opencodex/contributing/)**.
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
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/)**
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="assets/dashboard.png" alt="opencodex 控制台 —— 展示代理状态、provider 与可用模型的深色控制面板" width="820">
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
Codex 只能使用 Responses API(`/v1/responses`)。opencodex 位于 Codex 与你的 LLM
|
|
23
|
+
provider 之间,实时翻译两者之间的协议 —— 包括 streaming、工具调用、推理(reasoning)和图像
|
|
24
|
+
—— 并且是双向的。
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
Codex CLI / App / SDK ──/v1/responses──▶ opencodex ──▶ Any provider
|
|
28
|
+
│
|
|
29
|
+
Anthropic · Google · xAI · Kimi · Ollama Cloud · Groq
|
|
30
|
+
OpenRouter · Azure · DeepSeek · GLM · …and OpenAI itself
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 快速开始
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Install
|
|
37
|
+
npm install -g @bitkyc08/opencodex # or: bun install -g @bitkyc08/opencodex
|
|
38
|
+
|
|
39
|
+
# Interactive setup (writes config + injects into Codex)
|
|
40
|
+
ocx init
|
|
41
|
+
|
|
42
|
+
# Start the proxy
|
|
43
|
+
ocx start
|
|
44
|
+
|
|
45
|
+
# Use Codex normally — it now routes through opencodex
|
|
46
|
+
codex "Write a hello world in Rust"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
<details>
|
|
50
|
+
<summary><b>没有 <a href="https://bun.sh">bun</a>?</b> —— 先安装它(opencodex 运行在 bun 上)</summary>
|
|
51
|
+
|
|
52
|
+
<br/>
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# macOS / Linux / WSL
|
|
56
|
+
curl -fsSL https://bun.sh/install | bash
|
|
57
|
+
|
|
58
|
+
# Windows (PowerShell)
|
|
59
|
+
powershell -c "irm bun.sh/install.ps1 | iex"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
然后重新运行 `npm install -g @bitkyc08/opencodex`。(`ocx` 可执行文件是 bun 原生的,因此 bun 必须在你的 `PATH` 中。)
|
|
63
|
+
|
|
64
|
+
</details>
|
|
65
|
+
|
|
66
|
+
使用 `provider/model` 形式指定一个具体的已路由模型:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
codex -m "anthropic/claude-opus-4-8" "Explain this stack trace"
|
|
70
|
+
codex -m "ollama-cloud/glm-5.2" "Write a SQL migration"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 亮点
|
|
74
|
+
|
|
75
|
+
- **五种 adapter**,覆盖 Anthropic Messages、Google Gemini、Azure、OpenAI Responses 直通(passthrough),
|
|
76
|
+
以及**所有 OpenAI 兼容的 Chat Completions** 端点。
|
|
77
|
+
- **OAuth、API key 或 ChatGPT 转发(forward)。** 用你的 xAI / Anthropic / Kimi 账户登录(token
|
|
78
|
+
自动刷新)、转发你的 `codex login`,或直接粘贴一个 key(支持 `${ENV_VARS}`)。内置一份 18 家 provider 的
|
|
79
|
+
API-key 目录(含 **Ollama Cloud**)。
|
|
80
|
+
- **无缝接入 Codex。** 向 `~/.codex/config.toml` 注入一个 `[model_providers.opencodex]` 表,并
|
|
81
|
+
将已路由的模型合并进 Codex 的目录和 subagent 选择器 —— 完全可逆。
|
|
82
|
+
- **Sidecars。** 通过基于你 ChatGPT 登录的 `gpt-5.4-mini`,为非 OpenAI 模型提供真正的**网页搜索**和**图像理解**能力。
|
|
83
|
+
- **Web 仪表盘**,用于管理 provider、OAuth 登录、模型选择和请求日志。
|
|
84
|
+
|
|
85
|
+
## Providers 与 adapters
|
|
86
|
+
|
|
87
|
+
| Provider | Adapter | Auth |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| OpenAI(ChatGPT 登录) | `openai-responses` | 转发(无需 key) |
|
|
90
|
+
| OpenAI(API key) | `openai-responses` | key |
|
|
91
|
+
| Anthropic Claude | `anthropic` | oauth / key |
|
|
92
|
+
| xAI Grok | `openai-chat` | oauth / key |
|
|
93
|
+
| Kimi(Moonshot) | `openai-chat` | oauth / key |
|
|
94
|
+
| Google Gemini | `google` | key |
|
|
95
|
+
| Azure OpenAI | `azure` | key |
|
|
96
|
+
| Ollama Cloud + 17 家 provider 目录 | `openai-chat` | key |
|
|
97
|
+
| Ollama / vLLM / LM Studio(本地) | `openai-chat` | key(通常留空) |
|
|
98
|
+
| 任意 OpenAI 兼容端点 | `openai-chat` | key |
|
|
99
|
+
|
|
100
|
+
## CLI
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
ocx init # interactive setup
|
|
104
|
+
ocx start [--port 10100] # start the proxy
|
|
105
|
+
ocx stop # stop + restore native Codex
|
|
106
|
+
ocx restore # restore without stopping (alias: ocx eject)
|
|
107
|
+
ocx sync # refresh models + re-inject into Codex
|
|
108
|
+
ocx status # is the proxy running?
|
|
109
|
+
ocx login <xai|anthropic|kimi> # OAuth login
|
|
110
|
+
ocx logout <provider> # remove a stored login
|
|
111
|
+
ocx gui # open the web dashboard
|
|
112
|
+
ocx service <install|start|stop|status|uninstall> # run as a background service
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 配置
|
|
116
|
+
|
|
117
|
+
配置文件位于 `~/.opencodex/config.json`。最小示例:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"port": 10100,
|
|
122
|
+
"defaultProvider": "anthropic",
|
|
123
|
+
"providers": {
|
|
124
|
+
"anthropic": {
|
|
125
|
+
"adapter": "anthropic",
|
|
126
|
+
"baseUrl": "https://api.anthropic.com",
|
|
127
|
+
"authMode": "oauth",
|
|
128
|
+
"defaultModel": "claude-sonnet-4-6"
|
|
129
|
+
},
|
|
130
|
+
"ollama-cloud": {
|
|
131
|
+
"adapter": "openai-chat",
|
|
132
|
+
"baseUrl": "https://ollama.com/v1",
|
|
133
|
+
"apiKey": "${OLLAMA_API_KEY}",
|
|
134
|
+
"defaultModel": "glm-5.2"
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
每个字段的说明请参阅 **[配置参考](https://lidge-jun.github.io/opencodex/zh-cn/reference/configuration/)**。
|
|
141
|
+
|
|
142
|
+
## 文档
|
|
143
|
+
|
|
144
|
+
完整的开发者文档 —— 架构、每个 adapter、请求生命周期、sidecars、
|
|
145
|
+
Codex 集成,以及 CLI/配置参考 —— 是位于 [`docs-site/`](./docs-site) 下的一个 Astro 站点,
|
|
146
|
+
并发布于 **[lidge-jun.github.io/opencodex](https://lidge-jun.github.io/opencodex/zh-cn/)**。
|
|
147
|
+
|
|
148
|
+
## 开发
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
git clone https://github.com/lidge-jun/opencodex.git
|
|
152
|
+
cd opencodex
|
|
153
|
+
bun install
|
|
154
|
+
bun run dev # start the proxy in dev mode
|
|
155
|
+
bun x tsc --noEmit # typecheck
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
请参阅 **[贡献指南](https://lidge-jun.github.io/opencodex/zh-cn/contributing/)**。
|
|
159
|
+
|
|
160
|
+
## 许可证
|
|
161
|
+
|
|
162
|
+
MIT
|
package/gui/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# React + TypeScript + Vite
|
|
2
|
+
|
|
3
|
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
4
|
+
|
|
5
|
+
Currently, two official plugins are available:
|
|
6
|
+
|
|
7
|
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
|
|
8
|
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
|
|
9
|
+
|
|
10
|
+
## React Compiler
|
|
11
|
+
|
|
12
|
+
The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
|
|
13
|
+
|
|
14
|
+
## Expanding the ESLint configuration
|
|
15
|
+
|
|
16
|
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
17
|
+
|
|
18
|
+
```js
|
|
19
|
+
export default defineConfig([
|
|
20
|
+
globalIgnores(['dist']),
|
|
21
|
+
{
|
|
22
|
+
files: ['**/*.{ts,tsx}'],
|
|
23
|
+
extends: [
|
|
24
|
+
// Other configs...
|
|
25
|
+
|
|
26
|
+
// Remove tseslint.configs.recommended and replace with this
|
|
27
|
+
tseslint.configs.recommendedTypeChecked,
|
|
28
|
+
// Alternatively, use this for stricter rules
|
|
29
|
+
tseslint.configs.strictTypeChecked,
|
|
30
|
+
// Optionally, add this for stylistic rules
|
|
31
|
+
tseslint.configs.stylisticTypeChecked,
|
|
32
|
+
|
|
33
|
+
// Other configs...
|
|
34
|
+
],
|
|
35
|
+
languageOptions: {
|
|
36
|
+
parserOptions: {
|
|
37
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
38
|
+
tsconfigRootDir: import.meta.dirname,
|
|
39
|
+
},
|
|
40
|
+
// other options...
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
// eslint.config.js
|
|
50
|
+
import reactX from 'eslint-plugin-react-x'
|
|
51
|
+
import reactDom from 'eslint-plugin-react-dom'
|
|
52
|
+
|
|
53
|
+
export default defineConfig([
|
|
54
|
+
globalIgnores(['dist']),
|
|
55
|
+
{
|
|
56
|
+
files: ['**/*.{ts,tsx}'],
|
|
57
|
+
extends: [
|
|
58
|
+
// Other configs...
|
|
59
|
+
// Enable lint rules for React
|
|
60
|
+
reactX.configs['recommended-typescript'],
|
|
61
|
+
// Enable lint rules for React DOM
|
|
62
|
+
reactDom.configs.recommended,
|
|
63
|
+
],
|
|
64
|
+
languageOptions: {
|
|
65
|
+
parserOptions: {
|
|
66
|
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
67
|
+
tsconfigRootDir: import.meta.dirname,
|
|
68
|
+
},
|
|
69
|
+
// other options...
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
])
|
|
73
|
+
```
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--bg:#0b0b0f;--surface:#14141a;--raised:#1c1c25;--raised-hover:#232330;--border:#2a2a35;--border-soft:#20202a;--text:#e9e9ee;--muted:#9a9aa6;--faint:#6a6a76;--accent:#7c5cff;--accent-hover:#9077ff;--accent-ink:#fff;--accent-soft:#7c5cff24;--accent-ring:#7c5cff73;--green:#34d399;--green-soft:#34d39921;--red:#f87171;--red-soft:#f8717121;--amber:#fbbf24;--amber-soft:#fbbf2421;--radius:12px;--radius-sm:8px;--radius-xs:6px;--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 #00000080, 0 12px 32px #00000047;--shadow-sm:0 1px 2px #0006;--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;background-image:radial-gradient(1200px 600px at 18% -10%,#7c5cff14,#0000 60%);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)}::-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:#353542}: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-soft);background:linear-gradient(#ffffff04,#0000);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 img{width:26px;height:26px}.brand .name{letter-spacing:-.02em;font-size:15px;font-weight:700}.brand .ver{font-family:var(--mono);color:var(--muted);background:var(--raised);border:1px solid var(--border);border-radius:99px;padding:1px 6px;font-size:10px}.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{margin-top:auto;padding-top:12px}.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}.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:15px 16px}.stat .label{color:var(--muted);align-items:center;gap:6px;margin-bottom:7px;font-size:12px;display:flex}.stat .label svg{width:14px;height:14px}.stat .value{letter-spacing:-.02em;font-size:23px;font-weight:700;line-height:1.1}.stat .value.mono{font-family:var(--mono);font-size:19px}.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:#ffffff05}.tbl .num{text-align:right;font-family:var(--mono)}.tbl-wrap{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden}.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(--border);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}.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:#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:#34343f}.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{border-right:none;border-bottom:1px solid var(--border-soft);flex-flow:wrap;align-items:center;height:auto;position:static}.brand{width:100%;padding:6px 8px}.nav-item{width:auto}.sidebar-foot{margin:0;padding:0}.main-inner{padding:22px 18px 48px}.stat-row{grid-template-columns:repeat(2,1fr)}}
|