@bitkyc08/opencodex 2.5.5 → 2.5.6
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 +6 -10
- package/README.md +9 -10
- package/README.zh-CN.md +6 -10
- package/bin/ocx.mjs +85 -0
- package/gui/dist/assets/index-CKqUwc02.css +1 -0
- package/gui/dist/assets/index-CSUvRNAX.js +9 -0
- package/gui/dist/index.html +2 -2
- package/package.json +9 -4
- package/src/bun-runtime.ts +64 -0
- package/src/cli.ts +19 -4
- package/src/codex-auth-api.ts +11 -11
- package/src/codex-auth-collision.ts +14 -21
- package/src/codex-inject.ts +5 -3
- package/src/codex-journal.ts +63 -0
- package/src/codex-shim.ts +9 -1
- package/src/ports.ts +7 -0
- package/src/router.ts +42 -5
- package/src/server.ts +26 -6
- package/src/service.ts +10 -2
- package/src/update.ts +13 -5
- package/gui/dist/assets/index-CJF4_jax.css +0 -1
- package/gui/dist/assets/index-qkvcDJZw.js +0 -9
package/README.ko.md
CHANGED
|
@@ -28,12 +28,12 @@ Codex CLI / App / SDK ──/v1/responses──▶ opencodex ──▶ Any provi
|
|
|
28
28
|
| Linux (x64 / arm64) | 완전 지원 | systemd (user unit) |
|
|
29
29
|
| Windows (x64) | 완전 지원 | Task Scheduler |
|
|
30
30
|
|
|
31
|
-
[
|
|
31
|
+
[Node](https://nodejs.org) 18 이상이 필요합니다. Bun 런타임은 `npm install` 시 자동으로 번들되므로 따로 설치할 필요가 없습니다. 세 플랫폼 모두 네이티브로 동작합니다 (Windows에서도 WSL 없이 사용 가능합니다).
|
|
32
32
|
|
|
33
33
|
## 빠른 시작
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
# 설치
|
|
36
|
+
# 설치 (Bun 런타임이 자동으로 번들됩니다 — Node 18+ 만 있으면 됩니다)
|
|
37
37
|
npm install -g @bitkyc08/opencodex # 또는: bun install -g @bitkyc08/opencodex
|
|
38
38
|
|
|
39
39
|
# 대화형 설정 (config 작성 + Codex 주입 + 자동 시작 shim 설치 선택)
|
|
@@ -50,20 +50,16 @@ codex "Write a hello world in Rust"
|
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
<details>
|
|
53
|
-
<summary><b
|
|
53
|
+
<summary><b>"bundled Bun runtime is missing" 오류가 나나요?</b></summary>
|
|
54
54
|
|
|
55
55
|
<br/>
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
# macOS / Linux / WSL
|
|
59
|
-
curl -fsSL https://bun.sh/install | bash
|
|
57
|
+
opencodex는 Bun 런타임을 의존성으로 번들하고 Node 런처로 실행하므로 Bun을 직접 설치할 필요가 **없습니다**. "bundled Bun runtime is missing" 오류가 보이면 설치 과정에서 lifecycle 스크립트나 optional 의존성이 건너뛰어진 경우입니다. 해당 플래그 없이 다시 설치하세요:
|
|
60
58
|
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
```bash
|
|
60
|
+
npm install -g @bitkyc08/opencodex # --ignore-scripts, --omit=optional 없이
|
|
63
61
|
```
|
|
64
62
|
|
|
65
|
-
설치 후 `npm install -g @bitkyc08/opencodex`를 다시 실행하세요. (`ocx` 바이너리는 bun 기반이므로 bun이 `PATH`에 있어야 합니다.)
|
|
66
|
-
|
|
67
63
|
</details>
|
|
68
64
|
|
|
69
65
|
## 프로바이더 추가하기
|
package/README.md
CHANGED
|
@@ -52,12 +52,12 @@ flowchart LR
|
|
|
52
52
|
| Linux (x64 / arm64) | Fully supported | systemd (user unit) |
|
|
53
53
|
| Windows (x64) | Fully supported | Task Scheduler |
|
|
54
54
|
|
|
55
|
-
Requires [
|
|
55
|
+
Requires [Node](https://nodejs.org) 18+. The Bun runtime is bundled automatically on `npm install` — no separate Bun install needed. All three platforms work natively (no WSL needed on Windows).
|
|
56
56
|
|
|
57
57
|
## Quick start
|
|
58
58
|
|
|
59
59
|
```bash
|
|
60
|
-
# Install
|
|
60
|
+
# Install (bundles the Bun runtime automatically — only Node 18+ required)
|
|
61
61
|
npm install -g @bitkyc08/opencodex # or: bun install -g @bitkyc08/opencodex
|
|
62
62
|
|
|
63
63
|
# Interactive setup (writes config, injects into Codex, and offers autostart shim install)
|
|
@@ -74,20 +74,19 @@ codex "Write a hello world in Rust"
|
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
<details>
|
|
77
|
-
<summary><b>
|
|
77
|
+
<summary><b>"bundled Bun runtime is missing" error?</b></summary>
|
|
78
78
|
|
|
79
79
|
<br/>
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
opencodex bundles the Bun runtime as a dependency and runs it via a Node
|
|
82
|
+
launcher, so you do **not** need to install Bun yourself. If you see a
|
|
83
|
+
"bundled Bun runtime is missing" error, the install skipped lifecycle scripts
|
|
84
|
+
or optional dependencies. Reinstall without those flags:
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
```bash
|
|
87
|
+
npm install -g @bitkyc08/opencodex # no --ignore-scripts, no --omit=optional
|
|
87
88
|
```
|
|
88
89
|
|
|
89
|
-
Then re-run `npm install -g @bitkyc08/opencodex`. (The `ocx` binary is bun-native, so bun must be on your `PATH`.)
|
|
90
|
-
|
|
91
90
|
</details>
|
|
92
91
|
|
|
93
92
|
## Add a provider
|
package/README.zh-CN.md
CHANGED
|
@@ -27,12 +27,12 @@ Codex CLI / App / SDK ──/v1/responses──▶ opencodex ──▶ Any provi
|
|
|
27
27
|
| Linux (x64 / arm64) | 完整支持 | systemd(用户级) |
|
|
28
28
|
| Windows (x64) | 完整支持 | Task Scheduler |
|
|
29
29
|
|
|
30
|
-
需要 [
|
|
30
|
+
需要 [Node](https://nodejs.org) 18+。Bun 运行时会在 `npm install` 时自动打包,无需单独安装。三个平台都原生运行(Windows 不需要 WSL)。
|
|
31
31
|
|
|
32
32
|
## 快速开始
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
|
-
#
|
|
35
|
+
# 安装(自动打包 Bun 运行时 —— 只需 Node 18+)
|
|
36
36
|
npm install -g @bitkyc08/opencodex # 或者: bun install -g @bitkyc08/opencodex
|
|
37
37
|
|
|
38
38
|
# 交互式初始化(写入配置 + 注入 Codex)
|
|
@@ -46,20 +46,16 @@ codex "Write a hello world in Rust"
|
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
<details>
|
|
49
|
-
<summary><b
|
|
49
|
+
<summary><b>遇到 "bundled Bun runtime is missing" 错误?</b></summary>
|
|
50
50
|
|
|
51
51
|
<br/>
|
|
52
52
|
|
|
53
|
-
|
|
54
|
-
# macOS / Linux / WSL
|
|
55
|
-
curl -fsSL https://bun.sh/install | bash
|
|
53
|
+
opencodex 把 Bun 运行时作为依赖打包,并通过 Node 启动器运行,所以你**不需要**自己安装 Bun。如果看到 "bundled Bun runtime is missing" 错误,说明安装时跳过了 lifecycle 脚本或 optional 依赖。请不带这些标志重新安装:
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
```bash
|
|
56
|
+
npm install -g @bitkyc08/opencodex # 不要加 --ignore-scripts、--omit=optional
|
|
59
57
|
```
|
|
60
58
|
|
|
61
|
-
装完之后重新跑 `npm install -g @bitkyc08/opencodex`。(`ocx` 是 bun 原生二进制,所以 bun 必须在你的 `PATH` 里。)
|
|
62
|
-
|
|
63
59
|
</details>
|
|
64
60
|
|
|
65
61
|
## 亮点
|
package/bin/ocx.mjs
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* opencodex npm bin launcher.
|
|
4
|
+
*
|
|
5
|
+
* The package source is TypeScript that runs on the Bun runtime. To let
|
|
6
|
+
* `npm install -g @bitkyc08/opencodex` work without a separately-installed Bun,
|
|
7
|
+
* we bundle the runtime via the `bun` npm dependency and exec it from this
|
|
8
|
+
* Node shim. (Dev still runs `bun run src/cli.ts` directly via the shebang on
|
|
9
|
+
* src/cli.ts — only the published npm `bin` routes through here.)
|
|
10
|
+
*/
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { createRequire } from "node:module";
|
|
13
|
+
import { existsSync, statSync } from "node:fs";
|
|
14
|
+
import { dirname, join } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const cliPath = join(here, "..", "src", "cli.ts");
|
|
20
|
+
|
|
21
|
+
function bunBinDir() {
|
|
22
|
+
// Resolve the `bun` dependency's directory without hardcoding the platform
|
|
23
|
+
// package — npm's os/cpu/libc resolution already picked the right @oven/bun-*.
|
|
24
|
+
return dirname(require.resolve("bun/package.json"));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// The `bun` package ships a tiny ASCII placeholder at bin/bun.exe until its
|
|
28
|
+
// postinstall downloads the real ~60MB binary. --ignore-scripts / pnpm leave
|
|
29
|
+
// the ~450-byte stub in place, which is NOT executable (ENOEXEC). A size gate
|
|
30
|
+
// cleanly distinguishes the stub from a real binary on every platform.
|
|
31
|
+
const REAL_BUN_MIN_BYTES = 1_000_000;
|
|
32
|
+
|
|
33
|
+
function findBunBinary(bunDir) {
|
|
34
|
+
// The npm `bun` package ships the binary as bin/bun.exe on every platform;
|
|
35
|
+
// probe bin/bun too for forward compatibility.
|
|
36
|
+
for (const name of ["bun.exe", "bun"]) {
|
|
37
|
+
const p = join(bunDir, "bin", name);
|
|
38
|
+
if (existsSync(p) && statSync(p).size >= REAL_BUN_MIN_BYTES) return p;
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fail(msg) {
|
|
44
|
+
console.error(
|
|
45
|
+
`opencodex: ${msg}\n` +
|
|
46
|
+
"The bundled Bun runtime could not be prepared. This usually means the\n" +
|
|
47
|
+
"install skipped lifecycle scripts or optional dependencies. Reinstall with:\n" +
|
|
48
|
+
" npm install -g @bitkyc08/opencodex\n" +
|
|
49
|
+
"(without --ignore-scripts and without --omit=optional / optional=false)"
|
|
50
|
+
);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function resolveBun() {
|
|
55
|
+
let bunDir;
|
|
56
|
+
try {
|
|
57
|
+
bunDir = bunBinDir();
|
|
58
|
+
} catch {
|
|
59
|
+
fail("the `bun` dependency is not installed.");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let bin = findBunBinary(bunDir);
|
|
63
|
+
if (bin) return bin;
|
|
64
|
+
|
|
65
|
+
// Lazy fallback: --ignore-scripts (or a failed postinstall) leaves the
|
|
66
|
+
// ~450-byte placeholder stub. Run the bun package's own installer once.
|
|
67
|
+
const installJs = join(bunDir, "install.js");
|
|
68
|
+
if (existsSync(installJs)) {
|
|
69
|
+
const r = spawnSync(process.execPath, [installJs], { stdio: "inherit" });
|
|
70
|
+
if (r.status === 0) bin = findBunBinary(bunDir);
|
|
71
|
+
}
|
|
72
|
+
if (!bin) fail("Bun binary missing after install attempt.");
|
|
73
|
+
return bin;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const bun = resolveBun();
|
|
77
|
+
const res = spawnSync(bun, [cliPath, ...process.argv.slice(2)], { stdio: "inherit" });
|
|
78
|
+
if (res.error) {
|
|
79
|
+
console.error(`opencodex: failed to launch Bun runtime: ${res.error.message}`);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
if (res.signal) {
|
|
83
|
+
process.kill(process.pid, res.signal);
|
|
84
|
+
}
|
|
85
|
+
process.exit(res.status ?? 1);
|
|
@@ -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{color:var(--muted);border-radius:var(--radius-xs);padding:5px;transition:background .12s,color .12s}.btn-icon:hover{background:var(--raised-hover);color:var(--text)}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);min-width:0}.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:1px solid #0000;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)}.badge-clickable{cursor:pointer;appearance:none;transition:filter .12s}.badge-clickable:hover{filter:brightness(1.1)}.badge-disabled{opacity:.5;cursor:default}.card-badges{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;display:inline-flex}.card-badges .badge{flex-shrink:0}.confirm-icon{background:var(--amber-soft);width:44px;height:44px;color:var(--amber);border-radius:50%;justify-content:center;align-items:center;margin:0 auto 12px;display:flex}.credit-list{flex-direction:column;gap:6px;display:flex}.credit-item{border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--raised);padding:8px 10px;transition:border-color .12s}.credit-next{border-color:var(--amber);background:var(--amber-soft)}.credit-item-head{color:var(--text);align-items:center;gap:6px;margin-bottom:3px;font-size:12px;font-weight:600;display:flex}.credit-item-head svg{color:var(--amber);flex-shrink:0}.credit-item-dates{font-size:11px;font-family:var(--mono);color:var(--muted);justify-content:space-between;display:flex}.credit-urgent{color:var(--red);font-weight:600}.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}.select-sm{border-radius:var(--radius-sm);background:var(--raised);border:1px solid var(--border);color:var(--text);font:inherit;cursor:pointer;padding:5px 8px;font-size:13px;transition:border-color .12s}.select-sm:focus{border-color:var(--accent);outline:none}.select-sm:disabled{opacity:.5;cursor:default}.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}.card-head{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;padding:14px 16px 6px;display:flex}.card-head strong{overflow-wrap:anywhere;min-width:0}.card-sub{color:var(--muted);overflow-wrap:anywhere;min-width:0;padding:0 16px 10px;font-size:12px}.card-active{border-color:var(--accent-ring)}.card-right{color:var(--faint);align-items:center;gap:4px;margin-left:auto;font-size:11px;display:flex}.btn-icon-danger.card-right{appearance:none;color:var(--red);cursor:pointer;background:0 0;border:1px solid #0000;justify-content:center}.btn-icon-danger.card-right:hover{background:var(--red-soft);border-color:var(--red-soft);color:var(--red)}.card-row{justify-content:space-between;align-items:center;padding:14px 16px;display:flex}.badge-primary{background:var(--accent-soft);color:var(--accent-hover)}.dot-blue{background:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}.dot-muted{background:var(--muted)}.dot-amber{background:var(--amber);box-shadow:0 0 0 3px var(--amber-soft)}.section-sep{align-items:center;gap:10px;margin:20px 0 12px;display:flex}.section-label{color:var(--muted);text-transform:uppercase;letter-spacing:.04em;white-space:nowrap;font-size:12px;font-weight:600}.sep-line{background:var(--border);flex:1;height:1px}.quota-compact{gap:4px;padding:2px 16px 12px;display:grid}.quota-row{grid-template-columns:minmax(34px,max-content) 34px minmax(34px,44px) minmax(38px,42px) minmax(58px,1fr) minmax(30px,max-content);align-items:center;gap:8px;min-width:0;display:grid}.quota-label{color:var(--muted);font-size:11px;font-weight:600}.quota-val{font-size:11px;font-family:var(--mono);color:var(--text);text-align:right}.quota-reset-label{text-overflow:ellipsis;white-space:nowrap;color:var(--faint);font-size:11px;overflow:hidden}.quota-reset-day,.quota-reset-time{color:var(--muted);white-space:nowrap;font-size:11px}.quota-reset-time{font-family:var(--mono)}.bar{background:var(--raised);border-radius:3px;min-width:0;height:5px;overflow:hidden}.bar-fill{border-radius:3px;height:100%;transition:width .3s}.bar-green{background:var(--green)}.bar-amber{background:linear-gradient(90deg, var(--green), var(--amber))}.toggle{background:var(--raised);border:1px solid var(--border);cursor:pointer;border-radius:10px;flex-shrink:0;width:36px;height:20px;transition:background .15s;position:relative}.toggle.on{background:var(--accent);border-color:var(--accent)}.toggle-knob{background:#fff;border-radius:50%;width:16px;height:16px;transition:left .15s;position:absolute;top:1px;left:1px}.toggle.on .toggle-knob{left:17px}.notice-warn{border-radius:var(--radius-sm);background:var(--amber-soft);color:var(--amber);align-items:center;gap:6px;margin-bottom:12px;padding:8px 10px;font-size:12px;display:flex}.notice-warn svg{flex-shrink:0;width:14px;height:14px}.modal-desc{color:var(--muted);margin-bottom:14px;font-size:13px}.modal-actions{gap:8px;margin-top:16px;display:flex}.modal-actions .btn{flex:1}.setup-guide{border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:4px;padding:8px 12px;font-size:13px}.setup-guide summary{cursor:pointer;color:var(--accent-hover);font-weight:500}.setup-guide summary:hover{text-decoration:underline}.setup-guide a{color:var(--accent-hover)}.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}}
|