@bitkyc08/opencodex 2.7.18 → 2.7.20

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.
@@ -0,0 +1 @@
1
+ :root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;--bg:var(--lightningcss-light,#fff)var(--lightningcss-dark,#212121);--rail:var(--lightningcss-light,#f9f9f9)var(--lightningcss-dark,#171717);--surface:var(--lightningcss-light,#fff)var(--lightningcss-dark,#262626);--raised:var(--lightningcss-light,#f4f4f4)var(--lightningcss-dark,#303030);--raised-hover:var(--lightningcss-light,#ececec)var(--lightningcss-dark,#3a3a3a);--border:var(--lightningcss-light,#e6e6e6)var(--lightningcss-dark,#3d3d3d);--border-soft:var(--lightningcss-light,#f0f0f0)var(--lightningcss-dark,#333);--hover:var(--lightningcss-light,#0d0d0d08)var(--lightningcss-dark,#ffffff08);--text:var(--lightningcss-light,#0d0d0d)var(--lightningcss-dark,#ececec);--muted:var(--lightningcss-light,#6e6e6e)var(--lightningcss-dark,#a6a6a6);--faint:var(--lightningcss-light,#707070)var(--lightningcss-dark,#9a9a9a);--accent:var(--lightningcss-light,#0d0d0d)var(--lightningcss-dark,#ececec);--accent-hover:var(--lightningcss-light,#3d3d3d)var(--lightningcss-dark,#fff);--accent-ink:var(--lightningcss-light,#fff)var(--lightningcss-dark,#0d0d0d);--accent-soft:var(--lightningcss-light,#0d0d0d0f)var(--lightningcss-dark,#ffffff17);--accent-ring:var(--lightningcss-light,#0d0d0d4d)var(--lightningcss-dark,#ffffff61);--green:var(--lightningcss-light,#0a7d5c)var(--lightningcss-dark,#4ecb9d);--green-soft:var(--lightningcss-light,#10a37f1a)var(--lightningcss-dark,#4ecb9d21);--red:var(--lightningcss-light,#b91c1c)var(--lightningcss-dark,#f87171);--red-soft:var(--lightningcss-light,#b91c1c17)var(--lightningcss-dark,#f8717121);--amber:var(--lightningcss-light,#9a4a08)var(--lightningcss-dark,#fbbf24);--amber-soft:var(--lightningcss-light,#b453091a)var(--lightningcss-dark,#fbbf2421);--space-0-5:2px;--space-1:4px;--space-1-5:6px;--space-2:8px;--space-3:12px;--space-4:16px;--space-5:20px;--space-6:24px;--space-8:32px;--space-10:40px;--space-12:48px;--space-16:64px;--radius-2xs:4px;--radius:12px;--radius-sm:8px;--radius-xs:6px;--radius-lg:16px;--radius-round:50%;--radius-pill:999px;--font-ui:"OpenAI Sans", "Pretendard Variable", Pretendard, "Noto Sans KR", "Apple SD Gothic Neo", "Malgun Gothic", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;--font-code:ui-monospace, "SFMono-Regular", "Cascadia Code", "JetBrains Mono", "Noto Sans Mono CJK KR", Menlo, Consolas, monospace;--font:var(--font-ui);--mono:var(--font-code);--text-micro:10px;--text-caption:11px;--text-label:12px;--text-control:13px;--text-body:14px;--text-subtitle:16px;--text-title:20px;--text-display:24px;--weight-regular:400;--weight-medium:500;--weight-semibold:600;--weight-bold:700;--leading-tight:1.2;--leading-ui:1.35;--leading-body:1.5;--leading-relaxed:1.6;--tracking-normal:0;--tracking-wide:.04em;--control-sm:28px;--control-md:34px;--control-lg:40px;--control-touch:44px;--icon-sm:14px;--icon-md:16px;--icon-lg:20px;--motion-fast:.12s;--motion-normal:.18s;--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);--toggle-w:36px;--toggle-h:20px;--toggle-dot:14px;--toggle-off-bg:var(--lightningcss-light,#d4d4d4)var(--lightningcss-dark,#4a4a4a);--toggle-on-bg:var(--lightningcss-light,#0d0d0d)var(--lightningcss-dark,#4ecb9d);--toggle-dot-color:var(--lightningcss-light,#fff)var(--lightningcss-dark,#0d0d0d)}@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}:root{--glass-rail:var(--lightningcss-light,#f9f9f9a8)var(--lightningcss-dark,#1717179e);--glass-panel:var(--lightningcss-light,#ffffffc7)var(--lightningcss-dark,#262626d1);--glass-blur:saturate(1.6) blur(22px)}*{box-sizing:border-box}html,body,#root{height:100%}html{background:var(--bg);overflow-x:hidden}body{color:var(--text);font-family:var(--font-ui);font-size:var(--text-body);line-height:var(--leading-body);-webkit-font-smoothing:antialiased;text-rendering:optimizelegibility;background:0 0;margin:0;overflow-x:hidden}body:before{content:"";z-index:-1;pointer-events:none;filter:blur(70px);background:radial-gradient(42% 38% at 12% 6%,var(--lightningcss-light,#a4c4ff6b)var(--lightningcss-dark,#6082c829),#0000 70%),radial-gradient(46% 42% at 88% 18%,var(--lightningcss-light,#a8e2c561)var(--lightningcss-dark,#58a08221),#0000 70%),radial-gradient(40% 36% at 70% 92%,var(--lightningcss-light,#ffe0c24d)var(--lightningcss-dark,#b48c6414),#0000 72%);position:fixed;inset:-20%}a{color:var(--text);text-decoration:underline;-webkit-text-decoration-color:var(--faint);text-decoration-color:var(--faint);text-underline-offset:2px}a:hover{-webkit-text-decoration-color:var(--text);text-decoration-color:var(--text)}code{font-family:var(--font-code);font-size:var(--text-label)}.mono{font-family:var(--font-code);font-variant-numeric:tabular-nums}h1,h2,h3,h4{font-weight:var(--weight-semibold);letter-spacing:0;line-height:var(--leading-tight);margin:0}.text-micro{line-height:var(--leading-ui);font-size:var(--text-micro)!important}.text-caption{line-height:var(--leading-ui);font-size:var(--text-caption)!important}.text-label{line-height:var(--leading-ui);font-size:var(--text-label)!important}.text-control{line-height:var(--leading-ui);font-size:var(--text-control)!important}.text-body{line-height:var(--leading-body);font-size:var(--text-body)!important}.text-subtitle{line-height:var(--leading-tight);font-size:var(--text-subtitle)!important}.text-title{line-height:var(--leading-tight);font-size:var(--text-title)!important}.text-display{line-height:var(--leading-tight);font-size:var(--text-display)!important}.font-regular{font-weight:var(--weight-regular)!important}.font-medium{font-weight:var(--weight-medium)!important}.font-semibold{font-weight:var(--weight-semibold)!important}.font-bold{font-weight:var(--weight-bold)!important}.leading-tight{line-height:var(--leading-tight)!important}.leading-ui{line-height:var(--leading-ui)!important}.leading-body{line-height:var(--leading-body)!important}.leading-relaxed{line-height:var(--leading-relaxed)!important}::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-radius:var(--radius-pill);border:2px solid var(--bg)}::-webkit-scrollbar-thumb:hover{background:var(--faint)}:focus-visible{outline:2px solid var(--accent-ring);outline-offset:2px;border-radius:var(--radius-2xs)}.app{grid-template-columns:232px 1fr;min-height:100dvh;display:grid}.sidebar{border-right:1px solid var(--border);background:var(--glass-rail);height:100dvh;-webkit-backdrop-filter:var(--glass-blur);flex-direction:column;align-self:start;gap:4px;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{font-weight:var(--weight-semibold);font-size:var(--text-subtitle);letter-spacing:0;line-height:26px}.brand .ver{font-family:var(--font-code);font-size:var(--text-micro);color:var(--muted);line-height:var(--leading-tight);background:var(--raised);border:1px solid var(--border);border-radius:var(--radius-pill);align-self:center;padding:2px 6px}.sidebar nav{flex-direction:column;gap:4px;display:flex}.nav-item{border-radius:var(--radius-sm);text-align:left;cursor:pointer;width:100%;color:var(--muted);font:inherit;font-size:var(--text-control);font-weight:var(--weight-medium);transition:background var(--motion-fast), color var(--motion-fast);background:0 0;border:none;align-items:center;gap:10px;padding:8px 10px;display:flex}.nav-item:hover,.nav-item.active{background:var(--accent-soft);color:var(--text)}.nav-item.active{font-weight:var(--weight-semibold)}.nav-item svg{width:17px;height:17px;color:var(--faint);flex-shrink:0}.nav-item.active svg{color:var(--text)}.sidebar-foot{flex-direction:column;gap:2px;margin-top:auto;padding-top:12px;display:flex}.drawer-head{align-items:flex-start;display:flex}.drawer-head .brand{flex:auto;min-width:0}.mobile-topbar{display:none}.menu-toggle{border-radius:var(--radius-sm);min-width:44px;min-height:44px;color:var(--muted);cursor:pointer;transition:background var(--motion-fast), color var(--motion-fast);background:0 0;border:none;justify-content:center;align-items:center;padding:8px;display:none}.menu-toggle:hover{background:var(--accent-soft);color:var(--text)}.menu-toggle svg{width:20px;height:20px}.drawer-scrim{z-index:30;background:var(--lightningcss-light,#14141452)var(--lightningcss-dark,#00000085);display:none;position:fixed;inset:0}.sidebar-link{color:var(--muted);font-size:var(--text-control);border-radius:var(--radius-sm);align-items:center;gap:9px;padding:8px 10px;text-decoration:none;display:flex}.sidebar-link:hover{background:var(--accent-soft);color:var(--text);text-decoration:none}.sidebar-link svg{width:16px;height:16px}.lang-toggle{width:100%;color:var(--muted);font-size:var(--text-control);border-radius:var(--radius-sm);transition:background var(--motion-fast), color var(--motion-fast);align-items:center;gap:9px;padding:8px 10px;display:flex;position:relative}.lang-toggle:hover{background:var(--accent-soft);color:var(--text)}.lang-toggle svg{flex-shrink:0;width:16px;height:16px}.lang-toggle .custom-select{width:100%;position:static!important}.lang-toggle .select-trigger{width:100%;color:inherit;font-size:var(--text-control);min-height:auto;box-shadow:none;-webkit-backdrop-filter:none;background:0 0;border:none;justify-content:space-between;padding:0}.lang-toggle .select-trigger:hover:not(:disabled){color:inherit;box-shadow:none;background:0 0;border:none}.lang-toggle .select-dropdown{background:var(--glass-rail);-webkit-backdrop-filter:var(--glass-blur);border:1px solid var(--border);box-shadow:var(--shadow-sm)}.lang-toggle .select-dropdown-beside{min-width:10rem;top:0;left:calc(100% + 6px);right:auto}.theme-toggle{text-align:left;cursor:pointer;width:100%;color:var(--muted);font:inherit;font-size:var(--text-control);border-radius:var(--radius-sm);transition:background var(--motion-fast), color var(--motion-fast);background:0 0;border:none;align-items:center;gap:9px;padding:8px 10px;display:flex}.theme-toggle:hover{background:var(--accent-soft);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:var(--text-title)}.page-sub{color:var(--muted);font-size:var(--text-body);max-width:70ch;margin:4px 0 22px}.page-sub b{color:var(--text);font-weight:var(--weight-semibold)}.api-page h2 svg{vertical-align:-.16em;width:1em;height:1em}.api-page .page-sub code{font-size:var(--text-label);color:var(--text);background:var(--raised);border:1px solid var(--border-soft);border-radius:var(--radius-xs);padding:1px 4px}.btn{border-radius:var(--radius-pill);font:inherit;font-size:var(--text-control);font-weight:var(--weight-medium);line-height:var(--leading-ui);cursor:pointer;transition:background var(--motion-fast), border-color var(--motion-fast), opacity var(--motion-fast);white-space:nowrap;border:1px solid #0000;justify-content:center;align-items:center;gap:7px;padding:8px 16px;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(--bg);color:var(--text);border-color:var(--border)}.btn-ghost:hover:not(:disabled){background:var(--raised)}.btn-danger{color:var(--red);background:0 0;border-color:#f871714d}.btn-danger:hover:not(:disabled){background:var(--red-soft)}.btn-sm{font-size:var(--text-label);border-radius:var(--radius-pill);padding:4px 12px}.btn-icon{appearance:none;font:inherit;cursor:pointer;width:28px;height:28px;color:var(--muted);border-radius:var(--radius-sm);transition:background var(--motion-fast), color var(--motion-fast);background:0 0;border:none;justify-content:center;align-items:center;padding:0;display:inline-flex}.btn-icon:hover{background:var(--raised-hover);color:var(--text)}.btn-icon:focus-visible{outline:2px solid var(--accent-ring);outline-offset:1px}.btn-icon svg{width:16px;height:16px;display:block}.btn.btn-ghost.btn-icon{width:28px;height:28px;padding:0}.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);min-width:0}.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:18px}.panel-accent{border-color:color-mix(in srgb, var(--accent) 28%, var(--border));background:color-mix(in srgb, var(--accent) 5%, var(--surface))}.api-panel{flex-direction:column;gap:10px;display:flex;overflow:hidden}.api-panel .panel-title,.api-panel .muted{margin:0}.api-form-row{align-items:center;gap:8px;min-width:0;display:flex}.api-form-row .input{flex:1;min-width:0}.api-code{min-width:0;color:var(--text);background:var(--raised);border:1px solid var(--border-soft);border-radius:var(--radius-sm);font-family:var(--font-code);font-size:var(--text-label);line-height:var(--leading-relaxed);white-space:pre;margin:0;padding:9px 11px;display:block;overflow-x:auto}.api-code-inline{white-space:nowrap}.api-actions{justify-content:flex-end;align-items:center;gap:4px;display:flex}.maintenance-head{flex-wrap:wrap;align-items:flex-start}.maintenance-actions{flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:8px;display:flex}.maintenance-notice{align-items:flex-start;margin:14px 0 0}.update-row{justify-content:space-between;align-items:center;gap:12px;margin-bottom:14px;display:flex}.update-row .field-label{margin:0}.update-empty{margin-bottom:14px;padding:18px}.update-box{flex-direction:column;gap:12px;display:flex}.update-command{font-size:var(--text-label);flex-wrap:wrap;align-items:flex-start;gap:6px;display:flex}.update-command .chip{white-space:pre-wrap;word-break:break-all;overflow-wrap:anywhere;flex:auto;min-width:0;max-width:100%;line-height:1.5}.update-recheck{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.update-recheck .btn{flex:none}.update-recheck-reason{min-width:0;font-size:var(--text-label);color:var(--muted);flex:200px}.update-restart{border-top:1px solid var(--border-soft);padding-top:12px}.injection-head{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.injection-label{font-weight:var(--weight-semibold)}@media (width<=800px){.injection-head{gap:8px}.injection-label{flex:1 0 calc(100% - 70px);order:1}.injection-head>.badge{order:1;margin-left:auto}.injection-head>.custom-select{order:2}}.stat-row{grid-template-columns:repeat(6,1fr);gap:12px;margin-bottom:28px;display:grid}.stat-row>.stat{flex-direction:column;justify-content:center;min-height:80px;display:flex}@media (width<=1100px){.stat-row{grid-template-columns:repeat(3,1fr)}}@media (width<=640px){.stat-row{grid-template-columns:repeat(2,1fr)}}.stat{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);transition:border-color var(--motion-fast);padding:14px 16px}.stat:hover{border-color:var(--faint)}.stat .label{font-size:var(--text-label);color:var(--muted);font-weight:var(--weight-medium);align-items:center;gap:6px;margin-bottom:6px;display:flex}.stat .label svg{width:14px;height:14px}.stat .value{font-size:var(--text-title);font-weight:var(--weight-semibold);letter-spacing:0;line-height:var(--leading-tight)}.stat .value.mono{font-family:var(--font-code);font-size:var(--text-subtitle)}.model-group-head{font-size:var(--text-control);font-weight:var(--weight-semibold);color:var(--text);align-items:baseline;gap:8px;margin:0 0 8px;display:flex}.model-group-head .count{font-family:var(--font-code);font-weight:var(--weight-medium);color:var(--faint);font-size:var(--text-label)}.group-head{cursor:pointer;background:var(--surface);transition:background var(--motion-fast);padding:10px 12px}.group-head:hover{background:var(--hover)}.group-head.open{border-bottom:1px solid var(--border-soft)}.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);transition:border-color var(--motion-fast), background var(--motion-fast);padding:10px 12px}.model-card:hover{border-color:var(--faint);background:var(--hover)}.model-card .id{font-family:var(--font-code);font-weight:var(--weight-semibold);font-size:var(--text-control);letter-spacing:0;color:var(--text)}.badge{font-size:var(--text-caption);font-weight:var(--weight-semibold);line-height:var(--leading-ui);border-radius:var(--radius-pill);font-family:var(--font-code);letter-spacing:0;border:1px solid #0000;align-items:center;gap:5px;padding:2px 8px;display:inline-flex}.badge-accent{background:var(--accent-soft);color:var(--text)}.badge-green{background:var(--green-soft);color:var(--green)}.badge-amber{background:var(--amber-soft);color:var(--amber)}.badge-muted{background:var(--raised);color:var(--muted);border:1px solid var(--border)}.badge-clickable{cursor:pointer;transition:filter var(--motion-fast);appearance:none}.badge-clickable:hover{filter:brightness(1.1)}.badge-disabled{opacity:.5;cursor:default}.card-badges{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;display:inline-flex}.card-badges .badge{flex-shrink:0}.confirm-icon{border-radius:var(--radius-round);background:var(--amber-soft);width:44px;height:44px;color:var(--amber);justify-content:center;align-items:center;margin:0 auto 12px;display:flex}.credit-list{flex-direction:column;gap:6px;display:flex}.credit-item{border:1px solid var(--border);border-radius:var(--radius-sm);background:var(--raised);transition:border-color var(--motion-fast);padding:8px 10px}.credit-next{border-color:var(--amber);background:var(--amber-soft)}.credit-item-head{color:var(--text);font-size:var(--text-label);font-weight:var(--weight-semibold);align-items:center;gap:6px;margin-bottom:3px;display:flex}.credit-item-head svg{color:var(--amber);flex-shrink:0}.credit-item-dates{font-size:var(--text-caption);font-family:var(--font-code);color:var(--muted);justify-content:space-between;display:flex}.credit-urgent{color:var(--red);font-weight:var(--weight-semibold)}.dot{border-radius:var(--radius-round);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:var(--text-control)}.tbl thead th{text-align:left;color:var(--muted);font-weight:var(--weight-medium);font-size:var(--text-label);border-bottom:1px solid var(--border);padding:9px 12px}.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(--font-code);font-variant-numeric:tabular-nums}.tbl-wrap{border:1px solid var(--border);border-radius:var(--radius);background:var(--surface);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;font-size:var(--text-control);line-height:var(--leading-ui);transition:border-color var(--motion-fast);padding:8px 11px}.input::placeholder{color:var(--faint)}.input:focus{border-color:var(--faint);box-shadow:0 0 0 3px var(--accent-soft);outline:none}textarea.input{resize:vertical;font-family:var(--font-code);line-height:var(--leading-relaxed)}.field-label{font-size:var(--text-label);color:var(--muted);font-weight:var(--weight-medium);margin-bottom:5px;display:block}select.input{appearance:none}.select-sm{border-radius:var(--radius-pill);background:color-mix(in oklab, canvas 75%, transparent);-webkit-backdrop-filter:blur(12px)saturate(1.3);border:1px solid var(--border);color:var(--text);font:inherit;font-size:var(--text-control);line-height:var(--leading-ui);cursor:pointer;transition:border-color var(--motion-fast), box-shadow var(--motion-fast);padding:6px 12px;box-shadow:0 2px 8px #0000000f}.select-sm:focus{border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);outline:none}.select-sm:hover:not(:disabled){border-color:var(--faint);box-shadow:0 2px 12px #0000001a}.select-sm:disabled{opacity:.5;cursor:default;-webkit-backdrop-filter:none}.select-trigger{border-radius:var(--radius-pill);background:color-mix(in oklab, canvas 75%, transparent);-webkit-backdrop-filter:blur(12px)saturate(1.3);border:1px solid var(--border);color:var(--text);font:inherit;font-size:var(--text-control);line-height:var(--leading-ui);cursor:pointer;transition:border-color var(--motion-fast), box-shadow var(--motion-fast);white-space:nowrap;align-items:center;gap:6px;padding:6px 12px;display:inline-flex;box-shadow:0 2px 8px #0000000f}.select-trigger:hover:not(:disabled){border-color:var(--faint);box-shadow:0 2px 12px #0000001a}.select-trigger:disabled{opacity:.5;cursor:default}.select-dropdown{z-index:40;background:color-mix(in oklab, canvas 84%, transparent);-webkit-backdrop-filter:blur(20px)saturate(1.4);border:1px solid var(--border);border-radius:var(--radius);min-width:100%;max-height:280px;padding:4px;position:absolute;top:calc(100% + 4px);left:0;overflow-y:auto;box-shadow:0 8px 32px #00000029}.select-dropdown-right{right:0;left:auto!important}.select-dropdown-beside{min-width:10rem;top:0;left:calc(100% + 6px);right:auto}.select-option{text-align:left;border-radius:var(--radius-sm);width:100%;color:var(--text);font:inherit;font-size:var(--text-control);line-height:var(--leading-ui);cursor:pointer;transition:background var(--motion-fast);white-space:nowrap;background:0 0;border:none;padding:7px 12px;display:block}.select-option:hover{background:var(--hover)}.select-option.active{background:var(--accent-soft);color:var(--accent);font-weight:var(--weight-semibold)}.switch{border-radius:var(--radius-pill);border:1px solid var(--border);cursor:pointer;background:var(--raised);vertical-align:middle;width:34px;height:20px;transition:background var(--motion-normal), border-color var(--motion-normal);appearance:none;flex-shrink:0;align-items:center;padding:2px;line-height:0;display:inline-flex}.switch.on{background:var(--toggle-on-bg);border-color:var(--toggle-on-bg)}.switch:disabled{opacity:.6;cursor:default}.switch .knob{border-radius:var(--radius-round);width:14px;height:14px;transition:transform var(--motion-normal), background var(--motion-normal);background:var(--lightningcss-light,#fff)var(--lightningcss-dark,#ececec);transform:translate(0);box-shadow:0 0 0 1px #10182814,0 1px 1px #1018282e}.switch.on .knob{background:var(--toggle-dot-color);transform:translate(14px);box-shadow:0 0 0 1px #0000001f}.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}.setting-hint{font-size:var(--text-control);line-height:var(--leading-body);margin-top:3px}.stack{flex-direction:column;display:flex}.chip{font-family:var(--font-code);font-size:var(--text-label);line-height:var(--leading-ui);background:var(--raised);border:1px solid var(--border);border-radius:var(--radius-xs);color:var(--text);padding:1px 7px}.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);font-weight:var(--weight-semibold);margin-bottom:6px}.notice{font-size:var(--text-control);line-height:var(--leading-body);border-radius:var(--radius-sm);align-items:center;gap:8px;margin-bottom:14px;padding:9px 12px;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{font-size:var(--text-control);font-weight:var(--weight-semibold);line-height:var(--leading-ui);color:var(--text);align-items:center;gap:8px;margin:30px 0 12px;display:flex}.h-section .count{color:var(--muted);font-weight:var(--weight-medium);font-family:var(--font-code);font-size:var(--text-label)}.spin{border:2px solid var(--border);border-top-color:var(--accent);border-radius:var(--radius-round);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}}@supports not ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){.sidebar,.lang-toggle .select-dropdown{background:var(--rail)}.modal-card{background:var(--surface)}}@media (prefers-reduced-transparency:reduce){body:before{display:none}.sidebar,.lang-toggle .select-dropdown{background:var(--rail);-webkit-backdrop-filter:none}.modal-card{background:var(--surface);-webkit-backdrop-filter:none}.modal-overlay{-webkit-backdrop-filter:none}}.modal-overlay{-webkit-backdrop-filter:blur(40px)saturate(1.2);z-index:50;background:var(--lightningcss-light,#11131cc7)var(--lightningcss-dark,#000000d1);justify-content:center;align-items:flex-start;padding:8vh 16px;display:flex;position:fixed;inset:0}.modal-card{background:color-mix(in oklab, canvas 92%, transparent);-webkit-backdrop-filter:blur(20px)saturate(1.4);border:1px solid var(--border);border-radius:var(--radius-lg);width:100%;max-width:520px;max-height:84vh;padding:20px;overflow-y:auto;box-shadow:0 8px 32px #0000002e}.modal-head{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.modal-head h3{font-size:var(--text-subtitle)}.card-head{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;padding:14px 16px 6px;display:flex}.card-head strong{overflow-wrap:anywhere;min-width:0}.card-sub{font-size:var(--text-label);line-height:var(--leading-body);color:var(--muted);overflow-wrap:anywhere;min-width:0;padding:0 16px 10px}.card-active{border-color:var(--accent-ring)}.card-right{font-size:var(--text-caption);color:var(--faint);align-items:center;gap:4px;margin-left:auto;display:flex}.btn-icon-danger.card-right{appearance:none;color:var(--red);cursor:pointer;background:0 0;border:1px solid #0000;justify-content:center}.btn-icon-danger.card-right:hover{background:var(--red-soft);border-color:var(--red-soft);color:var(--red)}.card-row{justify-content:space-between;align-items:center;padding:14px 16px;display:flex}.badge-primary{background:var(--accent-soft);color:var(--accent-hover)}.dot-blue{background:var(--accent);box-shadow:0 0 0 3px var(--accent-soft)}.dot-muted{background:var(--muted)}.dot-amber{background:var(--amber);box-shadow:0 0 0 3px var(--amber-soft)}.section-sep{align-items:center;gap:10px;margin:20px 0 12px;display:flex}.section-label{font-size:var(--text-label);color:var(--muted);font-weight:var(--weight-medium);white-space:nowrap}.sep-line{background:var(--border);flex:1;height:1px}.quota-compact{gap:4px;padding:2px 16px 12px;display:grid}.quota-row{grid-template-columns:minmax(34px,max-content) 34px minmax(34px,44px) minmax(38px,42px) minmax(58px,1fr) minmax(30px,max-content);align-items:center;gap:8px;min-width:0;display:grid}.quota-label{font-size:var(--text-caption);color:var(--muted);font-weight:var(--weight-semibold)}.quota-val{font-size:var(--text-caption);font-family:var(--font-code);color:var(--text);text-align:right}.quota-reset-label{text-overflow:ellipsis;white-space:nowrap;font-size:var(--text-caption);color:var(--faint);overflow:hidden}.quota-reset-day,.quota-reset-time{font-size:var(--text-caption);color:var(--muted);white-space:nowrap}.quota-reset-time{font-family:var(--font-code)}.bar{background:var(--raised);border-radius:var(--radius-2xs);min-width:0;height:5px;overflow:hidden}.bar-fill{border-radius:var(--radius-2xs);height:100%;transition:width var(--motion-normal)}.bar-green{background:var(--green)}.bar-amber{background:linear-gradient(90deg, var(--green), var(--amber))}.toggle{width:var(--toggle-w);height:var(--toggle-h);border-radius:var(--radius-pill);background:var(--toggle-off-bg);border:1px solid var(--border);cursor:pointer;transition:background var(--motion-normal), border-color var(--motion-normal);flex-shrink:0;position:relative}.toggle.on{background:var(--toggle-on-bg);border-color:var(--toggle-on-bg)}.toggle-knob{border-radius:var(--radius-round);width:16px;height:16px;transition:left var(--motion-normal), background var(--motion-normal);background:var(--lightningcss-light,#fff)var(--lightningcss-dark,#ececec);position:absolute;top:1px;left:1px}.toggle.on .toggle-knob{background:var(--toggle-dot-color);left:17px}.notice-warn{font-size:var(--text-label);line-height:var(--leading-body);border-radius:var(--radius-sm);background:var(--amber-soft);color:var(--amber);align-items:center;gap:6px;margin-bottom:12px;padding:8px 10px;display:flex}.notice-warn svg{flex-shrink:0;width:14px;height:14px}.modal-desc{font-size:var(--text-control);line-height:var(--leading-body);color:var(--muted);margin-bottom:14px}.modal-actions{gap:8px;margin-top:16px;display:flex}.modal-actions .btn{flex:1}.log-reqid{-webkit-line-clamp:2;word-break:break-all;-webkit-box-orient:vertical;max-width:14ch;display:-webkit-box;overflow:hidden}.log-col-model{overflow-wrap:break-word;word-break:normal;max-width:18ch}.log-col-tokens{min-width:10ch}.log-status-cell{flex-direction:column;align-items:flex-start;gap:2px;display:inline-flex}.log-detail-btn{cursor:pointer;color:var(--accent-hover);font:inherit;font-size:var(--text-caption);white-space:nowrap;background:0 0;border:none;padding:0;text-decoration:underline}.log-detail-grid{font-size:var(--text-control);grid-template-columns:max-content minmax(0,1fr);gap:6px 14px;display:grid}.log-detail-break{word-break:break-all}.log-detail-json{background:var(--raised);border:1px solid var(--border-soft);border-radius:var(--radius-sm);font-family:var(--font-code);font-size:var(--text-caption);line-height:var(--leading-body);white-space:pre-wrap;word-break:break-all;max-height:40vh;margin:0;padding:10px 12px;overflow:auto}.setup-guide{font-size:var(--text-control);line-height:var(--leading-body);border:1px solid var(--border);border-radius:var(--radius-sm);margin-bottom:4px;padding:8px 12px}.setup-guide summary{cursor:pointer;color:var(--accent-hover);font-weight:var(--weight-medium)}.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;transition:background var(--motion-fast), border-color var(--motion-fast);justify-content:space-between;align-items:center;gap:10px;padding:11px 13px;display:flex}.list-row:hover{background:var(--raised-hover);border-color:var(--accent-ring)}.list-row .title{font-weight:var(--weight-semibold);font-size:var(--text-body)}.list-row .sub{font-size:var(--text-label);color:var(--muted);margin-top:2px}.prov-card{overflow:hidden}.prov-card-main{flex-wrap:wrap;justify-content:space-between;align-items:flex-start;gap:12px;padding:15px 16px;display:flex}.prov-card-disabled{opacity:.62}.prov-card-info{flex:360px;align-items:flex-start;gap:11px;min-width:0;display:flex}.prov-card-copy{flex:1;min-width:0}.prov-title{flex-wrap:wrap;align-items:center;gap:8px;min-width:0;margin-bottom:5px;display:flex}.provider-icon{border-radius:var(--radius-xs);background:var(--raised);border:1px solid var(--border-soft);width:31px;height:31px;color:var(--text);flex:none;justify-content:center;align-items:center;display:inline-flex}.provider-icon img{object-fit:contain;width:19px;height:19px;display:block}.prov-meta{flex-wrap:wrap;align-items:center;gap:5px;min-width:0;display:flex}.prov-meta>span{text-overflow:ellipsis;white-space:nowrap;min-width:0;max-width:100%;overflow:hidden}.provider-quota{padding-left:58px}.provider-actions{flex-shrink:0;align-items:center;gap:8px;margin-left:auto;display:flex}.link-btn{color:var(--accent-hover);font:inherit;font-size:var(--text-control);cursor:pointer;background:0 0;border:none;padding:6px 2px;text-decoration:underline}.prov-accounts-toggle{border:none;border-top:1px solid var(--border-soft);width:100%;color:var(--muted);font:inherit;font-size:var(--text-label);cursor:pointer;background:0 0;justify-content:center;align-items:center;gap:6px;min-height:24px;padding:4px 0;display:flex}.prov-accounts-toggle:hover{color:var(--text);background:var(--raised)}.prov-accounts-toggle .chev{transition:transform var(--motion-normal) ease;display:inline-flex}.prov-accounts-toggle .chev svg{width:12px;height:12px;transform:rotate(90deg)}.prov-accounts-toggle.open .chev svg{transform:rotate(-90deg)}.prov-accounts-list{border-top:1px solid var(--border-soft);flex-direction:column;gap:2px;padding:6px 16px 10px;display:flex}.prov-account-row{text-align:left;border-radius:var(--radius-xs);width:100%;color:var(--text);font:inherit;font-size:var(--text-control);line-height:var(--leading-ui);cursor:pointer;background:0 0;border:none;align-items:center;gap:8px;min-height:32px;padding:6px 8px;display:flex}.prov-account-row:hover{background:var(--raised)}.prov-account-row.active{cursor:default}.prov-account-row .prov-account-email{text-overflow:ellipsis;white-space:nowrap;flex:auto;min-width:0;overflow:hidden}.prov-account-row .badge{flex:none}.prov-account-remove{color:var(--muted);cursor:pointer;border-radius:var(--radius-xs);background:0 0;border:none;flex:none;justify-content:center;align-items:center;padding:4px;display:inline-flex}.prov-account-remove:hover{color:var(--red);background:var(--red-soft)}.prov-account-add{color:var(--muted);font-size:var(--text-label)}.prov-account-add:hover{color:var(--accent-hover)}.prov-account-keyform{cursor:default;gap:6px}.prov-account-keyform:hover{background:0 0}.prov-account-keyform .input-sm{min-width:0;font-size:var(--text-label);height:var(--control-sm);flex:auto;padding:4px 8px}.oauth-grid{grid-template-columns:minmax(140px,max-content) minmax(0,1fr) max-content;align-items:center;gap:10px 16px;display:grid}.oauth-row{grid-column:1/-1;grid-template-columns:subgrid;align-items:center;min-height:30px;display:grid}.oauth-name{font-size:var(--text-control);font-weight:var(--weight-semibold);align-items:center;gap:8px;min-width:0;display:inline-flex}.oauth-name-text{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.provider-icon-sm{border-radius:var(--radius-xs);width:24px;height:24px}.provider-icon-sm img{width:15px;height:15px}.oauth-status{font-size:var(--text-control);align-items:center;gap:7px;min-width:0;display:inline-flex}.oauth-email{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.oauth-actions{justify-content:flex-end;align-items:center;gap:8px;min-width:0;display:inline-flex}.oauth-login-hint{font-size:var(--text-label);line-height:var(--leading-body);flex-direction:column;grid-column:1/-1;align-items:stretch;gap:8px;display:flex}.oauth-login-hint-links{flex-wrap:wrap;align-items:center;gap:8px;display:inline-flex}.oauth-login-paste{align-items:center;gap:8px;width:100%;display:flex}.oauth-login-paste .input{min-width:0;font-size:var(--text-label);flex:1;padding:6px 10px}@media (width<=760px){.app{grid-template-rows:auto 1fr;grid-template-columns:1fr}.mobile-topbar{z-index:20;border-bottom:1px solid var(--border);background:var(--glass-rail);min-width:0;-webkit-backdrop-filter:var(--glass-blur);align-items:center;gap:2px;padding:4px 10px;display:flex;position:sticky;top:0}.mobile-topbar .brand{flex:auto;min-width:0;padding:4px}.mobile-topbar .stop-toggle{justify-content:center;width:auto;min-width:44px;min-height:44px;padding:8px}.menu-toggle{display:inline-flex}.sidebar{z-index:40;width:min(280px,84vw);height:100dvh;padding-bottom:calc(18px + env(safe-area-inset-bottom));visibility:hidden;transition:transform var(--motion-normal) ease, visibility var(--motion-normal);background:var(--lightningcss-light,#f9f9f9f7)var(--lightningcss-dark,#171717f5);outline:none;position:fixed;top:0;bottom:0;left:0;overflow-y:auto;transform:translate(-100%)}.sidebar.open{visibility:visible;transform:translate(0);box-shadow:0 12px 40px var(--lightningcss-light,#14141438)var(--lightningcss-dark,#00000080)}.drawer-scrim{display:block}.main-inner{padding:22px 18px 48px}.setting-row{flex-wrap:wrap}.setting-row .setting-copy{flex:100%!important}.setting-row .setting-controls{width:100%}.setting-row .setting-controls .select-sm{flex:1 1 0;min-width:0}.setting-row>.select-sm{width:100%}.api-form-row{flex-direction:column;align-items:stretch}.api-form-row .btn{width:100%;min-height:40px}.stat-row>.stat{min-width:100px}.tbl{min-width:460px}.usage-cards{grid-template-columns:repeat(2,minmax(0,1fr))!important}.provider-quota{padding-left:16px}.prov-meta{grid-template-columns:max-content minmax(0,1fr);width:100%;display:grid}.prov-meta .chip~span:not(:last-child){display:none}.oauth-grid{grid-template-columns:minmax(0,auto) minmax(0,1fr) max-content;column-gap:8px}.sidebar .lang-toggle .select-dropdown-beside{inset:auto auto calc(100% + 6px) 0}}.usage-cards{grid-template-columns:repeat(3,minmax(0,1fr));gap:12px;margin-top:8px;display:grid}.usage-cards .stat-value{font-size:var(--text-title);font-weight:var(--weight-semibold);margin-top:4px}.usage-range{border:1px solid var(--border);border-radius:var(--radius-pill);background:var(--surface);gap:2px;padding:2px;display:inline-flex}.usage-range-btn{color:var(--muted);border-radius:var(--radius-pill);cursor:pointer;font:inherit;background:0 0;border:none;padding:4px 12px}.usage-range-btn.active{background:var(--raised);color:var(--text);font-weight:var(--weight-semibold)}.panel-title{font-size:var(--text-body);font-weight:var(--weight-semibold);color:var(--text);margin:0 0 12px}.panel-head{justify-content:space-between;align-items:center;gap:12px;margin-bottom:12px;display:flex}.panel-head .panel-title{margin:0}.panel-head .input{max-width:220px}.heatmap{--hm-cell:11px;--hm-gap:3px;flex-direction:column;gap:6px;padding-bottom:4px;display:flex;overflow-x:auto}.heatmap-months{font-size:var(--text-caption);color:var(--muted);width:max-content;margin-bottom:-2px;display:grid}.heatmap-day-spacer{grid-column:1}.heatmap-month{white-space:nowrap}.heatmap-body{gap:var(--hm-gap);width:max-content;display:flex}.heatmap-days{grid-template-rows:repeat(7, var(--hm-cell));row-gap:var(--hm-gap);font-size:var(--text-micro);color:var(--muted);background:var(--surface);z-index:1;flex-shrink:0;align-items:center;width:25px;display:grid;position:sticky;left:0}.heatmap-grid{gap:var(--hm-gap);display:grid}.heatmap-week{grid-template-rows:repeat(7, var(--hm-cell));gap:var(--hm-gap);display:grid}.heatmap-cell{width:var(--hm-cell);height:var(--hm-cell);border-radius:var(--radius-2xs);background:var(--border)}.heatmap-cell-0{background:var(--border)}.heatmap-cell-1{background:color-mix(in oklch, var(--green) 25%, var(--surface))}.heatmap-cell-2{background:color-mix(in oklch, var(--green) 50%, var(--surface))}.heatmap-cell-3{background:color-mix(in oklch, var(--green) 75%, var(--surface))}.heatmap-cell-4{background:var(--green)}.heatmap-legend{font-size:var(--text-label);align-self:flex-end;align-items:center;gap:4px;display:inline-flex;position:sticky;right:0}.heatmap-legend .heatmap-cell{width:10px;height:10px}.heatmap-tip{z-index:10;pointer-events:none;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);white-space:nowrap;font-size:var(--text-label);padding:6px 10px;position:fixed;transform:translate(-50%,-100%)translateY(-8px);box-shadow:0 6px 20px #00000059}.heatmap-tip-date{font-weight:var(--weight-semibold);color:var(--text);margin-bottom:2px}.heatmap-tip-val{color:var(--text);font-variant-numeric:tabular-nums}.heatmap-tip-req{font-size:var(--text-caption)}.usage-bar{background:var(--border);border-radius:var(--radius-pill);width:100%;min-width:60px;height:6px;overflow:hidden}.usage-bar-fill{background:var(--green);border-radius:var(--radius-pill);height:100%}.daybars{grid-template-columns:repeat(7,1fr);align-items:end;gap:10px;height:180px;padding-top:8px;display:grid}.daybar{flex-direction:column;justify-content:flex-end;align-items:center;gap:6px;height:100%;display:flex;position:relative}.daybar-track{background:var(--border);border-radius:var(--radius-xs);flex:1;align-items:flex-end;width:100%;max-width:48px;display:flex;overflow:hidden}.daybar-stack{border-radius:var(--radius-xs) var(--radius-xs) 0 0;width:100%;min-height:2px;transition:height var(--motion-normal) ease;flex-direction:column-reverse;display:flex;overflow:hidden}.daybar-seg{width:100%;min-height:1px}.daybar-count{font-size:var(--text-label);font-weight:var(--weight-semibold);color:var(--text)}.daybar-label{font-size:var(--text-caption);white-space:nowrap}.daybar:hover .daybar-track{outline:1px solid var(--border)}.daybar-tip{z-index:5;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);pointer-events:none;min-width:160px;padding:8px 10px;position:absolute;bottom:calc(100% + 6px);left:50%;transform:translate(-50%);box-shadow:0 6px 20px #00000059}.daybar-tip-date{font-size:var(--text-label);font-weight:var(--weight-semibold);color:var(--text);white-space:nowrap;margin-bottom:6px}.daybar-tip-row{font-size:var(--text-label);line-height:var(--leading-relaxed);align-items:center;gap:8px;display:flex}.daybar-tip-swatch{border-radius:var(--radius-2xs);flex-shrink:0;width:10px;height:10px}.daybar-tip-name{color:var(--text);white-space:nowrap;text-overflow:ellipsis;flex:1;max-width:160px;overflow:hidden}.daybar-tip-val{color:var(--muted);font-variant-numeric:tabular-nums}.usage-scroll{max-height:360px;overflow-y:auto}.usage-scroll thead th{z-index:1;background:var(--surface);position:sticky;top:0}.toggle{width:var(--toggle-w);height:var(--toggle-h);flex-shrink:0;display:inline-block;position:relative}.toggle input{opacity:0;width:0;height:0;position:absolute}.toggle .slider{background:var(--toggle-off-bg);border-radius:var(--radius-pill);cursor:pointer;transition:background var(--motion-normal) ease;position:absolute;inset:0}.toggle .slider:after{content:"";width:var(--toggle-dot);height:var(--toggle-dot);background:var(--toggle-dot-color);border-radius:var(--radius-round);transition:transform var(--motion-normal) ease;position:absolute;top:3px;left:3px}.toggle input:checked+.slider{background:var(--toggle-on-bg)}.toggle input:checked+.slider:after{transform:translateX(calc(var(--toggle-w) - var(--toggle-dot) - 6px))}.toggle input:focus-visible+.slider{outline:2px solid var(--accent-ring);outline-offset:2px}.setting-row{justify-content:space-between;align-items:center;gap:16px;padding:12px 16px;display:flex}.setting-row+.setting-row{border-top:1px solid var(--border-soft)}.setting-label{flex-direction:column;gap:2px;min-width:0;display:flex}.setting-label .title{font-size:var(--text-body);font-weight:var(--weight-semibold);color:var(--text)}.setting-label .desc{font-size:var(--text-label);color:var(--muted);line-height:var(--leading-body)}
@@ -16,8 +16,8 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-DEbBFENM.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-BUBsQALh.css">
19
+ <script type="module" crossorigin src="/assets/index-BH-RbAg4.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-BpqkH5f_.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bitkyc08/opencodex",
3
- "version": "2.7.18",
4
- "description": "Universal provider proxy for OpenAI Codex — use any LLM with Codex CLI/App/SDK",
3
+ "version": "2.7.20",
4
+ "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code",
5
5
  "type": "module",
6
6
  "main": "./bin/package-main.mjs",
7
7
  "exports": {
@@ -45,7 +45,9 @@
45
45
  "prepack": "bun run prepare:package",
46
46
  "prepublishOnly": "bun run typecheck && bun run build:gui",
47
47
  "release": "bun scripts/release.ts",
48
- "release:watch": "bun scripts/release.ts watch"
48
+ "release:watch": "bun scripts/release.ts watch",
49
+ "prepush": "bun run typecheck && bun run test && bun run privacy:scan",
50
+ "setup:hooks": "bun scripts/setup-hooks.ts"
49
51
  },
50
52
  "dependencies": {
51
53
  "@bufbuild/protobuf": "^2.12.0",
@@ -64,7 +66,17 @@
64
66
  "llm",
65
67
  "ollama",
66
68
  "anthropic",
67
- "responses-api"
69
+ "responses-api",
70
+ "claude-code",
71
+ "claude",
72
+ "codex-cli",
73
+ "gemini",
74
+ "grok",
75
+ "deepseek",
76
+ "chatgpt",
77
+ "llm-proxy",
78
+ "ai-gateway",
79
+ "openrouter"
68
80
  ],
69
81
  "repository": {
70
82
  "type": "git",
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
+ import { shellInvocation } from "../../lib/win-exec";
2
3
  import { create } from "@bufbuild/protobuf";
3
4
  import {
4
5
  ComputerUseErrorSchema,
@@ -25,7 +26,7 @@ const DEFAULT_DESKTOP_TIMEOUT_MS = 30_000;
25
26
  * command receives the request as JSON on stdin and must print a JSON result on stdout.
26
27
  */
27
28
  export interface DesktopExecutorConfig {
28
- /** Command (run via `sh -c`) handling computer-use. Receives `{toolCallId, actions}` on stdin. */
29
+ /** Command (run via the platform shell) handling computer-use. Receives `{toolCallId, actions}` on stdin. */
29
30
  computerUseCommand?: string;
30
31
  /** Command handling record-screen. Receives `{mode, toolCallId, saveAsFilename?}` on stdin. */
31
32
  recordScreenCommand?: string;
@@ -121,14 +122,20 @@ function recordScreenFailure(error: string): RecordScreenResult {
121
122
  });
122
123
  }
123
124
 
124
- /** Spawn `command` via the shell, write `payload` as JSON to stdin, return parsed stdout JSON. */
125
+ /**
126
+ * Spawn `command` via the platform shell (sh -c on POSIX, cmd.exe /d /s /c on win32 —
127
+ * the configured command is platform-native shell syntax; devlog
128
+ * 260715_cross_platform_audit/020), write `payload` as JSON to stdin, return parsed stdout JSON.
129
+ */
125
130
  function runExternalJson(command: string, payload: unknown, config: DesktopExecutorConfig): Promise<unknown> {
126
131
  const timeoutMs = config.timeoutMs ?? DEFAULT_DESKTOP_TIMEOUT_MS;
127
132
  return new Promise((resolve, reject) => {
128
- const child = spawn("sh", ["-c", command], {
133
+ const inv = shellInvocation(command);
134
+ const child = spawn(inv.file, inv.args, {
129
135
  cwd: config.cwd,
130
136
  env: config.env ? { ...process.env, ...config.env } : process.env,
131
137
  stdio: ["pipe", "pipe", "pipe"],
138
+ ...inv.options,
132
139
  });
133
140
  let stdout = "";
134
141
  let stderr = "";
@@ -0,0 +1,228 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { getConfigDir } from "../config";
5
+ import type { OcxProviderConfig, OcxParsedRequest } from "../types";
6
+ import { createOpenAIChatAdapter } from "./openai-chat";
7
+ import type { ProviderAdapter, AdapterRequest } from "./base";
8
+
9
+ const BOOTSTRAP_URL = "https://api.xiaomimimo.com/api/free-ai/bootstrap";
10
+ export const MIMO_CHAT_URL = "https://api.xiaomimimo.com/api/free-ai/openai/chat";
11
+
12
+ /**
13
+ * Anti-abuse gate: the free chat endpoint returns 403 "Illegal access" unless
14
+ * a system message contains this exact string as a substring.
15
+ */
16
+ export const MIMO_SYSTEM_MARKER =
17
+ "You are MiMoCode, an interactive CLI tool that helps users with software engineering tasks.";
18
+
19
+ // Chrome-like User-Agent required by the upstream anti-abuse gate.
20
+ const USER_AGENTS = [
21
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
22
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
23
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
24
+ ];
25
+
26
+ const JWT_FALLBACK_TTL_MS = 3_000_000; // 50 min
27
+ const JWT_EXPIRY_BUFFER_MS = 300_000; // 5 min early refresh
28
+ const BOOTSTRAP_TIMEOUT_MS = 15_000;
29
+
30
+ // In-process JWT cache -- survives across requests, reset on restart.
31
+ let cachedJwt: string | null = null;
32
+ let jwtExpiresAt = 0;
33
+ // Single-flight guard: concurrent first requests share one bootstrap.
34
+ let inFlightJwt: Promise<string> | null = null;
35
+
36
+ function randomUserAgent(): string {
37
+ return USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)]!;
38
+ }
39
+
40
+ /**
41
+ * Anonymous per-install client id for the bootstrap `client` field. A random UUID
42
+ * persisted under the config dir (OPENCODEX_HOME-aware) — deliberately NOT derived
43
+ * from machine attributes (hostname/username/CPU), which would be a stable
44
+ * pseudonymous device fingerprint. Delete the file to rotate the id.
45
+ */
46
+ let cachedClientId: string | null = null;
47
+ export function getMimoClientId(): string {
48
+ if (cachedClientId) return cachedClientId;
49
+ const dir = getConfigDir();
50
+ const file = join(dir, "mimo-client-id");
51
+ try {
52
+ if (existsSync(file)) {
53
+ const stored = readFileSync(file, "utf8").trim();
54
+ if (/^[0-9a-f-]{36}$/i.test(stored)) {
55
+ cachedClientId = stored;
56
+ return stored;
57
+ }
58
+ }
59
+ } catch { /* fall through to regenerate */ }
60
+ const fresh = randomUUID();
61
+ try {
62
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
63
+ writeFileSync(file, `${fresh}\n`, "utf8");
64
+ } catch { /* persist best-effort; still usable for this process */ }
65
+ cachedClientId = fresh;
66
+ return fresh;
67
+ }
68
+
69
+ /** Test hook: clear the in-process client-id cache (file state is the test's concern). */
70
+ export function resetMimoClientIdCache(): void {
71
+ cachedClientId = null;
72
+ }
73
+
74
+ function parseJwtExp(jwt: string): number {
75
+ try {
76
+ const parts = jwt.split(".");
77
+ if (parts.length < 2) return 0;
78
+ const payload = JSON.parse(Buffer.from(parts[1]!, "base64").toString()) as { exp?: number };
79
+ if (payload.exp) return payload.exp * 1000;
80
+ } catch { /* ignore */ }
81
+ return Date.now() + JWT_FALLBACK_TTL_MS;
82
+ }
83
+
84
+ export function resetMimoJwtCache(): void {
85
+ cachedJwt = null;
86
+ jwtExpiresAt = 0;
87
+ inFlightJwt = null;
88
+ }
89
+
90
+ async function fetchJwt(signal?: AbortSignal): Promise<string> {
91
+ // Bounded bootstrap: request-abort propagates, and a stalled bootstrap can never
92
+ // hang past BOOTSTRAP_TIMEOUT_MS.
93
+ const timeout = AbortSignal.timeout(BOOTSTRAP_TIMEOUT_MS);
94
+ const combined = signal ? AbortSignal.any([signal, timeout]) : timeout;
95
+ const response = await fetch(BOOTSTRAP_URL, {
96
+ method: "POST",
97
+ headers: {
98
+ "Content-Type": "application/json",
99
+ "User-Agent": randomUserAgent(),
100
+ },
101
+ body: JSON.stringify({ client: getMimoClientId() }),
102
+ signal: combined,
103
+ });
104
+ if (!response.ok) {
105
+ try { await response.body?.cancel(); } catch { /* already consumed */ }
106
+ throw new Error(`MiMo bootstrap failed: ${response.status}`);
107
+ }
108
+ const data = await response.json() as { jwt?: string };
109
+ if (!data.jwt) throw new Error("MiMo bootstrap returned no JWT");
110
+ return data.jwt;
111
+ }
112
+
113
+ export async function getMimoJwt(signal?: AbortSignal): Promise<string> {
114
+ if (cachedJwt && Date.now() < jwtExpiresAt - JWT_EXPIRY_BUFFER_MS) {
115
+ return cachedJwt;
116
+ }
117
+ // Single-flight: concurrent callers await the same bootstrap instead of issuing
118
+ // parallel bootstraps.
119
+ if (!inFlightJwt) {
120
+ inFlightJwt = (async () => {
121
+ try {
122
+ const jwt = await fetchJwt(signal);
123
+ cachedJwt = jwt;
124
+ jwtExpiresAt = parseJwtExp(jwt);
125
+ return jwt;
126
+ } finally {
127
+ inFlightJwt = null;
128
+ }
129
+ })();
130
+ }
131
+ return inFlightJwt;
132
+ }
133
+
134
+ /**
135
+ * Idempotently prepend the MiMo anti-abuse system marker if it is not already present.
136
+ * The marker must appear in a system message; we prepend one if the request has none with it.
137
+ */
138
+ export function injectMimoSystemMarker(body: unknown): unknown {
139
+ if (!body || typeof body !== "object") return body;
140
+ const parsed = body as Record<string, unknown>;
141
+ const messages = parsed["messages"];
142
+ if (!Array.isArray(messages)) return body;
143
+ const hasMarker = messages.some(
144
+ (m): m is { role: string; content: string } =>
145
+ m !== null &&
146
+ typeof m === "object" &&
147
+ (m as Record<string, unknown>)["role"] === "system" &&
148
+ typeof (m as Record<string, unknown>)["content"] === "string" &&
149
+ ((m as Record<string, unknown>)["content"] as string).includes(MIMO_SYSTEM_MARKER),
150
+ );
151
+ if (hasMarker) return body;
152
+ return { ...parsed, messages: [{ role: "system", content: MIMO_SYSTEM_MARKER }, ...messages] };
153
+ }
154
+
155
+ /**
156
+ * Creates the MiMo Free adapter. Wraps openai-chat's request builder to inject:
157
+ * 1. JWT from the bootstrap endpoint (cached, auto-refreshed).
158
+ * 2. Anti-abuse system marker in the request body.
159
+ * 3. Required headers (User-Agent, X-Mimo-Source, x-session-affinity).
160
+ * On 401/403, flushes the JWT cache and retries once via fetchResponse.
161
+ */
162
+ export function createMimoFreeAdapter(provider: OcxProviderConfig): ProviderAdapter {
163
+ const base = createOpenAIChatAdapter(provider);
164
+ // Per-adapter session-affinity id (random, per process instance).
165
+ const sessionId = `ses_${Math.random().toString(36).slice(2, 26)}`;
166
+
167
+ return {
168
+ ...base,
169
+ name: "mimo-free",
170
+
171
+ async buildRequest(parsed: OcxParsedRequest): Promise<AdapterRequest> {
172
+ const jwt = await getMimoJwt();
173
+
174
+ // Let the base adapter build the wire body (handles reasoning, tools, etc.)
175
+ // but override the URL and headers after.
176
+ const baseReq = base.buildRequest(parsed) as AdapterRequest;
177
+ const baseBody = JSON.parse(baseReq.body as string) as unknown;
178
+ const markedBody = injectMimoSystemMarker(baseBody);
179
+
180
+ const headers: Record<string, string> = {
181
+ "Content-Type": "application/json",
182
+ "Authorization": `Bearer ${jwt}`,
183
+ "X-Mimo-Source": "mimocode-cli-free",
184
+ "User-Agent": randomUserAgent(),
185
+ "x-session-affinity": sessionId,
186
+ "Accept": parsed.stream ? "text/event-stream" : "application/json",
187
+ };
188
+
189
+ return {
190
+ url: MIMO_CHAT_URL,
191
+ method: "POST",
192
+ headers,
193
+ body: JSON.stringify(markedBody),
194
+ };
195
+ },
196
+
197
+ async fetchResponse(request: AdapterRequest, ctx): Promise<Response> {
198
+ const response = await fetch(request.url, {
199
+ method: request.method,
200
+ headers: request.headers as Record<string, string>,
201
+ body: request.body,
202
+ signal: ctx?.abortSignal,
203
+ });
204
+
205
+ // Retry predicate: 401 (expired/invalid JWT) retries ONCE with a fresh token.
206
+ // 403 is NOT retried — Xiaomi uses it for anti-abuse "Illegal access" and there is
207
+ // no documented token-expiry signature that would mark a 403 as retryable.
208
+ if (response.status === 401) {
209
+ // Drain the first response body before issuing the retry.
210
+ try { await response.body?.cancel(); } catch { /* already consumed */ }
211
+ resetMimoJwtCache();
212
+ const freshJwt = await getMimoJwt(ctx?.abortSignal);
213
+ const retryHeaders = {
214
+ ...(request.headers as Record<string, string>),
215
+ "Authorization": `Bearer ${freshJwt}`,
216
+ };
217
+ return fetch(request.url, {
218
+ method: request.method,
219
+ headers: retryHeaders,
220
+ body: request.body,
221
+ signal: ctx?.abortSignal,
222
+ });
223
+ }
224
+
225
+ return response;
226
+ },
227
+ };
228
+ }
@@ -172,6 +172,88 @@ function safeToolName(name: string | undefined): string {
172
172
  return sanitized;
173
173
  }
174
174
 
175
+ const ZEN_SCHEMA_MAP_KEYS = new Set(["properties", "$defs", "definitions"]);
176
+ const ZEN_DROPPED_SCHEMA_KEYS = new Set(["encrypted"]);
177
+
178
+ function sanitizeZenSchemaMap(value: unknown): unknown {
179
+ if (!value || typeof value !== "object" || Array.isArray(value)) return sanitizeZenToolParameters(value);
180
+ const out: Record<string, unknown> = {};
181
+ for (const [name, child] of Object.entries(value as Record<string, unknown>)) {
182
+ out[name] = sanitizeZenToolParameters(child);
183
+ }
184
+ return out;
185
+ }
186
+
187
+ function sanitizeZenToolParameters(value: unknown): unknown {
188
+ if (Array.isArray(value)) return value.map(sanitizeZenToolParameters);
189
+ if (!value || typeof value !== "object") return value;
190
+ const input = value as Record<string, unknown>;
191
+ const out: Record<string, unknown> = {};
192
+ for (const [key, child] of Object.entries(input)) {
193
+ if (ZEN_DROPPED_SCHEMA_KEYS.has(key)) continue;
194
+ if (key === "required" && Array.isArray(child) && child.length === 0) continue;
195
+ if (key === "type" && Array.isArray(child)) {
196
+ const nonNull = child.filter(entry => entry !== "null");
197
+ if (child.includes("null")) out.nullable = true;
198
+ if (nonNull.length > 0) out.type = nonNull[0];
199
+ continue;
200
+ }
201
+ out[key] = ZEN_SCHEMA_MAP_KEYS.has(key) ? sanitizeZenSchemaMap(child) : sanitizeZenToolParameters(child);
202
+ }
203
+ return out;
204
+ }
205
+
206
+ function ensureZenRootObjectSchema(schema: unknown): Record<string, unknown> {
207
+ const obj = schema && typeof schema === "object" && !Array.isArray(schema)
208
+ ? schema as Record<string, unknown>
209
+ : {};
210
+ const compositionKeys = ["oneOf", "anyOf", "allOf"] as const;
211
+ const hasComposition = compositionKeys.some(key => Array.isArray(obj[key]));
212
+ const rootType = obj.type;
213
+ const rootObjectType = rootType === "object" || (Array.isArray(rootType) && rootType.includes("object"));
214
+ if (!hasComposition) {
215
+ const base = sanitizeZenToolParameters(obj) as Record<string, unknown>;
216
+ return rootObjectType && base.type === "object" ? base : { ...base, type: "object" };
217
+ }
218
+
219
+ const props: Record<string, unknown> = {};
220
+ const required = new Set<string>();
221
+ if (obj.properties && typeof obj.properties === "object") {
222
+ Object.assign(props, sanitizeZenSchemaMap(obj.properties) as Record<string, unknown>);
223
+ }
224
+ if (Array.isArray(obj.required)) {
225
+ for (const entry of obj.required) if (typeof entry === "string") required.add(entry);
226
+ }
227
+ for (const key of compositionKeys) {
228
+ const variants = obj[key];
229
+ if (!Array.isArray(variants)) continue;
230
+ const mergeRequired = key === "allOf";
231
+ for (const variant of variants) {
232
+ if (!variant || typeof variant !== "object" || Array.isArray(variant)) continue;
233
+ const rec = variant as Record<string, unknown>;
234
+ if (rec.properties && typeof rec.properties === "object") {
235
+ Object.assign(props, sanitizeZenSchemaMap(rec.properties) as Record<string, unknown>);
236
+ }
237
+ if (mergeRequired && Array.isArray(rec.required)) {
238
+ for (const entry of rec.required) if (typeof entry === "string") required.add(entry);
239
+ }
240
+ }
241
+ }
242
+
243
+ const merged = sanitizeZenToolParameters(obj) as Record<string, unknown>;
244
+ delete merged.oneOf;
245
+ delete merged.anyOf;
246
+ delete merged.allOf;
247
+ merged.type = "object";
248
+ if (Object.keys(props).length > 0) merged.properties = props;
249
+ if (required.size > 0) merged.required = [...required];
250
+ return merged;
251
+ }
252
+
253
+ function shouldSanitizeZenToolParameters(provider: OcxProviderConfig): boolean {
254
+ return provider.baseUrl.replace(/\/+$/, "") === "https://opencode.ai/zen/v1";
255
+ }
256
+
175
257
  const XAI_SCHEMA_BASE_URLS = new Set(["api.x.ai", "cli-chat-proxy.grok.com"]);
176
258
 
177
259
  function isXaiSchemaTarget(provider: OcxProviderConfig): boolean {
@@ -240,6 +322,23 @@ function toolsToChatFormat(parsed: OcxParsedRequest, provider: OcxProviderConfig
240
322
  return formatted.length > 0 ? formatted : undefined;
241
323
  }
242
324
 
325
+ function toolsToChatFormatForProvider(parsed: OcxParsedRequest, provider: OcxProviderConfig): unknown[] | undefined {
326
+ const base = toolsToChatFormat(parsed, provider);
327
+ if (!base || !shouldSanitizeZenToolParameters(provider)) return base;
328
+ return base.map(tool => {
329
+ if (!tool || typeof tool !== "object") return tool;
330
+ const functionDef = (tool as { function?: Record<string, unknown> }).function;
331
+ if (!functionDef || typeof functionDef !== "object") return tool;
332
+ return {
333
+ ...tool,
334
+ function: {
335
+ ...functionDef,
336
+ parameters: ensureZenRootObjectSchema(functionDef.parameters ?? {}),
337
+ },
338
+ };
339
+ });
340
+ }
341
+
243
342
  function toolChoiceToChatFormat(tc: OcxParsedRequest["options"]["toolChoice"], tools: OcxParsedRequest["context"]["tools"]): unknown {
244
343
  if (!tc) return undefined;
245
344
  if (isAllowedToolChoice(tc)) return tc.mode === "required" ? "required" : "auto";
@@ -287,7 +386,7 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
287
386
  }
288
387
 
289
388
  const messages = messagesToChatFormat(parsed, provider);
290
- const tools = toolsToChatFormat(parsed, provider);
389
+ const tools = toolsToChatFormatForProvider(parsed, provider);
291
390
  const toolChoice = toolChoiceToChatFormat(parsed.options.toolChoice, parsed.context.tools);
292
391
 
293
392
  const body: Record<string, unknown> = {
@@ -346,6 +445,10 @@ export function createOpenAIChatAdapter(provider: OcxProviderConfig): ProviderAd
346
445
 
347
446
  const url = `${provider.baseUrl}/chat/completions`;
348
447
  const headers: Record<string, string> = { "Content-Type": "application/json" };
448
+ // Precedence preserved from pre-#128 behavior: apiKey Authorization first, then
449
+ // provider.headers may override (user/registry-configured headers win). Registry
450
+ // staticHeaders (e.g. opencode-free x-opencode-client) flow in via derive.ts and
451
+ // never carry Authorization, so keyless providers are unaffected.
349
452
  if (hasCredential) headers["Authorization"] = `Bearer ${provider.apiKey}`;
350
453
  if (provider.headers) Object.assign(headers, provider.headers);
351
454
 
@@ -180,7 +180,9 @@ function maybeElideSkillText(text: string, names: readonly string[]): string {
180
180
  if (!text.startsWith(SKILL_TEXT_MARKER)) return text;
181
181
  const firstLineEnd = text.indexOf("\n");
182
182
  const dir = text.slice(SKILL_TEXT_MARKER.length, firstLineEnd === -1 ? text.length : firstLineEnd).trim();
183
- const base = dir.split("/").filter(Boolean).pop()?.toLowerCase() ?? "";
183
+ // Windows clients send `C:\Users\...\claude-api`; normalize separators before
184
+ // basenaming (repo precedent: src/codex/inject.ts isOpencodexCatalogPath).
185
+ const base = dir.replace(/\\/g, "/").split("/").filter(Boolean).pop()?.toLowerCase() ?? "";
184
186
  if (!names.includes(base)) return text;
185
187
  return `[opencodex] '${base}' skill document bundle (${text.length} chars) elided for routed models `
186
188
  + "(claudeCode.blockedSkills). The skill is loaded; answer from general knowledge instead of citing the bundle.";
package/src/cli/claude.ts CHANGED
@@ -11,6 +11,7 @@ import { loadConfig } from "../config";
11
11
  import { injectClaudeAgentDefs } from "../claude/agents-inject";
12
12
  import { effectiveModelEnv, resolveAutoContext } from "../claude/context-windows";
13
13
  import { refreshGatewayModelCacheFromProxy } from "../claude/gateway-cache";
14
+ import { commandInvocation } from "../lib/win-exec";
14
15
  import { findLiveProxy } from "../server/proxy-liveness";
15
16
  import type { OcxConfig } from "../types";
16
17
 
@@ -141,6 +142,21 @@ async function ensureProxyForClaude(): Promise<number | null> {
141
142
  return null;
142
143
  }
143
144
 
145
+ const CLAUDE_INSTALL_HINT = "❌ `claude` CLI not found. Install it first: npm install -g @anthropic-ai/claude-code";
146
+
147
+ /**
148
+ * cmd.exe reports command-not-found as exit 9009 (the win32 launcher routes `.cmd`
149
+ * shims through cmd.exe, so ENOENT never fires there). Signal exits are not hints.
150
+ * Devlog 260715_cross_platform_audit/020.
151
+ */
152
+ export function claudeNotFoundHint(
153
+ code: number | null,
154
+ signal: NodeJS.Signals | null,
155
+ platform: NodeJS.Platform = process.platform,
156
+ ): string | null {
157
+ return platform === "win32" && code === 9009 && !signal ? CLAUDE_INSTALL_HINT : null;
158
+ }
159
+
144
160
  export async function cmdClaude(args: string[]): Promise<number> {
145
161
  const config = loadConfig();
146
162
  if (config.claudeCode?.enabled === false) {
@@ -176,16 +192,19 @@ export async function cmdClaude(args: string[]): Promise<number> {
176
192
  console.error(`⚠ Claude agent definitions could not be synced: ${message}`);
177
193
  }
178
194
  return await new Promise<number>(resolve => {
179
- const child = spawn("claude", args, { stdio: "inherit", env: env as NodeJS.ProcessEnv });
195
+ const inv = commandInvocation("claude", args);
196
+ const child = spawn(inv.file, inv.args, { stdio: "inherit", env: env as NodeJS.ProcessEnv, ...inv.options });
180
197
  child.on("error", (err: NodeJS.ErrnoException) => {
181
198
  if (err.code === "ENOENT") {
182
- console.error("❌ `claude` CLI not found. Install it first: npm install -g @anthropic-ai/claude-code");
199
+ console.error(CLAUDE_INSTALL_HINT);
183
200
  } else {
184
201
  console.error(`❌ Failed to launch claude: ${err.message}`);
185
202
  }
186
203
  resolve(1);
187
204
  });
188
205
  child.on("exit", (code, signal) => {
206
+ const hint = claudeNotFoundHint(code, signal);
207
+ if (hint) console.error(hint);
189
208
  resolve(signal ? 1 : code ?? 0);
190
209
  });
191
210
  });
package/src/cli/v2.ts CHANGED
@@ -13,22 +13,38 @@
13
13
  import { execFileSync } from "node:child_process";
14
14
  import { getLogicalMaxThreads, hasAgentsMaxThreads, isMultiAgentV2Enabled, transitionMultiAgentV2 } from "../codex/features";
15
15
 
16
+ import { commandInvocation, type SpawnInvocation } from "../lib/win-exec";
16
17
  import { loadConfig, saveConfig } from "../config";
17
18
 
18
19
  export interface V2CliDeps {
19
- execFile?: (file: string, args: string[]) => void;
20
+ execFile?: (file: string, args: string[], options?: SpawnInvocation["options"]) => void;
20
21
  isEnabled?: typeof isMultiAgentV2Enabled;
21
22
  hasMaxThreads?: typeof hasAgentsMaxThreads;
22
23
  sync?: (port?: number) => Promise<unknown>;
23
24
  log?: Pick<Console, "log" | "error">;
24
25
  }
25
26
 
27
+ /**
28
+ * Shared invocation for `codex features enable|disable multi_agent_v2` — the single
29
+ * source of truth for the CLI and the management API fallback. Windows npm installs
30
+ * expose `codex` as a `.cmd` shim, which needs the win-exec launcher
31
+ * (devlog 260715_cross_platform_audit/020).
32
+ */
33
+ export function codexFeaturesInvocation(
34
+ action: "enable" | "disable",
35
+ platform: NodeJS.Platform = process.platform,
36
+ deps: Parameters<typeof commandInvocation>[3] = {},
37
+ ): SpawnInvocation {
38
+ const command = (deps.env ?? process.env).CODEX_CLI_PATH?.trim() || "codex";
39
+ return commandInvocation(command, ["features", action, "multi_agent_v2"], platform, deps);
40
+ }
41
+
26
42
  function runCodexFeatures(action: "enable" | "disable", deps: V2CliDeps): void {
27
- const exec = deps.execFile ?? ((file: string, args: string[]) => {
28
- execFileSync(file, args, { stdio: ["ignore", "pipe", "pipe"], timeout: 15_000, windowsHide: true });
43
+ const exec = deps.execFile ?? ((file: string, args: string[], options?: SpawnInvocation["options"]) => {
44
+ execFileSync(file, args, { stdio: ["ignore", "pipe", "pipe"], timeout: 15_000, windowsHide: true, ...options });
29
45
  });
30
- const command = process.env.CODEX_CLI_PATH?.trim() || "codex";
31
- exec(command, ["features", action, "multi_agent_v2"]);
46
+ const inv = codexFeaturesInvocation(action);
47
+ exec(inv.file, inv.args, inv.options);
32
48
  }
33
49
 
34
50
  export function v2StatusLine(enabled: boolean): string {