@bitkyc08/opencodex 2.5.6 → 2.6.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 +17 -6
- package/README.md +19 -7
- package/README.zh-CN.md +12 -3
- package/assets/architecture.png +0 -0
- package/assets/banner.png +0 -0
- package/assets/codex-app-picker.png +0 -0
- package/bin/ocx.mjs +88 -2
- package/bin/package-main.mjs +9 -0
- package/gui/dist/assets/index-BS4X1QDi.js +9 -0
- package/gui/dist/assets/{index-CKqUwc02.css → index-BwvDb198.css} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +20 -6
- package/src/adapters/anthropic.ts +16 -5
- package/src/adapters/google.ts +9 -2
- package/src/adapters/openai-chat.ts +13 -5
- package/src/bun-runtime.ts +22 -1
- package/src/cli-help.ts +111 -0
- package/src/cli-status.ts +164 -0
- package/src/cli.ts +77 -186
- package/src/codex-account-store.ts +47 -8
- package/src/codex-auth-api.ts +111 -54
- package/src/codex-auth-collision.ts +5 -0
- package/src/codex-catalog.ts +24 -12
- package/src/codex-history-provider.ts +29 -13
- package/src/codex-inject.ts +46 -29
- package/src/codex-journal.ts +77 -13
- package/src/codex-quota.ts +11 -3
- package/src/codex-routing.ts +14 -4
- package/src/codex-shim.ts +71 -24
- package/src/codex-websocket-registry.ts +20 -4
- package/src/config.ts +138 -4
- package/src/init.ts +7 -2
- package/src/oauth/callback-server.ts +22 -15
- package/src/oauth/index.ts +18 -4
- package/src/oauth/login-cli.ts +8 -1
- package/src/oauth/store.ts +2 -1
- package/src/process-control.ts +36 -0
- package/src/provider-label.ts +8 -0
- package/src/responses/parser.ts +18 -1
- package/src/router.ts +61 -5
- package/src/server.ts +878 -94
- package/src/service-secrets.ts +6 -0
- package/src/service.ts +293 -28
- package/src/types.ts +26 -1
- package/src/update.ts +16 -9
- package/src/usage-debug.ts +65 -0
- package/src/usage-log.ts +62 -0
- package/src/usage-summary.ts +0 -0
- package/src/ws-bridge.ts +2 -2
- package/gui/README.md +0 -73
- package/gui/dist/assets/index-CSUvRNAX.js +0 -9
|
@@ -1 +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}}
|
|
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}.usage-cards{grid-template-columns:repeat(2,minmax(0,1fr))}}.usage-cards{grid-template-columns:repeat(5,minmax(0,1fr));gap:12px;margin-top:8px;display:grid}.usage-cards .stat-value{margin-top:4px;font-size:22px;font-weight:600}.usage-range{border:1px solid var(--border);background:var(--surface);border-radius:999px;gap:2px;padding:2px;display:inline-flex}.usage-range-btn{color:var(--muted);cursor:pointer;font:inherit;background:0 0;border:none;border-radius:999px;padding:4px 12px}.usage-range-btn.active{background:var(--raised);color:var(--text);font-weight:600}.panel-title{color:var(--text);margin:0 0 12px;font-size:14px;font-weight:600}.panel-head{justify-content:space-between;align-items:center;gap:12px;margin-bottom:12px;display:flex}.panel-head .panel-title{margin:0}.panel-head .input{max-width:220px}.heatmap{flex-direction:column;gap:6px;padding-bottom:4px;display:flex;overflow-x:auto}.heatmap-months{color:var(--muted);margin-bottom:-2px;font-size:11px;display:grid}.heatmap-day-spacer{grid-column:1}.heatmap-month{white-space:nowrap}.heatmap-body{gap:3px;display:flex}.heatmap-days{color:var(--muted);flex-shrink:0;grid-template-rows:repeat(7,1fr);align-items:center;width:25px;font-size:10px;display:grid}.heatmap-grid{flex:1;gap:3px;min-width:0;display:grid}.heatmap-week{grid-template-rows:repeat(7,1fr);gap:3px;display:grid}.heatmap-cell{aspect-ratio:1;background:var(--border);border-radius:2px;min-width:0}.heatmap-cell-0{background:var(--border)}.heatmap-cell-1{background:color-mix(in oklch, var(--green) 25%, var(--surface))}.heatmap-cell-2{background:color-mix(in oklch, var(--green) 50%, var(--surface))}.heatmap-cell-3{background:color-mix(in oklch, var(--green) 75%, var(--surface))}.heatmap-cell-4{background:var(--green)}.heatmap-legend{align-self:flex-end;align-items:center;gap:4px;font-size:12px;display:inline-flex}.heatmap-legend .heatmap-cell{aspect-ratio:auto;width:10px;height:10px}.usage-bar{background:var(--border);border-radius:999px;width:100%;min-width:60px;height:6px;overflow:hidden}.usage-bar-fill{background:var(--green);border-radius:999px;height:100%}
|
package/gui/dist/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-BS4X1QDi.js"></script>
|
|
20
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BwvDb198.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
|
23
23
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitkyc08/opencodex",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.0",
|
|
4
4
|
"description": "Universal provider proxy for OpenAI Codex — use any LLM with Codex CLI/App/SDK",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "
|
|
6
|
+
"main": "./bin/package-main.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"bun": "./src/index.ts",
|
|
10
|
+
"default": "./bin/package-main.mjs"
|
|
11
|
+
},
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
7
14
|
"bin": {
|
|
8
15
|
"opencodex": "./bin/ocx.mjs",
|
|
9
16
|
"ocx": "./bin/ocx.mjs"
|
|
@@ -12,6 +19,9 @@
|
|
|
12
19
|
"bin",
|
|
13
20
|
"src",
|
|
14
21
|
"gui/dist",
|
|
22
|
+
"assets/banner.png",
|
|
23
|
+
"assets/architecture.png",
|
|
24
|
+
"assets/codex-app-picker.png",
|
|
15
25
|
"README.md",
|
|
16
26
|
"LICENSE"
|
|
17
27
|
],
|
|
@@ -23,23 +33,27 @@
|
|
|
23
33
|
],
|
|
24
34
|
"scripts": {
|
|
25
35
|
"dev": "bun run src/cli.ts start",
|
|
36
|
+
"dev:proxy": "bun run src/cli.ts start",
|
|
37
|
+
"dev:gui": "cd gui && bun run dev",
|
|
26
38
|
"start": "bun run src/cli.ts start",
|
|
27
39
|
"test": "bun test tests",
|
|
28
40
|
"typecheck": "bun x tsc --noEmit",
|
|
29
41
|
"privacy:scan": "bun scripts/privacy-scan.ts",
|
|
30
42
|
"generate:jawcode-metadata": "bun scripts/generate-jawcode-metadata.ts",
|
|
31
|
-
"build:gui": "cd gui && bun install && bun run build",
|
|
43
|
+
"build:gui": "cd gui && bun install --frozen-lockfile && bun run build && cd .. && bun run prepare:package",
|
|
44
|
+
"prepare:package": "bun scripts/prepare-package.ts",
|
|
45
|
+
"prepack": "bun run prepare:package",
|
|
32
46
|
"prepublishOnly": "bun run typecheck && bun run build:gui",
|
|
33
47
|
"release": "bun scripts/release.ts",
|
|
34
48
|
"release:watch": "bun scripts/release.ts watch"
|
|
35
49
|
},
|
|
36
50
|
"dependencies": {
|
|
37
51
|
"bun": "1.3.14",
|
|
38
|
-
"zod": "
|
|
52
|
+
"zod": "4.4.3"
|
|
39
53
|
},
|
|
40
54
|
"devDependencies": {
|
|
41
|
-
"@types/bun": "
|
|
42
|
-
"typescript": "
|
|
55
|
+
"@types/bun": "1.3.14",
|
|
56
|
+
"typescript": "5.9.3"
|
|
43
57
|
},
|
|
44
58
|
"keywords": [
|
|
45
59
|
"codex",
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
OcxToolResultMessage,
|
|
14
14
|
OcxUsage,
|
|
15
15
|
} from "../types";
|
|
16
|
-
import { namespacedToolName } from "../types";
|
|
16
|
+
import { isAllowedToolChoice, namespacedToolName, resolveToolChoiceWireName, toolAllowedByChoice } from "../types";
|
|
17
17
|
import { ANTHROPIC_OAUTH_BETA, CLAUDE_CODE_SYSTEM_INSTRUCTION, applyClaudeToolPrefix, stripClaudeToolPrefix } from "../oauth/anthropic";
|
|
18
18
|
import { parseDataUrl } from "./image";
|
|
19
19
|
|
|
@@ -115,7 +115,10 @@ function messagesToAnthropicFormat(
|
|
|
115
115
|
parsed: OcxParsedRequest,
|
|
116
116
|
toolNames: { toWire: (name: string) => string },
|
|
117
117
|
): { system: string | undefined; messages: unknown[] } {
|
|
118
|
-
const system = parsed.context.systemPrompt?.join("\n\n")
|
|
118
|
+
const system = parsed.context.systemPrompt?.join("\n\n").replace(
|
|
119
|
+
"You are Codex, a coding agent based on GPT-5.",
|
|
120
|
+
`You are a coding agent (underlying model: ${parsed.modelId}) running via the opencodex proxy. Do not claim to be GPT-5 or to be made by OpenAI.`,
|
|
121
|
+
) || undefined;
|
|
119
122
|
const messages: unknown[] = [];
|
|
120
123
|
|
|
121
124
|
for (let i = 0; i < parsed.context.messages.length; i++) {
|
|
@@ -192,7 +195,14 @@ function messagesToAnthropicFormat(
|
|
|
192
195
|
|
|
193
196
|
function toolsToAnthropicFormat(parsed: OcxParsedRequest, toolNames: { toWire: (name: string) => string }): unknown[] | undefined {
|
|
194
197
|
if (!parsed.context.tools || parsed.context.tools.length === 0) return undefined;
|
|
195
|
-
|
|
198
|
+
const allowed = isAllowedToolChoice(parsed.options.toolChoice)
|
|
199
|
+
? new Set(parsed.options.toolChoice.allowedTools)
|
|
200
|
+
: undefined;
|
|
201
|
+
const tools = allowed
|
|
202
|
+
? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed))
|
|
203
|
+
: parsed.context.tools;
|
|
204
|
+
if (tools.length === 0) return undefined;
|
|
205
|
+
return tools.map(t => ({
|
|
196
206
|
name: toolNames.toWire(namespacedToolName(t.namespace, t.name)),
|
|
197
207
|
description: t.description,
|
|
198
208
|
input_schema: t.parameters,
|
|
@@ -245,12 +255,13 @@ export function createAnthropicAdapter(provider: OcxProviderConfig): ProviderAda
|
|
|
245
255
|
delete body.top_p;
|
|
246
256
|
}
|
|
247
257
|
|
|
248
|
-
if (parsed.options.toolChoice) {
|
|
258
|
+
if (parsed.options.toolChoice && (tools || parsed.options.toolChoice === "none")) {
|
|
249
259
|
const tc = parsed.options.toolChoice;
|
|
250
260
|
if (tc === "auto") body.tool_choice = { type: "auto" };
|
|
251
261
|
else if (tc === "none") body.tool_choice = { type: "none" };
|
|
252
262
|
else if (tc === "required") body.tool_choice = { type: "any" };
|
|
253
|
-
else if (
|
|
263
|
+
else if (isAllowedToolChoice(tc)) body.tool_choice = { type: tc.mode === "required" ? "any" : "auto" };
|
|
264
|
+
else if (typeof tc === "object" && "name" in tc) body.tool_choice = { type: "tool", name: toolNames.toWire(resolveToolChoiceWireName(parsed.context.tools, tc.name)) };
|
|
254
265
|
}
|
|
255
266
|
|
|
256
267
|
const base = provider.baseUrl.replace(/\/v1\/?$/, "");
|
package/src/adapters/google.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type {
|
|
|
10
10
|
OcxToolCall,
|
|
11
11
|
OcxUsage,
|
|
12
12
|
} from "../types";
|
|
13
|
-
import { namespacedToolName } from "../types";
|
|
13
|
+
import { isAllowedToolChoice, namespacedToolName, toolAllowedByChoice } from "../types";
|
|
14
14
|
import { contentPartsToText, parseDataUrl } from "./image";
|
|
15
15
|
|
|
16
16
|
function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?: unknown; contents: unknown[] } {
|
|
@@ -68,8 +68,15 @@ function messagesToGeminiFormat(parsed: OcxParsedRequest): { systemInstruction?:
|
|
|
68
68
|
|
|
69
69
|
function toolsToGeminiFormat(parsed: OcxParsedRequest): unknown[] | undefined {
|
|
70
70
|
if (!parsed.context.tools?.length) return undefined;
|
|
71
|
+
const allowed = isAllowedToolChoice(parsed.options.toolChoice)
|
|
72
|
+
? new Set(parsed.options.toolChoice.allowedTools)
|
|
73
|
+
: undefined;
|
|
74
|
+
const tools = allowed
|
|
75
|
+
? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed))
|
|
76
|
+
: parsed.context.tools;
|
|
77
|
+
if (tools.length === 0) return undefined;
|
|
71
78
|
return [{
|
|
72
|
-
functionDeclarations:
|
|
79
|
+
functionDeclarations: tools.map(t => ({
|
|
73
80
|
name: namespacedToolName(t.namespace, t.name),
|
|
74
81
|
description: t.description,
|
|
75
82
|
parameters: t.parameters,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ProviderAdapter } from "./base";
|
|
2
2
|
import { debugDroppedFrame } from "../debug";
|
|
3
3
|
import type { AdapterEvent, OcxAssistantMessage, OcxContentPart, OcxMessage, OcxParsedRequest, OcxProviderConfig, OcxTextContent, OcxThinkingContent, OcxToolCall, OcxUsage } from "../types";
|
|
4
|
-
import { modelInList, namespacedToolName } from "../types";
|
|
4
|
+
import { isAllowedToolChoice, modelInList, namespacedToolName, resolveToolChoiceWireName, toolAllowedByChoice } from "../types";
|
|
5
5
|
import { mapReasoningEffort } from "../reasoning-effort";
|
|
6
6
|
import { contentPartsToText } from "./image";
|
|
7
7
|
|
|
@@ -115,7 +115,14 @@ function safeToolName(name: string | undefined): string {
|
|
|
115
115
|
|
|
116
116
|
function toolsToChatFormat(parsed: OcxParsedRequest): unknown[] | undefined {
|
|
117
117
|
if (!parsed.context.tools || parsed.context.tools.length === 0) return undefined;
|
|
118
|
-
|
|
118
|
+
const allowed = isAllowedToolChoice(parsed.options.toolChoice)
|
|
119
|
+
? new Set(parsed.options.toolChoice.allowedTools)
|
|
120
|
+
: undefined;
|
|
121
|
+
const tools = allowed
|
|
122
|
+
? parsed.context.tools.filter(t => toolAllowedByChoice(t, allowed))
|
|
123
|
+
: parsed.context.tools;
|
|
124
|
+
if (tools.length === 0) return undefined;
|
|
125
|
+
return tools.map(t => ({
|
|
119
126
|
type: "function",
|
|
120
127
|
function: {
|
|
121
128
|
name: namespacedToolName(t.namespace, t.name),
|
|
@@ -126,10 +133,11 @@ function toolsToChatFormat(parsed: OcxParsedRequest): unknown[] | undefined {
|
|
|
126
133
|
}));
|
|
127
134
|
}
|
|
128
135
|
|
|
129
|
-
function toolChoiceToChatFormat(tc: OcxParsedRequest["options"]["toolChoice"]): unknown {
|
|
136
|
+
function toolChoiceToChatFormat(tc: OcxParsedRequest["options"]["toolChoice"], tools: OcxParsedRequest["context"]["tools"]): unknown {
|
|
130
137
|
if (!tc) return undefined;
|
|
138
|
+
if (isAllowedToolChoice(tc)) return tc.mode === "required" ? "required" : "auto";
|
|
131
139
|
if (tc === "auto" || tc === "none" || tc === "required") return tc;
|
|
132
|
-
if ("name" in tc) return { type: "function", function: { name: tc.name } };
|
|
140
|
+
if ("name" in tc) return { type: "function", function: { name: resolveToolChoiceWireName(tools, tc.name) } };
|
|
133
141
|
return undefined;
|
|
134
142
|
}
|
|
135
143
|
|
|
@@ -152,7 +160,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
|
|
|
152
160
|
buildRequest(parsed: OcxParsedRequest) {
|
|
153
161
|
const messages = messagesToChatFormat(parsed, provider);
|
|
154
162
|
const tools = toolsToChatFormat(parsed);
|
|
155
|
-
const toolChoice = toolChoiceToChatFormat(parsed.options.toolChoice);
|
|
163
|
+
const toolChoice = toolChoiceToChatFormat(parsed.options.toolChoice, parsed.context.tools);
|
|
156
164
|
|
|
157
165
|
const body: Record<string, unknown> = {
|
|
158
166
|
model: parsed.modelId,
|
package/src/bun-runtime.ts
CHANGED
|
@@ -20,6 +20,13 @@ const require = createRequire(import.meta.url);
|
|
|
20
20
|
// postinstall downloads the real ~60MB binary; reject the stub by size so we
|
|
21
21
|
// never bake a non-executable path into durable artifacts.
|
|
22
22
|
const REAL_BUN_MIN_BYTES = 1_000_000;
|
|
23
|
+
const BUN_OVERRIDE_ENV = "OPENCODEX_BUN_PATH";
|
|
24
|
+
|
|
25
|
+
export type DurableBunRuntime = {
|
|
26
|
+
path: string;
|
|
27
|
+
source: "override" | "bundled" | "process";
|
|
28
|
+
overrideEnv: typeof BUN_OVERRIDE_ENV;
|
|
29
|
+
};
|
|
23
30
|
|
|
24
31
|
/**
|
|
25
32
|
* True only for a real, downloaded Bun binary — not the ~450-byte ASCII
|
|
@@ -53,6 +60,20 @@ export function bundledBunPath(): string | null {
|
|
|
53
60
|
}
|
|
54
61
|
}
|
|
55
62
|
|
|
63
|
+
export function overrideBunPath(): string | null {
|
|
64
|
+
const value = process.env[BUN_OVERRIDE_ENV]?.trim();
|
|
65
|
+
if (!value) return null;
|
|
66
|
+
return isRealBunBinary(value) ? value : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function durableBunRuntime(): DurableBunRuntime {
|
|
70
|
+
const override = overrideBunPath();
|
|
71
|
+
if (override) return { path: override, source: "override", overrideEnv: BUN_OVERRIDE_ENV };
|
|
72
|
+
const bundled = bundledBunPath();
|
|
73
|
+
if (bundled) return { path: bundled, source: "bundled", overrideEnv: BUN_OVERRIDE_ENV };
|
|
74
|
+
return { path: process.execPath, source: "process", overrideEnv: BUN_OVERRIDE_ENV };
|
|
75
|
+
}
|
|
76
|
+
|
|
56
77
|
/**
|
|
57
78
|
* Bun path to bake into durable artifacts (launchd/systemd/Task Scheduler and
|
|
58
79
|
* the Codex auto-start shim). Prefer the bundled binary — it lives under the
|
|
@@ -60,5 +81,5 @@ export function bundledBunPath(): string | null {
|
|
|
60
81
|
* current runtime, which is Bun when launched normally.
|
|
61
82
|
*/
|
|
62
83
|
export function durableBunPath(): string {
|
|
63
|
-
return
|
|
84
|
+
return durableBunRuntime().path;
|
|
64
85
|
}
|
package/src/cli-help.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const repoRoot = dirname(fileURLToPath(new URL("../package.json", import.meta.url)));
|
|
6
|
+
|
|
7
|
+
type HelpEntry = {
|
|
8
|
+
usage: string;
|
|
9
|
+
summary: string;
|
|
10
|
+
details?: string[];
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const helpEntries: Record<string, HelpEntry> = {
|
|
14
|
+
init: { usage: "ocx init", summary: "Interactive setup for providers and Codex config injection." },
|
|
15
|
+
start: { usage: "ocx start [--port <port>]", summary: "Start the proxy server and sync models to Codex." },
|
|
16
|
+
stop: { usage: "ocx stop", summary: "Stop the proxy and restore native Codex config." },
|
|
17
|
+
restore: { usage: "ocx restore", summary: "Restore native Codex config without stopping the proxy." },
|
|
18
|
+
eject: { usage: "ocx eject", summary: "Restore native Codex config without stopping the proxy." },
|
|
19
|
+
"recover-history": {
|
|
20
|
+
usage: "ocx recover-history --legacy-openai",
|
|
21
|
+
summary: "Explicitly recover pre-backup syncResumeHistory rows.",
|
|
22
|
+
},
|
|
23
|
+
uninstall: {
|
|
24
|
+
usage: "ocx uninstall",
|
|
25
|
+
summary: "Remove service/shim/config and restore native Codex.",
|
|
26
|
+
details: ["Alias: ocx remove"],
|
|
27
|
+
},
|
|
28
|
+
remove: {
|
|
29
|
+
usage: "ocx remove",
|
|
30
|
+
summary: "Remove service/shim/config and restore native Codex.",
|
|
31
|
+
details: ["Alias of: ocx uninstall"],
|
|
32
|
+
},
|
|
33
|
+
service: {
|
|
34
|
+
usage: "ocx service <install|start|stop|status|uninstall|remove>",
|
|
35
|
+
summary: "Run as a background service.",
|
|
36
|
+
details: ["Use `ocx service status` to see diagnostics and log paths."],
|
|
37
|
+
},
|
|
38
|
+
"codex-shim": {
|
|
39
|
+
usage: "ocx codex-shim <install|status|uninstall|remove>",
|
|
40
|
+
summary: "Auto-start the proxy when `codex` launches.",
|
|
41
|
+
details: ["Use `remove` as an alias for `uninstall`."],
|
|
42
|
+
},
|
|
43
|
+
ensure: { usage: "ocx ensure", summary: "Ensure the proxy is running and Codex config/cache are current." },
|
|
44
|
+
sync: { usage: "ocx sync", summary: "Fetch provider models and inject them into Codex config." },
|
|
45
|
+
"sync-cache": { usage: "ocx sync-cache", summary: "Refresh Codex's model cache from the active catalog." },
|
|
46
|
+
status: { usage: "ocx status", summary: "Check proxy server status." },
|
|
47
|
+
login: { usage: "ocx login <provider>", summary: "OAuth or API-key login for a provider." },
|
|
48
|
+
logout: { usage: "ocx logout <provider>", summary: "Remove a stored provider login." },
|
|
49
|
+
gui: { usage: "ocx gui", summary: "Open the opencodex dashboard." },
|
|
50
|
+
update: {
|
|
51
|
+
usage: "ocx update [--tag latest|preview]",
|
|
52
|
+
summary: "Update opencodex. Preview installs stay on the preview tag unless overridden.",
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function packageVersion(): string {
|
|
57
|
+
const raw = readFileSync(join(repoRoot, "package.json"), "utf8");
|
|
58
|
+
const parsed = JSON.parse(raw) as { version?: unknown };
|
|
59
|
+
return typeof parsed.version === "string" ? parsed.version : "unknown";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function printVersion(): void {
|
|
63
|
+
console.log(`opencodex ${packageVersion()}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function printUsage(): void {
|
|
67
|
+
console.log(`opencodex (ocx) — Universal provider proxy for Codex
|
|
68
|
+
|
|
69
|
+
Usage:
|
|
70
|
+
ocx init Interactive setup (provider + Codex config injection)
|
|
71
|
+
ocx start [--port <port>] Start the proxy server (auto-syncs models to Codex)
|
|
72
|
+
ocx stop Stop the proxy AND restore native Codex (plain codex works again)
|
|
73
|
+
ocx restore Restore native Codex without stopping (alias: eject)
|
|
74
|
+
ocx recover-history --legacy-openai
|
|
75
|
+
Explicitly recover pre-backup syncResumeHistory rows
|
|
76
|
+
ocx uninstall Remove service/shim/config and restore native Codex (alias: remove)
|
|
77
|
+
ocx service <sub> Run as a background service (install|start|stop|status|uninstall|remove)
|
|
78
|
+
ocx codex-shim <sub> Auto-start proxy when \`codex\` launches (install|status|uninstall|remove)
|
|
79
|
+
ocx ensure Ensure the proxy is running and Codex config/cache are current
|
|
80
|
+
ocx sync Fetch models from providers and inject into Codex config
|
|
81
|
+
ocx sync-cache Refresh Codex's model cache from the active catalog
|
|
82
|
+
ocx status Check proxy server status
|
|
83
|
+
ocx login <provider> OAuth login (xai) — opens browser, stores token in ~/.opencodex/auth.json
|
|
84
|
+
ocx logout <provider> Remove a stored OAuth login
|
|
85
|
+
ocx gui Open the opencodex dashboard
|
|
86
|
+
ocx update [--tag <tag>] Update opencodex (keeps preview installs on @preview)
|
|
87
|
+
ocx help [command] Show help
|
|
88
|
+
ocx --version | -v Print version
|
|
89
|
+
|
|
90
|
+
Examples:
|
|
91
|
+
ocx init Set up provider and inject into Codex
|
|
92
|
+
ocx start Start on default port (10100)
|
|
93
|
+
ocx start --port 8080 Start on custom port
|
|
94
|
+
ocx help service Show service command help
|
|
95
|
+
ocx sync Sync available models to Codex`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function hasHelpFlag(values: string[]): boolean {
|
|
99
|
+
return values.some(value => value === "--help" || value === "-h" || value === "help");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function printSubcommandUsage(name: string | undefined): void {
|
|
103
|
+
const entry = name ? helpEntries[name] : undefined;
|
|
104
|
+
if (!entry) {
|
|
105
|
+
console.error(`Unknown command: ${name ?? ""}`.trim());
|
|
106
|
+
printUsage();
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
console.log(`Usage: ${entry.usage}\n\n${entry.summary}`);
|
|
110
|
+
if (entry.details?.length) console.log(`\n${entry.details.join("\n")}`);
|
|
111
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { durableBunRuntime } from "./bun-runtime";
|
|
2
|
+
import { codexAutoStartEnabled, getConfigPath, getPidPath, readConfigDiagnostics, readPid, readRuntimePort, type RuntimePortState } from "./config";
|
|
3
|
+
import type { OcxConfig } from "./types";
|
|
4
|
+
import { serviceStatusSummary } from "./service";
|
|
5
|
+
|
|
6
|
+
type HealthCheck = {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
url: string;
|
|
9
|
+
message: string;
|
|
10
|
+
label: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type CliStatusJson = {
|
|
14
|
+
schemaVersion: 1;
|
|
15
|
+
proxy: {
|
|
16
|
+
running: boolean;
|
|
17
|
+
pid: number | null;
|
|
18
|
+
health: {
|
|
19
|
+
ok: boolean;
|
|
20
|
+
url: string;
|
|
21
|
+
message: string;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
dashboard: { url: string };
|
|
25
|
+
listen: {
|
|
26
|
+
port: number;
|
|
27
|
+
hostname: string | null;
|
|
28
|
+
source: "runtime" | "config";
|
|
29
|
+
};
|
|
30
|
+
paths: {
|
|
31
|
+
config: string;
|
|
32
|
+
pid: string;
|
|
33
|
+
runtime: string;
|
|
34
|
+
};
|
|
35
|
+
runtime: {
|
|
36
|
+
source: string;
|
|
37
|
+
overrideEnv?: string;
|
|
38
|
+
};
|
|
39
|
+
codexAutostart: boolean;
|
|
40
|
+
defaultProvider: string | null;
|
|
41
|
+
config: {
|
|
42
|
+
source: "default" | "file" | "fallback";
|
|
43
|
+
error: string | null;
|
|
44
|
+
};
|
|
45
|
+
service: { summary: string };
|
|
46
|
+
codexShim: { summary: string };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type CliStatusView = {
|
|
50
|
+
json: CliStatusJson;
|
|
51
|
+
proxyLabel: string;
|
|
52
|
+
healthLabel: string;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function healthHost(hostname?: string): string {
|
|
56
|
+
return !hostname || hostname === "0.0.0.0" || hostname === "::" ? "127.0.0.1" : hostname;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type ListenTarget = {
|
|
60
|
+
port: number;
|
|
61
|
+
hostname?: string;
|
|
62
|
+
source: "runtime" | "config";
|
|
63
|
+
healthUrl: string;
|
|
64
|
+
dashboardUrl: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export function selectListenTarget(
|
|
68
|
+
config: Pick<OcxConfig, "port" | "hostname">,
|
|
69
|
+
pid: number | null,
|
|
70
|
+
runtimePort: RuntimePortState | null,
|
|
71
|
+
): ListenTarget {
|
|
72
|
+
const currentRuntimePort = pid && runtimePort?.pid === pid ? runtimePort : null;
|
|
73
|
+
const port = currentRuntimePort ? currentRuntimePort.port : config.port ?? 10100;
|
|
74
|
+
const hostname = currentRuntimePort ? currentRuntimePort.hostname : config.hostname;
|
|
75
|
+
return {
|
|
76
|
+
port,
|
|
77
|
+
hostname,
|
|
78
|
+
source: currentRuntimePort ? "runtime" : "config",
|
|
79
|
+
healthUrl: `http://${healthHost(hostname)}:${port}/healthz`,
|
|
80
|
+
dashboardUrl: `http://localhost:${port}/`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function checkProxyHealth(target: ListenTarget): Promise<HealthCheck> {
|
|
85
|
+
const url = target.healthUrl;
|
|
86
|
+
const controller = new AbortController();
|
|
87
|
+
const timer = setTimeout(() => controller.abort(), 800);
|
|
88
|
+
try {
|
|
89
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
const message = `returned HTTP ${response.status}`;
|
|
92
|
+
return { ok: false, url, message, label: `${url} ${message}` };
|
|
93
|
+
}
|
|
94
|
+
const body = await response.json().catch(() => null) as { version?: unknown; uptime?: unknown } | null;
|
|
95
|
+
const version = typeof body?.version === "string" ? ` v${body.version}` : "";
|
|
96
|
+
const uptime = typeof body?.uptime === "number" ? `, uptime ${Math.round(body.uptime)}s` : "";
|
|
97
|
+
const message = `ok${version}${uptime}`;
|
|
98
|
+
return { ok: true, url, message, label: `${url} ${message}` };
|
|
99
|
+
} catch (error) {
|
|
100
|
+
const reason = error instanceof Error && error.name === "AbortError" ? "timed out" : "unreachable";
|
|
101
|
+
return { ok: false, url, message: reason, label: `${url} ${reason}` };
|
|
102
|
+
} finally {
|
|
103
|
+
clearTimeout(timer);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function collectStatus(): Promise<CliStatusView> {
|
|
108
|
+
const configDiagnostics = readConfigDiagnostics();
|
|
109
|
+
const config = configDiagnostics.config;
|
|
110
|
+
const pid = readPid();
|
|
111
|
+
const listen = selectListenTarget(config, pid, pid ? readRuntimePort(pid) : null);
|
|
112
|
+
const health = await checkProxyHealth(listen);
|
|
113
|
+
const bunRuntime = durableBunRuntime();
|
|
114
|
+
const serviceSummary = serviceStatusSummary();
|
|
115
|
+
const { codexShimStatus } = await import("./codex-shim");
|
|
116
|
+
const codexShimSummary = codexShimStatus();
|
|
117
|
+
const proxyLabel = pid && health.ok
|
|
118
|
+
? `running (PID ${pid})`
|
|
119
|
+
: pid
|
|
120
|
+
? `PID file points to PID ${pid}, but health check failed`
|
|
121
|
+
: health.ok
|
|
122
|
+
? "reachable, but PID file is missing or stale"
|
|
123
|
+
: "not running";
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
proxyLabel,
|
|
127
|
+
healthLabel: health.label,
|
|
128
|
+
json: {
|
|
129
|
+
schemaVersion: 1,
|
|
130
|
+
proxy: {
|
|
131
|
+
running: Boolean(pid && health.ok),
|
|
132
|
+
pid,
|
|
133
|
+
health: {
|
|
134
|
+
ok: health.ok,
|
|
135
|
+
url: health.url,
|
|
136
|
+
message: health.message,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
dashboard: { url: listen.dashboardUrl },
|
|
140
|
+
listen: {
|
|
141
|
+
port: listen.port,
|
|
142
|
+
hostname: listen.hostname ?? null,
|
|
143
|
+
source: listen.source,
|
|
144
|
+
},
|
|
145
|
+
paths: {
|
|
146
|
+
config: getConfigPath(),
|
|
147
|
+
pid: getPidPath(),
|
|
148
|
+
runtime: bunRuntime.path,
|
|
149
|
+
},
|
|
150
|
+
runtime: {
|
|
151
|
+
source: bunRuntime.source,
|
|
152
|
+
...(bunRuntime.source === "override" ? { overrideEnv: bunRuntime.overrideEnv } : {}),
|
|
153
|
+
},
|
|
154
|
+
codexAutostart: codexAutoStartEnabled(config),
|
|
155
|
+
defaultProvider: typeof config.defaultProvider === "string" ? config.defaultProvider : null,
|
|
156
|
+
config: {
|
|
157
|
+
source: configDiagnostics.source,
|
|
158
|
+
error: configDiagnostics.error,
|
|
159
|
+
},
|
|
160
|
+
service: { summary: serviceSummary },
|
|
161
|
+
codexShim: { summary: codexShimSummary },
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|