@bitkyc08/opencodex 2.0.0 → 2.0.2

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.
@@ -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{padding:5px}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px}.panel-accent{background:linear-gradient(180deg, var(--accent-soft), transparent 120%), var(--surface);border-color:#7c5cff47}.stat-row{grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:28px;display:grid}.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding: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-radius:99px;align-items:center;gap:5px;padding:2px 8px;display:inline-flex}.badge-accent{background:var(--accent-soft);color:var(--accent-hover)}.badge-green{background:var(--green-soft);color:var(--green)}.badge-amber{background:var(--amber-soft);color:var(--amber)}.badge-muted{background:var(--raised);color:var(--muted);border:1px solid var(--border)}.dot{border-radius:50%;flex-shrink:0;width:7px;height:7px}.dot-green{background:var(--green);box-shadow:0 0 0 3px var(--green-soft)}.dot-red{background:var(--red);box-shadow:0 0 0 3px var(--red-soft)}.tbl{border-collapse:collapse;width:100%;font-size:13px}.tbl thead th{text-align:left;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--border);padding:9px 12px;font-size:11.5px;font-weight:600}.tbl tbody td{border-bottom:1px solid var(--border-soft);padding:10px 12px}.tbl tbody tr:last-child td{border-bottom:none}.tbl tbody tr:hover td{background: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}.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}.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{padding:5px}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius)}.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px}.panel-accent{background:linear-gradient(180deg, var(--accent-soft), transparent 120%), var(--surface);border-color:#7c5cff47}.stat-row{grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:28px;display:grid}.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding: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-radius:99px;align-items:center;gap:5px;padding:2px 8px;display:inline-flex}.badge-accent{background:var(--accent-soft);color:var(--accent-hover)}.badge-green{background:var(--green-soft);color:var(--green)}.badge-amber{background:var(--amber-soft);color:var(--amber)}.badge-muted{background:var(--raised);color:var(--muted);border:1px solid var(--border)}.dot{border-radius:50%;flex-shrink:0;width:7px;height:7px}.dot-green{background:var(--green);box-shadow:0 0 0 3px var(--green-soft)}.dot-red{background:var(--red);box-shadow:0 0 0 3px var(--red-soft)}.tbl{border-collapse:collapse;width:100%;font-size:13px}.tbl thead th{text-align:left;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--border);padding:9px 12px;font-size:11.5px;font-weight:600}.tbl tbody td{border-bottom:1px solid var(--border-soft);padding:10px 12px}.tbl tbody tr:last-child td{border-bottom:none}.tbl tbody tr:hover td{background: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}.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}.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}}
@@ -16,8 +16,8 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-LGqpEmI5.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-DRr-3yL3.css">
19
+ <script type="module" crossorigin src="/assets/index-PrH8v83W.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-cEIM1XWY.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Universal provider proxy for OpenAI Codex — use any LLM with Codex CLI/App/SDK",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/cli.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { execFileSync } from "node:child_process";
3
- import { existsSync } from "node:fs";
2
+ import { execFileSync, spawn } from "node:child_process";
4
3
  import { restoreNativeCodex } from "./codex-inject";
5
4
  import { loadConfig, readPid, removePid, writePid } from "./config";
6
5
  import { serviceCommand, stopServiceIfInstalled } from "./service";
@@ -19,6 +18,7 @@ Usage:
19
18
  ocx stop Stop the proxy AND restore native Codex (plain codex works again)
20
19
  ocx restore Restore native Codex without stopping (alias: eject)
21
20
  ocx service <sub> Run as a background service (install|start|stop|status|uninstall)
21
+ ocx codex-shim <sub> Auto-start proxy when \`codex\` launches (install|status|uninstall)
22
22
  ocx sync Fetch models from providers and inject into Codex config
23
23
  ocx status Check proxy server status
24
24
  ocx login <provider> OAuth login (xai) — opens browser, stores token in ~/.opencodex/auth.json
@@ -38,11 +38,10 @@ async function syncModelsToCodex(port?: number) {
38
38
  const p = port ?? config.port ?? 10100;
39
39
  let catalogPath: string | null | undefined;
40
40
  try {
41
- const { invalidateCodexModelsCache, syncCatalogModels } = await import("./codex-catalog");
42
- const cat = await syncCatalogModels(config);
43
- catalogPath = existsSync(cat.path) ? cat.path : null;
41
+ const { refreshCodexModelCatalog } = await import("./codex-refresh");
42
+ const cat = await refreshCodexModelCatalog(config);
43
+ catalogPath = cat.catalogExists ? cat.path : null;
44
44
  if (cat.added > 0) {
45
- invalidateCodexModelsCache();
46
45
  console.log(` + ${cat.added} models appended to Codex catalog (${cat.path})`);
47
46
  } else if (catalogPath === null) {
48
47
  console.error("catalog sync skipped: no Codex catalog source found; keeping Codex's native catalog.");
@@ -56,7 +55,7 @@ async function syncModelsToCodex(port?: number) {
56
55
  return result;
57
56
  }
58
57
 
59
- async function handleStart() {
58
+ async function handleStart(options: { block?: boolean } = {}) {
60
59
  const existingPid = readPid();
61
60
  if (existingPid) {
62
61
  console.error(`⚠️ Proxy already running (PID ${existingPid}). Use 'ocx stop' first.`);
@@ -91,6 +90,10 @@ async function handleStart() {
91
90
 
92
91
  await maybeShowStarPrompt(); // once-only [Y/n] GitHub-star prompt on first interactive start
93
92
  await syncModelsToCodex(port).catch(() => {});
93
+ if (options.block ?? true) {
94
+ setInterval(() => {}, 60_000);
95
+ await new Promise<void>(() => {});
96
+ }
94
97
  }
95
98
 
96
99
  function killProxy(pid: number): void {
@@ -204,7 +207,12 @@ switch (command) {
204
207
  const guiUrl = `http://localhost:${config.port}`;
205
208
  if (!cfg.readPid()) {
206
209
  console.log("Proxy not running. Starting...");
207
- await handleStart();
210
+ const child = spawn(process.execPath, [process.argv[1], "start"], {
211
+ detached: true,
212
+ stdio: "ignore",
213
+ env: process.env,
214
+ });
215
+ child.unref();
208
216
  await new Promise(r => setTimeout(r, 1000));
209
217
  }
210
218
  console.log(`Opening ${guiUrl}`);
@@ -215,6 +223,29 @@ switch (command) {
215
223
  case "service":
216
224
  serviceCommand(args[1]);
217
225
  break;
226
+ case "codex-shim": {
227
+ const { codexShimStatus, installCodexShim, uninstallCodexShim } = await import("./codex-shim");
228
+ switch (args[1]) {
229
+ case "install": {
230
+ const r = installCodexShim();
231
+ console.log(r.installed ? `✅ ${r.message}` : `⚠️ ${r.message}`);
232
+ break;
233
+ }
234
+ case "status":
235
+ console.log(codexShimStatus());
236
+ break;
237
+ case "uninstall":
238
+ case "remove": {
239
+ const r = uninstallCodexShim();
240
+ console.log(r.removed ? `✅ ${r.message}` : `⚠️ ${r.message}`);
241
+ break;
242
+ }
243
+ default:
244
+ console.error("Usage: ocx codex-shim <install|status|uninstall>");
245
+ process.exit(1);
246
+ }
247
+ break;
248
+ }
218
249
  case "update": {
219
250
  const { runUpdate } = await import("./update");
220
251
  runUpdate();
@@ -1,4 +1,4 @@
1
- import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
1
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { atomicWriteFile, websocketsEnabled } from "./config";
@@ -560,12 +560,15 @@ export function restoreCodexCatalog(): { removed: number; kept: number; path: st
560
560
  }
561
561
 
562
562
  /**
563
- * Delete Codex's models cache ($CODEX_HOME/models_cache.json) so the next turn re-fetches /v1/models.
564
- * Codex caches the model list for 5 min (DEFAULT_MODEL_CACHE_TTL); invalidating makes catalog edits
565
- * (enable/disable, subagent reorder) apply on the next turn instead of waiting for the TTL.
563
+ * Refresh Codex's models cache ($CODEX_HOME/models_cache.json) from the active catalog.
564
+ * Codex caches the model list for 5 min (DEFAULT_MODEL_CACHE_TTL); copying the injected catalog
565
+ * makes catalog edits (enable/disable, subagent reorder) apply on the next turn instead of waiting.
566
566
  */
567
567
  export function invalidateCodexModelsCache(): void {
568
568
  try {
569
- if (existsSync(CODEX_MODELS_CACHE_PATH)) unlinkSync(CODEX_MODELS_CACHE_PATH);
569
+ const catalogPath = readCodexCatalogPath();
570
+ if (!existsSync(catalogPath)) return;
571
+ const catalog = readFileSync(catalogPath, "utf8");
572
+ atomicWriteFile(CODEX_MODELS_CACHE_PATH, catalog.endsWith("\n") ? catalog : `${catalog}\n`);
570
573
  } catch { /* best-effort */ }
571
574
  }
@@ -0,0 +1,49 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { invalidateCodexModelsCache, syncCatalogModels } from "./codex-catalog";
3
+ import { CODEX_MODELS_CACHE_PATH } from "./codex-paths";
4
+ import { atomicWriteFile } from "./config";
5
+ import type { OcxConfig } from "./types";
6
+
7
+ export interface CodexCatalogRefreshResult {
8
+ added: number;
9
+ path: string;
10
+ catalogExists: boolean;
11
+ cacheSynced: boolean;
12
+ }
13
+
14
+ interface RefreshDeps {
15
+ syncCatalogModels: typeof syncCatalogModels;
16
+ invalidateCodexModelsCache: typeof invalidateCodexModelsCache;
17
+ syncCodexModelsCacheFromCatalog: typeof syncCodexModelsCacheFromCatalog;
18
+ existsSync: typeof existsSync;
19
+ }
20
+
21
+ const defaultDeps: RefreshDeps = {
22
+ syncCatalogModels,
23
+ invalidateCodexModelsCache,
24
+ syncCodexModelsCacheFromCatalog,
25
+ existsSync,
26
+ };
27
+
28
+ export function syncCodexModelsCacheFromCatalog(catalogPath: string): void {
29
+ const content = readFileSync(catalogPath, "utf8");
30
+ atomicWriteFile(CODEX_MODELS_CACHE_PATH, content);
31
+ }
32
+
33
+ /**
34
+ * Rebuild Codex's on-disk model catalog and keep Codex's models cache aligned
35
+ * when a catalog file exists. Codex Desktop can read models_cache.json directly,
36
+ * so deleting a stale cache is not enough: the cache must be replaced with the
37
+ * same catalog content the CLI debug path reads.
38
+ */
39
+ export async function refreshCodexModelCatalog(
40
+ config: OcxConfig,
41
+ deps: RefreshDeps = defaultDeps,
42
+ ): Promise<CodexCatalogRefreshResult> {
43
+ const result = await deps.syncCatalogModels(config);
44
+ const catalogExists = deps.existsSync(result.path);
45
+ if (!catalogExists) return { ...result, catalogExists, cacheSynced: false };
46
+ deps.invalidateCodexModelsCache();
47
+ deps.syncCodexModelsCacheFromCatalog(result.path);
48
+ return { ...result, catalogExists, cacheSynced: true };
49
+ }
@@ -0,0 +1,145 @@
1
+ import { delimiter, dirname, extname, join } from "node:path";
2
+ import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import { getConfigDir } from "./config";
4
+
5
+ const SHIM_MARKER = "opencodex codex autostart shim";
6
+ const STATE_PATH = join(getConfigDir(), "codex-shim.json");
7
+
8
+ interface ShimState {
9
+ platform: NodeJS.Platform;
10
+ wrapperPath: string;
11
+ originalPath: string;
12
+ backupPath: string;
13
+ }
14
+
15
+ function cliEntry(): { bun: string; cli: string } {
16
+ return { bun: process.execPath, cli: join(import.meta.dir, "cli.ts") };
17
+ }
18
+
19
+ function commandNames(name: string): string[] {
20
+ if (process.platform !== "win32") return [name];
21
+ const exts = (process.env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD;.PS1").split(";").filter(Boolean);
22
+ return [name, ...exts.flatMap(ext => [`${name}${ext.toLowerCase()}`, `${name}${ext.toUpperCase()}`])];
23
+ }
24
+
25
+ function isShim(path: string): boolean {
26
+ try {
27
+ return readFileSync(path, "utf8").includes(SHIM_MARKER);
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ function findCodexOnPath(): string | null {
34
+ for (const dir of (process.env.PATH ?? "").split(delimiter).filter(Boolean)) {
35
+ for (const name of commandNames("codex")) {
36
+ const path = join(dir, name);
37
+ if (!existsSync(path) || isShim(path)) continue;
38
+ try {
39
+ if (!lstatSync(path).isDirectory()) return path;
40
+ } catch {
41
+ continue;
42
+ }
43
+ }
44
+ }
45
+ return null;
46
+ }
47
+
48
+ function backupPathFor(path: string): string {
49
+ const ext = extname(path);
50
+ return ext ? `${path.slice(0, -ext.length)}.opencodex-real${ext}` : `${path}.opencodex-real`;
51
+ }
52
+
53
+ export function buildUnixCodexShim(realCodexPath: string, bunPath: string, cliPath: string): string {
54
+ return `#!/usr/bin/env sh
55
+ # ${SHIM_MARKER}
56
+ if [ -z "$OCX_SHIM_BYPASS" ]; then
57
+ if ! "${bunPath}" "${cliPath}" status 2>/dev/null | grep -q "Proxy running"; then
58
+ mkdir -p "$HOME/.opencodex"
59
+ nohup env OCX_SERVICE=1 "${bunPath}" "${cliPath}" start >> "$HOME/.opencodex/shim.log" 2>&1 &
60
+ i=0
61
+ while [ "$i" -lt 50 ]; do
62
+ if "${bunPath}" "${cliPath}" status 2>/dev/null | grep -q "Proxy running"; then
63
+ break
64
+ fi
65
+ sleep 0.1
66
+ i=$((i + 1))
67
+ done
68
+ fi
69
+ fi
70
+ exec "${realCodexPath}" "$@"
71
+ `;
72
+ }
73
+
74
+ export function buildWindowsCodexShim(realCodexPath: string, bunPath: string, cliPath: string): string {
75
+ return `@echo off\r
76
+ rem ${SHIM_MARKER}\r
77
+ if not "%OCX_SHIM_BYPASS%"=="" goto run_codex\r
78
+ "${bunPath}" "${cliPath}" status 2>nul | findstr /C:"Proxy running" >nul\r
79
+ if %ERRORLEVEL% EQU 0 goto run_codex\r
80
+ if not exist "%USERPROFILE%\\.opencodex" mkdir "%USERPROFILE%\\.opencodex"\r
81
+ start "" /b cmd /c "set OCX_SERVICE=1 && ""${bunPath}"" ""${cliPath}"" start >> ""%USERPROFILE%\\.opencodex\\shim.log"" 2>&1"\r
82
+ for /l %%i in (1,1,50) do (\r
83
+ "${bunPath}" "${cliPath}" status 2>nul | findstr /C:"Proxy running" >nul\r
84
+ if not errorlevel 1 goto run_codex\r
85
+ powershell -NoProfile -Command "Start-Sleep -Milliseconds 100" >nul 2>nul\r
86
+ )\r
87
+ :run_codex\r
88
+ "${realCodexPath}" %*\r
89
+ `;
90
+ }
91
+
92
+ function readState(): ShimState | null {
93
+ try {
94
+ return JSON.parse(readFileSync(STATE_PATH, "utf8")) as ShimState;
95
+ } catch {
96
+ return null;
97
+ }
98
+ }
99
+
100
+ function writeState(state: ShimState): void {
101
+ if (!existsSync(getConfigDir())) mkdirSync(getConfigDir(), { recursive: true });
102
+ writeFileSync(STATE_PATH, JSON.stringify(state, null, 2) + "\n", "utf8");
103
+ }
104
+
105
+ export function installCodexShim(): { installed: boolean; message: string } {
106
+ const existing = readState();
107
+ if (existing && existsSync(existing.wrapperPath) && existsSync(existing.backupPath)) {
108
+ return { installed: false, message: `Codex autostart shim already installed at ${existing.wrapperPath}.` };
109
+ }
110
+
111
+ const originalPath = findCodexOnPath();
112
+ if (!originalPath) return { installed: false, message: "Could not find a codex executable on PATH." };
113
+
114
+ const backupPath = backupPathFor(originalPath);
115
+ if (existsSync(backupPath)) return { installed: false, message: `Refusing to overwrite existing backup: ${backupPath}` };
116
+
117
+ const { bun, cli } = cliEntry();
118
+ const wrapperPath = process.platform === "win32" ? join(dirname(originalPath), "codex.cmd") : originalPath;
119
+ renameSync(originalPath, backupPath);
120
+ if (process.platform === "win32") {
121
+ writeFileSync(wrapperPath, buildWindowsCodexShim(backupPath, bun, cli), "utf8");
122
+ } else {
123
+ writeFileSync(wrapperPath, buildUnixCodexShim(backupPath, bun, cli), "utf8");
124
+ chmodSync(wrapperPath, 0o755);
125
+ }
126
+ writeState({ platform: process.platform, wrapperPath, originalPath, backupPath });
127
+ return { installed: true, message: `Codex autostart shim installed at ${wrapperPath}. Original saved at ${backupPath}.` };
128
+ }
129
+
130
+ export function uninstallCodexShim(): { removed: boolean; message: string } {
131
+ const state = readState();
132
+ if (!state) return { removed: false, message: "Codex autostart shim is not installed." };
133
+ if (existsSync(state.wrapperPath) && isShim(state.wrapperPath)) unlinkSync(state.wrapperPath);
134
+ if (existsSync(state.backupPath) && !existsSync(state.originalPath)) renameSync(state.backupPath, state.originalPath);
135
+ if (existsSync(STATE_PATH)) unlinkSync(STATE_PATH);
136
+ return { removed: true, message: `Codex autostart shim removed. Restored ${state.originalPath}.` };
137
+ }
138
+
139
+ export function codexShimStatus(): string {
140
+ const state = readState();
141
+ if (!state) return "Codex autostart shim is not installed.";
142
+ const wrapper = existsSync(state.wrapperPath) ? "present" : "missing";
143
+ const backup = existsSync(state.backupPath) ? "present" : "missing";
144
+ return `Codex autostart shim: wrapper ${wrapper} at ${state.wrapperPath}; original backup ${backup} at ${state.backupPath}.`;
145
+ }
@@ -201,8 +201,9 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
201
201
  { id: "nvidia", label: "NVIDIA NIM", baseUrl: "https://integrate.api.nvidia.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://build.nvidia.com" },
202
202
  { id: "venice", label: "Venice", baseUrl: "https://api.venice.ai/api/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://venice.ai/settings/api" },
203
203
  {
204
- id: "zai", label: "Z.AI (GLM Coding)", baseUrl: "https://api.z.ai/api/coding/paas/v4", adapter: "openai-chat", authKind: "key",
204
+ id: "zai", label: "Z.AI GLM Coding Plan", baseUrl: "https://api.z.ai/api/coding/paas/v4", adapter: "openai-chat", authKind: "key",
205
205
  dashboardUrl: "https://z.ai/manage-apikey/apikey-list", defaultModel: "glm-5.2",
206
+ note: "GLM-5.2 coding subscription",
206
207
  models: ["glm-5.2", "glm-5.2[1m]", "glm-5.1", "glm-5", "glm-4.6"],
207
208
  noVisionModels: ZAI_GLM_52_MODELS,
208
209
  modelReasoningEfforts: Object.fromEntries(ZAI_GLM_52_MODELS.map(id => [id, ZAI_GLM_52_REASONING_EFFORTS])),
@@ -235,8 +236,8 @@ export const PROVIDER_REGISTRY: readonly ProviderRegistryEntry[] = [
235
236
  ],
236
237
  },
237
238
  { id: "mistral", label: "Mistral", baseUrl: "https://api.mistral.ai/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://console.mistral.ai/api-keys", defaultModel: "codestral-latest" },
238
- { id: "minimax", label: "MiniMax", baseUrl: "https://api.minimax.io/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.minimax.io", defaultModel: "MiniMax-M2.5", jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive" },
239
- { id: "minimax-cn", label: "MiniMax (CN)", baseUrl: "https://api.minimaxi.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.minimaxi.com", defaultModel: "MiniMax-M2.5", jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive" },
239
+ { id: "minimax", label: "MiniMax — Coding Plan", baseUrl: "https://api.minimax.io/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.minimax.io", defaultModel: "MiniMax-M2.5", jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive", note: "Subscription Key or API Key" },
240
+ { id: "minimax-cn", label: "MiniMax — Coding Plan (CN)", baseUrl: "https://api.minimaxi.com/v1", adapter: "openai-chat", authKind: "key", dashboardUrl: "https://platform.minimaxi.com", defaultModel: "MiniMax-M2.5", jawcodeBundle: "minimax", metadataModelIdNormalize: "case-insensitive", note: "中国区 Subscription Key" },
240
241
  {
241
242
  id: "kimi-code", label: "Kimi (coding)", baseUrl: "https://api.kimi.com/coding/v1", adapter: "openai-chat", authKind: "key",
242
243
  dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-k2.7-code",
package/src/server.ts CHANGED
@@ -362,6 +362,15 @@ function jsonResponse(data: unknown, status = 200): Response {
362
362
  }
363
363
 
364
364
  async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): Promise<Response | null> {
365
+ async function refreshCodexCatalogBestEffort(): Promise<void> {
366
+ try {
367
+ const { refreshCodexModelCatalog } = await import("./codex-refresh");
368
+ await refreshCodexModelCatalog(config);
369
+ } catch {
370
+ /* catalog absent */
371
+ }
372
+ }
373
+
365
374
  if (url.pathname === "/api/config" && req.method === "GET") {
366
375
  const safeConfig = JSON.parse(JSON.stringify(config));
367
376
  for (const prov of Object.values(safeConfig.providers as Record<string, OcxProviderConfig>)) {
@@ -406,6 +415,7 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
406
415
  config.providers[name] = prov;
407
416
  if (body.setDefault) config.defaultProvider = name;
408
417
  save(config);
418
+ await refreshCodexCatalogBestEffort();
409
419
  return jsonResponse({ success: true, name });
410
420
  }
411
421
 
@@ -416,11 +426,7 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
416
426
  delete config.providers[name];
417
427
  save(config);
418
428
  // Drop its models from Codex's catalog immediately (re-sync + cache bust) so removal is live.
419
- try {
420
- const { syncCatalogModels, invalidateCodexModelsCache } = await import("./codex-catalog");
421
- await syncCatalogModels(config);
422
- invalidateCodexModelsCache();
423
- } catch { /* catalog absent */ }
429
+ await refreshCodexCatalogBestEffort();
424
430
  return jsonResponse({ success: true });
425
431
  }
426
432
 
@@ -442,11 +448,7 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
442
448
  config.disabledModels = disabled;
443
449
  const { saveConfig: save } = await import("./config");
444
450
  save(config);
445
- try {
446
- const { syncCatalogModels, invalidateCodexModelsCache } = await import("./codex-catalog");
447
- await syncCatalogModels(config);
448
- invalidateCodexModelsCache();
449
- } catch { /* catalog absent */ }
451
+ await refreshCodexCatalogBestEffort();
450
452
  return jsonResponse({ ok: true, disabled });
451
453
  }
452
454
 
@@ -487,11 +489,7 @@ async function handleManagementAPI(req: Request, url: URL, config: OcxConfig): P
487
489
  config.subagentModels = chosen;
488
490
  const { saveConfig: save } = await import("./config");
489
491
  save(config);
490
- try {
491
- const { syncCatalogModels, invalidateCodexModelsCache } = await import("./codex-catalog");
492
- await syncCatalogModels(config);
493
- invalidateCodexModelsCache();
494
- } catch { /* catalog absent */ }
492
+ await refreshCodexCatalogBestEffort();
495
493
  return jsonResponse({ ok: true, applied: chosen });
496
494
  }
497
495
 
@@ -568,6 +566,7 @@ export function startServer(port?: number) {
568
566
 
569
567
  const server = Bun.serve<WsData>({
570
568
  port: listenPort,
569
+ idleTimeout: 255,
571
570
  async fetch(req) {
572
571
  const url = new URL(req.url);
573
572