@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.
- package/README.md +1 -0
- package/gui/dist/assets/{index-LGqpEmI5.js → index-PrH8v83W.js} +1 -1
- package/gui/dist/assets/{index-DRr-3yL3.css → index-cEIM1XWY.css} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/cli.ts +39 -8
- package/src/codex-catalog.ts +8 -5
- package/src/codex-refresh.ts +49 -0
- package/src/codex-shim.ts +145 -0
- package/src/providers/registry.ts +4 -3
- package/src/server.ts +14 -15
|
@@ -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}}
|
package/gui/dist/index.html
CHANGED
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
} catch (e) {}
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
20
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-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
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 {
|
|
42
|
-
const cat = await
|
|
43
|
-
catalogPath =
|
|
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
|
-
|
|
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();
|
package/src/codex-catalog.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { copyFileSync, existsSync, mkdirSync, readFileSync,
|
|
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
|
-
*
|
|
564
|
-
* Codex caches the model list for 5 min (DEFAULT_MODEL_CACHE_TTL);
|
|
565
|
-
* (enable/disable, subagent reorder) apply on the next turn instead of waiting
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|