@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.
Files changed (51) hide show
  1. package/README.ko.md +17 -6
  2. package/README.md +19 -7
  3. package/README.zh-CN.md +12 -3
  4. package/assets/architecture.png +0 -0
  5. package/assets/banner.png +0 -0
  6. package/assets/codex-app-picker.png +0 -0
  7. package/bin/ocx.mjs +88 -2
  8. package/bin/package-main.mjs +9 -0
  9. package/gui/dist/assets/index-BS4X1QDi.js +9 -0
  10. package/gui/dist/assets/{index-CKqUwc02.css → index-BwvDb198.css} +1 -1
  11. package/gui/dist/index.html +2 -2
  12. package/package.json +20 -6
  13. package/src/adapters/anthropic.ts +16 -5
  14. package/src/adapters/google.ts +9 -2
  15. package/src/adapters/openai-chat.ts +13 -5
  16. package/src/bun-runtime.ts +22 -1
  17. package/src/cli-help.ts +111 -0
  18. package/src/cli-status.ts +164 -0
  19. package/src/cli.ts +77 -186
  20. package/src/codex-account-store.ts +47 -8
  21. package/src/codex-auth-api.ts +111 -54
  22. package/src/codex-auth-collision.ts +5 -0
  23. package/src/codex-catalog.ts +24 -12
  24. package/src/codex-history-provider.ts +29 -13
  25. package/src/codex-inject.ts +46 -29
  26. package/src/codex-journal.ts +77 -13
  27. package/src/codex-quota.ts +11 -3
  28. package/src/codex-routing.ts +14 -4
  29. package/src/codex-shim.ts +71 -24
  30. package/src/codex-websocket-registry.ts +20 -4
  31. package/src/config.ts +138 -4
  32. package/src/init.ts +7 -2
  33. package/src/oauth/callback-server.ts +22 -15
  34. package/src/oauth/index.ts +18 -4
  35. package/src/oauth/login-cli.ts +8 -1
  36. package/src/oauth/store.ts +2 -1
  37. package/src/process-control.ts +36 -0
  38. package/src/provider-label.ts +8 -0
  39. package/src/responses/parser.ts +18 -1
  40. package/src/router.ts +61 -5
  41. package/src/server.ts +878 -94
  42. package/src/service-secrets.ts +6 -0
  43. package/src/service.ts +293 -28
  44. package/src/types.ts +26 -1
  45. package/src/update.ts +16 -9
  46. package/src/usage-debug.ts +65 -0
  47. package/src/usage-log.ts +62 -0
  48. package/src/usage-summary.ts +0 -0
  49. package/src/ws-bridge.ts +2 -2
  50. package/gui/README.md +0 -73
  51. 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%}
@@ -16,8 +16,8 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-CSUvRNAX.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-CKqUwc02.css">
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.5.6",
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": "src/index.ts",
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": "^4.0.0"
52
+ "zod": "4.4.3"
39
53
  },
40
54
  "devDependencies": {
41
- "@types/bun": "latest",
42
- "typescript": "^5.8.0"
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") || undefined;
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
- return parsed.context.tools.map(t => ({
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 (typeof tc === "object" && "name" in tc) body.tool_choice = { type: "tool", name: toolNames.toWire(tc.name) };
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\/?$/, "");
@@ -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: parsed.context.tools.map(t => ({
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
- return parsed.context.tools.map(t => ({
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,
@@ -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 bundledBunPath() ?? process.execPath;
84
+ return durableBunRuntime().path;
64
85
  }
@@ -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
+ }