Skip to content

Commit 5cd2e26

Browse files
committed
Merge branch 'main' into preview
# Conflicts: # package.json
2 parents 74adc60 + e43f590 commit 5cd2e26

85 files changed

Lines changed: 4626 additions & 377 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.ko.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ ocx sync # 모델 갱신 + Codex에 재주입
247247
ocx status # 프록시 실행 중인지 확인
248248
ocx login <xai|anthropic|kimi> # OAuth 로그인
249249
ocx logout <provider> # 저장된 로그인 정보 삭제
250+
ocx account <list|current|use> # 계정/API key pool 조회·전환 (마스킹; refresh/auto-switch/remove/add-key 포함)
250251
ocx gui # 웹 대시보드 열기
251252
ocx claude [args...] # 프록시에 연결된 Claude Code 실행 (모델 디스커버리 켜짐)
252253
ocx codex-shim install # codex 실행 시 `ocx ensure` 실행

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,7 @@ ocx codex-shim install # run `ocx ensure` whenever `codex` is launched
280280
ocx status # is the proxy running?
281281
ocx login <provider> # OAuth login (xai, anthropic, kimi, cursor, ...)
282282
ocx logout <provider> # remove a stored login
283+
ocx account <list|current|use> # list/switch accounts & API-key pools (masked; also refresh/auto-switch/remove/add-key)
283284
ocx gui # open the web dashboard
284285
ocx claude [args...] # launch Claude Code wired to the proxy (model discovery on)
285286
ocx service [install|start|stop|status|uninstall] # install/update/start background service

README.zh-CN.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ ocx sync # 刷新模型列表 + 重新注入 Codex
240240
ocx status # 查看代理是否在运行
241241
ocx login <xai|anthropic|kimi> # OAuth 登录
242242
ocx logout <provider> # 移除已保存的登录
243+
ocx account <list|current|use> # 查看/切换账号与 API-key pool(脱敏;含 refresh/auto-switch/remove/add-key)
243244
ocx gui # 打开 Web 仪表盘
244245
ocx claude [args...] # 启动接入代理的 Claude Code(模型发现已开启)
245246
ocx codex-shim install # 运行 codex 时自动启动代理

bin/ocx.mjs

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,37 @@ function runNpmSelfUpdate() {
124124
return [launcher, "service", "install"];
125125
}
126126

127+
// Capture listen target before stop clears runtime-port.json (mirrors GUI/CLI update worker).
128+
// Do not treat a live runtime port of 10100 as "missing" — track whether the read succeeded.
129+
let bakePort = 10100;
130+
let sawRuntimePort = false;
131+
try {
132+
const rt = JSON.parse(readFileSync(join(configDir(), "runtime-port.json"), "utf8"));
133+
if (Number.isFinite(rt?.port) && rt.port > 0 && rt.port <= 65535) {
134+
// Only trust runtime when its pid still looks alive (stale crash leftovers fall back to config).
135+
const rtPid = Number(rt?.pid);
136+
let runtimeLive = false;
137+
if (Number.isSafeInteger(rtPid) && rtPid > 0) {
138+
try {
139+
process.kill(rtPid, 0);
140+
runtimeLive = true;
141+
} catch (e) {
142+
if (e && typeof e === "object" && "code" in e && e.code === "EPERM") runtimeLive = true;
143+
}
144+
}
145+
if (runtimeLive) {
146+
bakePort = Math.trunc(rt.port);
147+
sawRuntimePort = true;
148+
}
149+
}
150+
} catch { /* fall through to config */ }
151+
if (!sawRuntimePort) {
152+
try {
153+
const cfg = JSON.parse(readFileSync(join(configDir(), "config.json"), "utf8"));
154+
if (Number.isFinite(cfg?.port) && cfg.port > 0 && cfg.port <= 65535) bakePort = Math.trunc(cfg.port);
155+
} catch { /* keep default */ }
156+
}
157+
127158
// Never replace package files under a live proxy — stop it first (full `ocx stop`
128159
// semantics: graceful drain, service stop, native Codex restore). Gate on the service
129160
// and the runtime-port record too: a service-managed or orphaned proxy can be live
@@ -163,9 +194,16 @@ function runNpmSelfUpdate() {
163194
// launcher so the new files write the baked paths and the service restarts.
164195
if (serviceWasInstalled) {
165196
console.log("Reinstalling the background service with the updated files...");
166-
const svcArgs = serviceReinstallArgs();
167-
const svc = spawnSync(process.execPath, svcArgs, { stdio: "inherit", windowsHide: true });
168-
if (svc.status !== 0) console.warn("opencodex: service refresh failed — run 'ocx service install' manually.");
197+
const prevBake = process.env.OCX_BAKE_PORT;
198+
process.env.OCX_BAKE_PORT = String(bakePort);
199+
try {
200+
const svcArgs = serviceReinstallArgs();
201+
const svc = spawnSync(process.execPath, svcArgs, { stdio: "inherit", windowsHide: true });
202+
if (svc.status !== 0) console.warn("opencodex: service refresh failed — run 'ocx service install' manually.");
203+
} finally {
204+
if (prevBake === undefined) delete process.env.OCX_BAKE_PORT;
205+
else process.env.OCX_BAKE_PORT = prevBake;
206+
}
169207
} else {
170208
console.log("Restart the proxy: ocx start");
171209
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Unanswered issue triage and fixes
2+
3+
This work validates the unanswered issues open on 2026-07-21 before changing code.
4+
5+
## Scope
6+
7+
- #190: Cursor rejects an aggregate Codex tool catalog with `resource_exhausted`.
8+
- #181: provider deletion hides the server's actionable default-provider guard.
9+
- #198: usage/log cost estimates can be mistaken for actual billing.
10+
- #180 and #196: verify fixes already absorbed into `dev` instead of duplicating them.
11+
- #177 and #178: verify whether the vendors expose a model-inference contract that fits an OpenCodex provider.
12+
13+
[Decision Log]
14+
- 목적과 의도: Validate each report against current `dev`, fix confirmed defects, and leave evidence-backed issue replies.
15+
- 기존 구현 및 제약 조건: Cursor registers client tools through a protobuf `McpTools` field with observed count and byte ceilings. The same filtered catalog must drive protobuf advertising and client tool-call recognition. GUI-visible text must be translated in every locale.
16+
- 검토한 주요 대안: Import the closed #192 heuristic patch; drop whole namespaces by count; stub schemas and move them into prompt text; measure actual serialized definitions and prioritize recoverable tools.
17+
- 선택한 방식: Measure the real serialized `McpTools` payload, enforce one hard count cap, prioritize explicit and `tool_search`-loaded tools, and skip oversized candidates while continuing to fill the catalog. Surface server delete errors directly and label cost values as non-billing list-price equivalents.
18+
- 다른 대안 대신 이 방식을 선택한 이유: The closed patch undercounted protobuf bytes, could exceed the count cap through reserved tools, and did not reliably promote searched tools. Exact serialization removes those failure modes without inventing an incompatible schema-stubbing protocol.
19+
- 장점, 단점 및 영향: The transport stays below both observed Cursor limits and searched tools become usable on the next turn. Some low-priority tools can still be omitted in very large catalogs, so the request includes an explicit recovery note when `tool_search` is available. Serialization adds bounded setup work once per Cursor turn.
20+
21+
## Verification plan
22+
23+
- Focused parser, Cursor request-builder/tool-definition/error tests.
24+
- GUI lint, i18n lint, TypeScript build, and relevant existing tests.
25+
- Full repository pre-push gate before pushing `dev`.
26+
27+
## Result
28+
29+
- #180 was already implemented on current `dev` as the complete `ocx account` command family. Its 41-case regression matrix passes; privacy-scan fixture strings were changed from token-shaped literals without changing behavior.
30+
- #196 was already absorbed on `dev`; the registry and three Qwen 3.8 reasoning replay tests pass.
31+
- #190 now budgets the exact serialized `McpTools` protobuf to 120,000 bytes and 330 definitions, prioritizes explicit/native and tool-search-loaded definitions, and classifies residual catalog exhaustion as HTTP 400 `tool_catalog_too_large` rather than a quota rate limit.
32+
- #181 now displays the management API's structured delete error, with a translated fallback for network, empty, or malformed responses.
33+
- #198 now labels dashboard cost values as API list-price equivalents rather than charges in English, Korean, German, and Chinese, with matching public documentation.
34+
- #177 and #178 remain feature requests: Warp documents an asynchronous Oz agent-run API, while Factory documents configuring upstream model gateways for its own client. Neither public contract is an OpenAI-compatible model-inference endpoint that can be registered as an OpenCodex provider without a new adapter contract.
35+
36+
## Verification result
37+
38+
- Full repository suite: 3,296 passed, 0 failed before the fixture-only privacy repair.
39+
- Post-repair focused suite: 94 passed, 0 failed for Cursor error/catalog handling, request logging, and the #180 CLI matrix.
40+
- Root TypeScript typecheck passed.
41+
- GUI ESLint, i18n lint, and production build passed.
42+
- Privacy scan passed.
43+
- React Doctor full scan completed; it reported the repository's existing baseline (197 diagnostics, including one pre-existing `Models.tsx` state-updater error). No new diagnostic was introduced by these changes.

docs-site/src/content/docs/guides/providers.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ under `provider.apiKeyPool`, makes it active, and mirrors it to `provider.apiKey
137137
adapters continue to read the same field as before. The same dropdown can switch or remove keys; the
138138
management API is `/api/providers/keys` and returns masked keys only.
139139

140+
### Switching accounts from the terminal
141+
142+
Use `ocx account list`, `ocx account current`, and `ocx account use` to inspect or switch the same
143+
Codex, OAuth, and API-key pools without opening the dashboard. See the
144+
[CLI reference](/opencodex/reference/cli/#ocx-account-subcommand) for commands, JSON output, and
145+
new-session behavior.
146+
140147
### GPT-5.6 preview paths
141148

142149
GPT-5.6 Sol/Terra/Luna are seeded in provider fallback lists so `ocx sync` can keep the models

docs-site/src/content/docs/guides/web-dashboard.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ bun run dev:gui
3939
| **Usage / Debug** | Inspect token-usage coverage and trends, or enable opt-in provider transport and usage-extraction diagnostics. |
4040
| **Stop** | Gracefully stop the proxy and installed background service, restore native Codex, and exit (`POST /api/stop`). |
4141

42+
Cost values in **Logs** and **Usage** are API list-price equivalents calculated from reported tokens.
43+
They are not billing receipts or evidence of an actual charge; subscription usage or provider credits
44+
may apply instead.
45+
4246
## Delegation picker vs spawn routing
4347

4448
The Dashboard's **Sub-agent delegation** picker stores `injectionModel` and, optionally,

docs-site/src/content/docs/ko/guides/providers.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,12 @@ opencodex v2.7.1에는 빌트인 프리셋이 50개 들어 있습니다. 키 방
134134
`provider.apiKey`에도 반영합니다. 같은 드롭다운에서 키를 전환하거나 제거할 수 있습니다. 관리 API는
135135
`/api/providers/keys`이며 마스킹된 키만 반환합니다.
136136

137+
### 터미널에서 계정 전환하기
138+
139+
대시보드를 열지 않고도 `ocx account list`, `ocx account current`, `ocx account use`로 같은 Codex,
140+
OAuth, API-key pool을 확인하고 전환할 수 있습니다. 전체 명령, JSON 출력, 새 세션 적용 방식은
141+
[CLI 레퍼런스](/opencodex/ko/reference/cli/#ocx-account-subcommand)를 참고하세요.
142+
137143
### GPT-5.6 프리뷰 경로
138144

139145
실시간 모델 카탈로그 갱신이 늦어도 `ocx sync`에서 모델이 사라지지 않도록 GPT-5.6

docs-site/src/content/docs/ko/guides/web-dashboard.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ bun run dev:gui
3939
| **Usage / Debug** | 토큰 사용량의 측정 범위와 추이를 보거나, 선택적 프로바이더 전송/사용량 추출 진단을 켭니다. |
4040
| **Stop** | 프록시와 설치된 백그라운드 서비스를 정상 종료하고 네이티브 Codex를 복원한 뒤 끝냅니다(`POST /api/stop`). |
4141

42+
**Logs****Usage**의 비용 값은 보고된 토큰으로 계산한 API 정가 환산치입니다. 결제 영수증이나
43+
실제 청구 증거가 아니며, 구독 사용량 또는 프로바이더 크레딧이 대신 적용될 수 있습니다.
44+
4245
## 위임 선택기와 스폰 라우팅의 차이
4346

4447
Dashboard의 **Sub-agent delegation** 선택기는 `injectionModel`과 선택적인 `injectionEffort`

docs-site/src/content/docs/ko/reference/cli.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,135 @@ ocx provider show anthropic --json
192192
ocx models --provider anthropic --json
193193
```
194194

195+
### `ocx account <subcommand>`
196+
197+
실행 중인 프록시를 통해 프로바이더 계정과 API-key pool을 조회하고 전환합니다. 배포된 도움말의
198+
명령 표면은 다음과 같습니다.
199+
200+
```text
201+
Usage: ocx account <list|current|use|refresh|auto-switch|remove|add-key> ...
202+
203+
List and switch provider accounts and API-key pools (GUI parity).
204+
205+
list [provider] Codex account pool, OAuth accounts and API keys (identifiers shown masked as the API returns them).
206+
current <provider> Show the active account or key.
207+
use <provider> <id> Switch the active credential; 'main' selects the Codex App login.
208+
refresh <provider> Force-refresh Codex or provider quota reports.
209+
auto-switch <provider> <on|off|status|threshold N> Control the Codex pool threshold.
210+
remove <provider> <id> --yes Remove a stored account or key after an existence check.
211+
add-key <provider> [--label <label>] Add a key read only from piped stdin.
212+
Codex pool switches apply to new sessions; running threads keep their account.
213+
```
214+
215+
모든 하위 명령은 프록시가 실행 중이어야 하며 CLI가 기록된 런타임 포트를 자동으로 찾습니다. 성공은
216+
종료 코드 0을 반환합니다. 잘못된 사용법, 알 수 없는 프로바이더나 계정/key id, 프록시 연결 실패,
217+
API 오류는 종료 코드 1입니다. 자격 증명 필드는 management API가 반환한 그대로(API가 적용한
218+
마스킹 포함) 표시하며, 원본 API key와 OAuth token은 반환하지 않습니다. 화면 편의 값은 대시보드와
219+
같은 방식으로 CLI가 합성합니다: `main``openai` 계정 풀의 Codex App 로그인 별칭이고, 이메일이
220+
없는 OAuth 계정은 `Account N`으로 표시되며, plan/label 열은 plan → 마스킹 이메일 → label →
221+
마스킹 key 순으로 대체합니다.
222+
223+
`--json`의 계정 행은 아래 공통 형태를 사용합니다(값이 없으면 선택 필드는 생략됩니다).
224+
225+
```json
226+
{
227+
"provider": "openai",
228+
"type": "codex | oauth | api-key",
229+
"id": "__main__",
230+
"label": "plus",
231+
"email": "m***@example.com",
232+
"plan": "plus",
233+
"masked": "sk-ab****wxyz",
234+
"active": true,
235+
"needsReauth": false,
236+
"quota": null
237+
}
238+
```
239+
240+
#### `ocx account list [provider] [--json] [--all]`
241+
242+
프로바이더를 생략하면 Codex pool, OAuth 계정, 설정된 API-key pool을 모두 나열합니다. 빈
243+
프로바이더는 `--all`을 지정하지 않으면 건너뛰며, 프로바이더를 지정하면 해당 자격 증명 family만
244+
조회합니다. 일반 출력 열은 `PROVIDER TYPE ID PLAN/LABEL STATUS`이고 고정된 Codex 행에는
245+
`next session`이 표시됩니다. 저장된 Kiro 계정이 있으면 로그인 슬롯이 하나이며 다시 로그인하면 현재 계정이
246+
교체된다는 안내가 나옵니다. 결과가 비어 있어도 성공입니다. `--json`은 다음을 반환합니다.
247+
248+
```text
249+
{ accounts: AccountRow[], notes: string[] }
250+
```
251+
252+
#### `ocx account current <provider> [--json]`
253+
254+
활성 계정이나 key를 표시합니다. 수동 pin이 없는 Codex pool은 사용량이 가장 낮은 계정을 자동으로
255+
선택한다고 표시합니다. 다른 family에 활성 자격 증명이 없어도 그 상태를 알리고 종료 코드 0을
256+
반환합니다. `--json` 형태는 다음과 같습니다.
257+
258+
```text
259+
{ provider, type, activeId: string | null, autoSwitchThreshold?: number, account: AccountRow | null }
260+
```
261+
262+
#### `ocx account use <provider> <account-or-key-id|main> [--json]`
263+
264+
기존 Codex 계정, OAuth 계정 또는 API key를 선택합니다. `openai`에서 `main`은 Codex App 로그인을
265+
선택합니다. Codex 선택은 **새 세션**부터 적용되며 기존 thread는 현재 계정을 유지합니다. auto-switch
266+
threshold가 켜져 있으면 나중에 수동 pin을 덮어쓸 수 있습니다. 알 수 없는 프로바이더나 id는 종료
267+
코드 1입니다. `--json`은 다음을 반환합니다.
268+
269+
```text
270+
{ ok: true, provider, type, activeId }
271+
```
272+
273+
#### `ocx account refresh <provider> [--json]`
274+
275+
Codex pool은 `ocx account refresh openai [--json]`을 사용합니다. 계정 quota를 강제로 새로 고치고
276+
확인 가능한 주간/월간 백분율과 reset 시각을 표시합니다. quota 정보가 없으면 0%가 아니라 unknown으로
277+
표시합니다. JSON envelope은 `{ accounts: AccountRow[] }`이며 각 Codex 행에 `quota`가 들어갑니다.
278+
279+
OAuth 및 API-key 프로바이더에서는 provider quota-report endpoint를 강제로 새로 고칩니다. token
280+
재로그인이나 단순 account-list 재조회가 아닙니다. `--json`
281+
`{ provider, report: ProviderQuotaReport | null }`을 반환합니다. 지원되는 quota report가 없으면
282+
`no quota report available for <provider>`를 출력하고 종료 코드 0을 반환합니다. 알 수 없는
283+
프로바이더와 management API 오류는 종료 코드 1이며, upstream quota probe가 실패하거나 시간
284+
초과되면 대시보드의 quota 막대와 마찬가지로 null/오래된 report로 저하되어 종료 코드 0을
285+
반환합니다.
286+
287+
#### `ocx account auto-switch <provider> <on|off|status|threshold <0-100>> [--json]`
288+
289+
`openai` Codex 계정 pool만 제어합니다. `on`은 80%, `off`는 0%로 설정하고 `status`는 현재 값을
290+
읽습니다. `threshold <n>`은 0부터 100까지의 정수만 받습니다. 다른 프로바이더나 잘못된 값은 종료
291+
코드 1입니다. `--json`은 다음을 반환합니다.
292+
293+
```text
294+
{ provider, autoSwitchThreshold: number, enabled: boolean }
295+
```
296+
297+
#### `ocx account remove <provider> <id|main> --yes [--json]`
298+
299+
보호된 비대화형 삭제이므로 `--yes`가 필수입니다. 삭제 전에 id 존재 여부를 확인하며, 없는 id는
300+
DELETE를 보내지 않고 종료 코드 1을 반환합니다. 메인 Codex App 로그인은 제거할 수 없으므로
301+
`remove openai main --yes`도 거부합니다. 삭제 후 family를 다시 읽습니다. 고정된 Codex 계정을
302+
지우면 pin이 해제되어 자동 선택으로 돌아가고, OAuth는 남은 첫 계정을 활성화하거나 계정 없음으로
303+
표시하며, API-key pool은 남은 첫 key를 활성화하거나 key 없음으로 표시합니다. `--json`의 성공/실패
304+
형태는 다음과 같습니다.
305+
306+
```text
307+
{ ok: true, provider, id, removedActive: boolean, promotedActiveId: string | null }
308+
{ error: string } // stderr, exit 1
309+
```
310+
311+
#### `ocx account add-key <provider> [--label <label>] [--json]`
312+
313+
API-key 프로바이더에 key를 추가하고 활성화합니다. key는 TTY가 아닌 pipe/redirect stdin으로만
314+
읽습니다. 대화형 TTY 입력, 빈 입력, OAuth/Codex 프로바이더, API 오류는 종료 코드 1입니다. label
315+
안에 key가 들어 있어도 key를 절대 echo하지 않습니다. secret manager나 here-string을 사용하세요.
316+
317+
```bash
318+
ocx account add-key openrouter --label personal <<< "$OPENROUTER_API_KEY"
319+
security find-generic-password -w openrouter | ocx account add-key openrouter --json
320+
```
321+
322+
`--json``{ ok: true, id: string | null, label?: string }`을 반환하며 key를 포함하지 않습니다.
323+
195324
## 인증
196325

197326
### `ocx login <provider>`

0 commit comments

Comments
 (0)