cicy-desktop 2.1.95 → 2.1.97

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{--accent: #5b8df7;--accent-soft: rgba(91,141,247,.16);--accent-line: rgba(91,141,247,.32);--accent-text: #a5c4ff;--ok: #4ade80;--danger: #f7a3a3;--text: #e5e7eb;--text-dim: #9da7b3;--text-mute: #6b7280;--line: rgba(255,255,255,.08)}*{box-sizing:border-box}html,body,#root{margin:0;height:100%}html,body{overflow:hidden}body{font-family:-apple-system,BlinkMacSystemFont,PingFang SC,Segoe UI,Roboto,Helvetica Neue,sans-serif;background:#07080c;color:#e5e7eb;-webkit-font-smoothing:antialiased}.shell{position:relative;min-height:100vh;display:flex;align-items:center;justify-content:center;overflow:hidden;-webkit-app-region:drag}.shell--app{align-items:stretch;justify-content:stretch;flex-direction:row;background:linear-gradient(180deg,#0b0d13,#07080c);overflow:hidden;-webkit-app-region:no-drag}.shell__left{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;overflow:hidden}.helper-aside{flex:0 0 auto;position:relative;background:#0f1115;border-left:1px solid rgba(255,255,255,.06);display:flex;flex-direction:column;min-width:320px;z-index:1}.helper-aside,.helper-aside *,.shell--app .main{-webkit-app-region:no-drag}.shell--app .topbar{-webkit-app-region:drag}.shell--app .topbar .user-chip,.shell--app .topbar .user-chip *,.shell--app .topbar .btn-ghost{-webkit-app-region:no-drag}.glow{position:absolute;top:-40%;right:-40%;bottom:-40%;left:-40%;z-index:0;pointer-events:none;background:radial-gradient(closest-side,rgba(91,141,247,.2),transparent 60%),radial-gradient(closest-side at 30% 70%,rgba(167,139,250,.13),transparent 65%);filter:blur(30px)}.card{-webkit-app-region:no-drag;position:relative;z-index:1;width:380px;padding:36px 36px 28px;background:linear-gradient(180deg,#141820d9,#0d1016d9);border:1px solid rgba(255,255,255,.06);border-radius:16px;box-shadow:0 1px #ffffff0a inset,0 30px 60px #0006;-webkit-backdrop-filter:blur(18px);backdrop-filter:blur(18px);display:flex;flex-direction:column;align-items:center;gap:18px}.brand{display:flex;align-items:center;gap:12px;align-self:stretch}.brand-mark{width:40px;height:40px;border-radius:12px;display:grid;place-items:center;background:linear-gradient(135deg,#5b8df7,#a78bfa);box-shadow:0 8px 20px #5b8df759}.brand-mark.sm{width:28px;height:28px;border-radius:8px;box-shadow:none}.brand-mark.sm svg{width:16px;height:16px}.brand-text{line-height:1.2}.brand-name{font-weight:600;font-size:15px}.brand-sub{font-size:12px;color:#9ca3af;margin-top:2px}.tagline{margin:4px 0 0;color:#c7cdd6;font-size:13.5px;text-align:center;line-height:1.55}.btn-primary{-webkit-app-region:no-drag;margin-top:4px;-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;height:42px;display:inline-flex;align-items:center;justify-content:center;gap:8px;background:linear-gradient(180deg,#5b8df7,#4570d8);color:#fff;border:0;border-radius:10px;font-size:14px;font-weight:500;letter-spacing:.2px;cursor:pointer;box-shadow:0 1px #ffffff2e inset,0 -1px #0000002e inset,0 10px 22px #5b8df747;transition:transform 80ms ease,filter .12s ease}.btn-primary:hover{filter:brightness(1.08)}.btn-primary:active{transform:translateY(1px)}.btn-primary:disabled{filter:grayscale(.4) brightness(.7);cursor:default}.btn-ghost{-webkit-app-region:no-drag;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;color:#9ca3af;border:1px solid rgba(255,255,255,.08);border-radius:8px;padding:6px 14px;font-size:12.5px;cursor:pointer;transition:color .12s ease,border-color .12s ease,background .12s ease}.btn-ghost.sm{padding:4px 10px;font-size:11.5px}.btn-ghost:hover{color:#e5e7eb;background:#ffffff0a;border-color:#ffffff24}.btn-ghost:disabled{opacity:.4;cursor:default}.hint{margin:0;font-size:11.5px;color:#6b7280}.spinner-row{display:inline-flex;align-items:center;gap:8px;color:#c7cdd6;font-size:13px}.spin{animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.error{width:100%;font-size:12px;color:#fca5a5;background:#ef444414;border:1px solid rgba(239,68,68,.18);padding:8px 12px;border-radius:8px;text-align:center;line-height:1.5}.topbar{-webkit-app-region:drag;position:relative;flex:0 0 auto;z-index:10;display:flex;align-items:center;justify-content:space-between;padding:14px 24px 12px;background:#08090e99;border-bottom:1px solid rgba(255,255,255,.05);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px)}[data-platform=darwin][data-fullscreen="0"] .topbar{padding-top:34px}.brand-mini{display:inline-flex;align-items:center;gap:10px}.brand-mini .brand-name{font-size:14px}.user-chip{-webkit-app-region:no-drag;display:inline-flex;align-items:center;gap:10px}.welcome{font-size:12px;color:var(--text-dim);padding:4px 10px;border-radius:999px;background:#ffffff0d;border:1px solid var(--line);animation:fadein .2s ease}@keyframes fadein{0%{opacity:0;transform:translateY(-2px)}to{opacity:1;transform:none}}.avatar{width:26px;height:26px;border-radius:50%;display:grid;place-items:center;background:linear-gradient(135deg,#5b8df7,#a78bfa);font-size:12px;font-weight:600;color:#fff}.user-name{font-size:13px;color:#c7cdd6}.user-chip{position:relative}.user-chip__trigger{-webkit-app-region:no-drag;display:inline-flex;align-items:center;gap:8px;border:1px solid transparent;background:transparent;border-radius:999px;padding:3px 8px 3px 3px;cursor:pointer;transition:.12s}.user-chip__trigger:hover,.user-chip__trigger.is-open{background:#ffffff0f;border-color:#ffffff1f}.user-chip__caret{font-size:10px;color:#8a93a3;transition:transform .12s}.user-chip__trigger.is-open .user-chip__caret{transform:rotate(180deg)}.user-chip__menu{position:absolute;top:38px;right:0;z-index:30;min-width:200px;padding:6px;background:#1b2027;border:1px solid rgba(255,255,255,.12);border-radius:12px;box-shadow:0 12px 34px #00000080;display:flex;flex-direction:column;gap:2px;animation:fadein .14s ease}.user-chip__menu-item{text-align:left;width:100%;border:none;background:transparent;color:#d1d5db;border-radius:8px;padding:9px 11px;font-size:13px;cursor:pointer;transition:.12s}.user-chip__menu-item:hover{background:#ffffff12;color:#fff}.user-chip__menu-item.is-danger{color:#f7a3a3}.user-chip__menu-item.is-danger:hover{background:#ef444429;color:#fff}.user-chip__menu-sep{height:1px;margin:4px 2px;background:#ffffff14}.user-chip__menu-version{margin-top:4px;padding:8px 11px 4px;font-size:11px;color:#6b7280;text-align:center;font-variant-numeric:tabular-nums;-webkit-user-select:text;user-select:text;border-top:1px solid rgba(255,255,255,.06)}.user-chip__mitm-row{display:flex;align-items:center;justify-content:space-between;gap:10px;cursor:default}.user-chip__mitm-row:hover{background:transparent}.user-chip__mitm-label{font-size:13px;color:var(--text)}.mini-switch{-webkit-app-region:no-drag;position:relative;flex:0 0 auto;width:36px;height:20px;padding:0;border-radius:999px;border:1px solid var(--line);background:#ffffff14;cursor:pointer;transition:background .16s ease,border-color .16s ease}.mini-switch.is-on{background:var(--accent);border-color:var(--accent)}.mini-switch.is-busy{opacity:.6;cursor:default}.mini-switch:disabled{cursor:default}.mini-switch__knob{position:absolute;top:1px;left:1px;width:16px;height:16px;border-radius:50%;background:#fff;box-shadow:0 1px 2px #00000073;transition:transform .16s ease}.mini-switch.is-on .mini-switch__knob{transform:translate(16px)}.mini-switch.is-busy .mini-switch__knob{animation:spin 1s linear infinite}.user-chip__mitm-note{margin:0 4px;padding:2px 4px 6px;font-size:11px;color:var(--text-mute)}.user-chip__mitm-err{margin:2px 4px 6px;padding:5px 8px;font-size:11px;color:var(--danger);background:#ef44441a;border-radius:6px;line-height:1.4}.bcard__top-right{display:inline-flex;align-items:center;gap:6px}.bcard__billing-btn{-webkit-app-region:no-drag;border:1px solid var(--line);background:transparent;color:var(--text-dim);border-radius:7px;padding:3px 9px;font-size:11px;cursor:pointer;transition:color .12s,border-color .12s,background .12s}.bcard__billing-btn:hover{color:var(--accent-text);border-color:var(--accent-line);background:var(--accent-soft)}.glow--app{inset:-10% -10% auto -10%;height:50vh;background:radial-gradient(closest-side at 75% 0%,rgba(91,141,247,.18),transparent 65%),radial-gradient(closest-side at 20% 10%,rgba(167,139,250,.1),transparent 60%);filter:blur(40px)}.main{position:relative;z-index:1;padding:22px 32px 48px;width:100%;display:flex;flex-direction:column;gap:16px;flex:1 1 auto;min-height:0;overflow-y:auto}.main::-webkit-scrollbar{width:8px}.main::-webkit-scrollbar-track{background:transparent}.main::-webkit-scrollbar-thumb{background:#7d87964d;border-radius:999px;border:2px solid transparent;background-clip:padding-box}.main::-webkit-scrollbar-thumb:hover{background-color:#7d87968c}.app__tabs{display:inline-flex;align-items:center;gap:4px;padding:4px;background:#1418208c;border:1px solid rgba(255,255,255,.06);border-radius:10px;align-self:flex-start;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.app__tab{-webkit-app-region:no-drag;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;color:#9ca3af;border:0;border-radius:7px;padding:6px 14px;font-size:12.5px;font-weight:500;letter-spacing:.15px;cursor:pointer;display:inline-flex;align-items:center;gap:6px;transition:color .12s ease,background .12s ease}.app__tab:hover{color:#e5e7eb;background:#ffffff0a}.app__tab.is-active{color:#fff;background:#5b8df72e;box-shadow:0 0 0 1px #5b8df74d inset}.app__tab-count{font-size:10.5px;color:#9ca3af;background:#ffffff0f;padding:1px 6px;border-radius:999px;min-width:18px;text-align:center}.app__tab.is-active .app__tab-count{background:#5b8df74d;color:#fff}.app__tabsrow{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%}.app__add-team{-webkit-app-region:no-drag;-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:pointer;flex:none;color:#5b8df7;font-weight:600;font-size:12.5px;background:#5b8df71a;border:1px solid rgba(91,141,247,.35);border-radius:9px;padding:7px 14px;transition:color .12s ease,background .12s ease,border-color .12s ease}.app__add-team:hover{color:#fff;background:#5b8df738;border-color:#5b8df7a6}.app__grid{display:grid;grid-template-columns:repeat(auto-fill,200px);gap:14px}.add-card{-webkit-app-region:no-drag;-webkit-appearance:none;-moz-appearance:none;appearance:none;width:200px;height:200px;background:transparent;border:1.5px dashed rgba(255,255,255,.12);border-radius:14px;color:#9ca3af;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;transition:border-color .16s ease,color .16s ease,background .16s ease}.add-card:hover{border-color:#5b8df780;color:#e5e7eb;background:#5b8df70d}.add-card__plus{width:36px;height:36px;border-radius:50%;display:grid;place-items:center;background:#ffffff0a;font-size:22px;font-weight:300;color:inherit}.add-card__label{font-size:13px;font-weight:500}.bcard--helper{background:linear-gradient(160deg,#5b8df729,#a78bfa24 60%,#1418208c);border-color:#a78bfa40}.bcard--helper .bcard__accent{background:linear-gradient(90deg,#5b8df7,#a78bfa);opacity:1}.bcard--helper:hover{border-color:#a78bfa73}.bcard__pill--helper{background:#a78bfa2e;border-color:#a78bfa4d;color:#c4b5fd;font-size:11px;font-weight:600;letter-spacing:.2px}.bcard__helper-icon{font-size:12px;filter:grayscale(.2)}.bcard__badge--free{background:#34d39926;color:#34d399;border:1px solid rgba(52,211,153,.3)}.bcard__badge--trial{background:#ffffff0f;color:var(--text-dim);border:1px solid var(--line);font-size:9.5px;letter-spacing:.3px;padding:2px 7px}.bcard__badge--local{background:#5b8df72e;color:#a5c4ff;border:1px solid rgba(91,141,247,.35);font-size:9.5px;letter-spacing:.3px;padding:2px 7px}.bcard--helper .bcard__name{color:#f3f4f6}.bcard__desc{margin:4px 0 0;font-size:11.5px;line-height:1.5;color:#c7cdd6;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.bcard__fineprint{margin:4px 0 0;font-size:10.5px;color:#9ca3af;opacity:.8}.bcard__cta--helper{background:#ffffff0d;box-shadow:none}.bcard__cta--helper:hover{background:var(--accent-soft)}.helper-aside__top{display:flex;align-items:center;justify-content:space-between;padding:12px 14px 10px;border-bottom:1px solid rgba(255,255,255,.05)}.helper-aside__title{font-size:12.5px;font-weight:600;color:#c7cdd6}.helper-aside__close{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;color:#6b7280;font-size:20px;line-height:1;width:26px;height:26px;border-radius:6px;cursor:pointer;transition:color .12s ease,background .12s ease}.helper-aside__close:hover{color:#e5e7eb;background:#ffffff0f}.helper-modal__backdrop{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center;background:transparent;z-index:20;animation:fadein .14s ease;padding:16px;pointer-events:auto}.helper-modal{width:min(360px,100%);background:linear-gradient(180deg,#1f2733,#1a2029);border:1px solid rgba(255,255,255,.1);border-radius:12px;padding:20px 22px 16px;color:#e5e7eb;box-shadow:0 24px 48px -12px #0009,0 1px #ffffff14 inset;animation:fadein .18s ease}.helper-modal__title{font-size:14px;font-weight:600;color:#f3f4f6;margin-bottom:8px}.helper-modal__desc{font-size:12.5px;line-height:1.6;color:#b6bcc6;margin-bottom:18px}.helper-modal__desc code{font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,monospace;font-size:11.5px;padding:1px 6px;background:#ffffff14;border:1px solid rgba(255,255,255,.08);border-radius:4px;color:#e7eaef}.helper-modal__actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}.helper-modal__btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#ffffff0f;color:#d2d6dd;border:1px solid rgba(255,255,255,.1);border-radius:7px;padding:7px 14px;font-size:12.5px;font-weight:500;cursor:pointer;transition:background .12s ease,color .12s ease,transform 80ms ease}.helper-modal__btn:hover{background:#ffffff1a;color:#fff}.helper-modal__btn:active{transform:translateY(1px)}.helper-modal__btn--ghost{background:transparent;border-color:transparent;color:#ffffff8c;margin-right:auto;padding-left:4px;padding-right:4px}.helper-modal__btn--ghost:hover{background:transparent;color:#ffffffd9}.helper-modal__btn--primary{background:linear-gradient(180deg,#5b8df7,#4570d8);border-color:#ffffff2e;color:#fff}.helper-modal__btn--primary:hover{background:linear-gradient(180deg,#6c9af9,#5a82e0)}.helper-modal__btn:disabled{filter:grayscale(.4) brightness(.7);cursor:default}.helper-placeholder{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;padding:32px 40px;text-align:center;color:#9ca3af}.helper-placeholder__mark{font-size:48px;line-height:1;filter:grayscale(.2);opacity:.85}.helper-placeholder__title{margin:0;font-size:16px;font-weight:600;color:#e5e7eb}.helper-placeholder__sub{margin:0;font-size:12.5px;line-height:1.6;color:#9ca3af;max-width:320px}.helper-placeholder__note{margin-top:8px;font-size:11px;color:#6b7280;padding:4px 10px;background:#ffffff08;border:1px solid rgba(255,255,255,.06);border-radius:999px}.helper-placeholder__note code{font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,monospace;font-size:11px;color:#c7cdd6}.section{background:linear-gradient(180deg,#141820b8,#0d1016b8);border:1px solid rgba(255,255,255,.06);border-radius:14px;box-shadow:0 1px #ffffff0a inset,0 14px 30px #00000052;-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);padding:18px 18px 16px}.section-head{display:flex;align-items:center;gap:8px;padding:0 4px 14px;border-bottom:1px solid rgba(255,255,255,.04);margin-bottom:14px}.section-icon{font-size:14px;opacity:.8}.section-head h2{margin:0;font-size:13.5px;font-weight:600;color:#e5e7eb;letter-spacing:.2px}.section-sub{font-size:12px;color:#6b7280}.section-body{display:flex;flex-direction:column;gap:10px}.grid{display:grid;grid-template-columns:repeat(auto-fill,200px);gap:14px}.bcard{position:relative;width:200px;height:200px;background:#1418208c;border:1px solid rgba(255,255,255,.07);border-radius:14px;padding:16px;display:flex;flex-direction:column;overflow:hidden;transition:transform .18s ease,border-color .18s ease,box-shadow .18s ease;--brand: #5b8df7;--accent-cloud: #f59e0b;--accent-custom: #8b5cf6}.bcard:hover{transform:translateY(-2px);border-color:#ffffff24;box-shadow:0 12px 32px -10px #00000073,0 1px #ffffff0a inset}.bcard__accent{position:absolute;top:0;left:0;right:0;height:3px;background:var(--brand);opacity:.9}.bcard--cloud .bcard__accent{background:var(--accent-cloud)}.bcard--custom .bcard__accent{background:var(--accent-custom)}.bcard--online:before{content:"";position:absolute;bottom:-40%;right:-20%;width:140%;height:100%;background:radial-gradient(circle at center,rgba(91,141,247,.22) 0%,transparent 60%);pointer-events:none;opacity:.55}.bcard--cloud.bcard--online:before{background:radial-gradient(circle at center,rgba(245,158,11,.22) 0%,transparent 60%)}.bcard--custom.bcard--online:before{background:radial-gradient(circle at center,rgba(139,92,246,.22) 0%,transparent 60%)}.bcard__top{display:flex;align-items:center;justify-content:space-between;position:relative;z-index:5}.bcard__pill{display:inline-flex;align-items:center;gap:6px;padding:4px 8px;background:#08090eb3;border:1px solid rgba(255,255,255,.07);border-radius:999px;color:#9ca3af}.bcard__pill svg{display:block}.bcard__dot{width:7px;height:7px;border-radius:50%;background:#6b7280;flex-shrink:0}.bcard__dot[data-tone=ok]{background:#34d399;box-shadow:0 0 0 2px #34d39940}.bcard__dot[data-tone=off]{background:#6b7280}.bcard__dot[data-tone=warn]{background:#fbbf24;box-shadow:0 0 0 2px #fbbf2440}.bcard__dot[data-tone=err]{background:#f87171;box-shadow:0 0 0 2px #f8717140}.bcard__badge{display:inline-flex;align-items:center;padding:2px 8px;border-radius:999px;background:var(--accent-soft);color:var(--accent-text);font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.5px}.bcard__body{flex:1;display:flex;flex-direction:column;gap:4px;padding-top:12px;padding-bottom:12px;position:relative;z-index:1;min-width:0}.bcard__name{margin:0;font-size:17px;font-weight:700;color:#e5e7eb;letter-spacing:-.015em;line-height:1.25;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bcard__host{font-family:ui-monospace,SFMono-Regular,SF Mono,Menlo,monospace;font-size:11px;color:#9ca3af;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.bcard__meta{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}.bcard__chip{font-size:10.5px;color:#9ca3af;background:#ffffff08;border:1px solid rgba(255,255,255,.06);padding:2px 7px;border-radius:999px}.bcard__cta{position:relative;z-index:1;display:inline-flex;align-items:center;justify-content:center;gap:8px;width:100%;height:38px;border:1px solid var(--line);border-radius:9px;background:#ffffff0d;color:var(--text);font-size:13px;font-weight:600;letter-spacing:.1px;cursor:pointer;transition:transform 80ms ease,background .14s ease,border-color .14s ease,color .14s ease;box-shadow:none}.bcard--cloud .bcard__cta,.bcard--custom .bcard__cta{background:#ffffff0d;box-shadow:none}.bcard__cta:hover{background:var(--accent-soft);border-color:var(--accent-line);color:var(--accent-text)}.bcard__cta:active{transform:translateY(1px)}.bcard__cta:disabled{background:#ffffff08;color:#6b7280;cursor:not-allowed;box-shadow:none;border:1px solid rgba(255,255,255,.06)}.empty{font-size:12.5px;color:#6b7280;padding:14px 16px;background:#ffffff05;border:1px dashed rgba(255,255,255,.08);border-radius:10px}.bcard__menuwrap{position:relative}.bcard__kebab{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border:1px solid transparent;border-radius:8px;background:transparent;color:#8b949e;cursor:pointer;transition:.15s}.bcard__kebab:hover:not(:disabled){color:#e6edf3;background:#ffffff0f;border-color:#ffffff1f}.bcard__kebab:disabled{opacity:.5;cursor:default}.bcard__kebab.has-dot{color:#e6edf3}.bcard__kebab.has-dot:after{content:"";position:absolute;top:3px;right:3px;width:7px;height:7px;border-radius:50%;background:#f59e0b;box-shadow:0 0 0 2px #0f1115}.bcard__menu{position:absolute;top:32px;right:0;z-index:20;min-width:150px;padding:5px;background:#1b2027;border:1px solid rgba(255,255,255,.12);border-radius:10px;box-shadow:0 10px 30px #00000073;display:flex;flex-direction:column;gap:2px}.bcard__menu-item{text-align:left;width:100%;border:none;background:transparent;color:#d1d5db;border-radius:7px;padding:7px 10px;font-size:12.5px;cursor:pointer;transition:.12s}.bcard__menu-item:hover{background:#ffffff12;color:#fff}.bcard__menu-item.is-accent{color:var(--accent-text);font-weight:600}.bcard__menu-item.is-accent:hover{background:var(--accent-soft);color:#c7dbff}.bcard__menu-item.is-danger{color:#f7a3a3}.bcard__menu-item.is-danger:hover{background:#ef444429;color:#fff}.bcard__menu--portal{z-index:9990}.bcard__menu--portal .bcard__menu-item{white-space:normal;word-break:break-word}.docker-setup{margin-bottom:14px;padding:14px 16px;background:linear-gradient(180deg,#5b8df714,#14182080);border:1px solid rgba(91,141,247,.22);border-radius:12px}.docker-setup__head{display:flex;flex-direction:column;gap:3px;margin-bottom:12px}.docker-setup__title{font-size:13.5px;font-weight:600;color:#e6edf3}.docker-setup__sub{font-size:11.5px;color:#9ca3af;line-height:1.5}.docker-setup__steps{display:flex;flex-direction:column;gap:6px;margin-bottom:12px}.docker-step{display:flex;align-items:center;gap:8px;font-size:12px;color:#9ca3af}.docker-step__dot{width:8px;height:8px;border-radius:50%;flex:none;background:#3a4150;transition:.2s}.docker-step__label{color:#c7cdd6;min-width:96px}.docker-step__msg{color:#8b949e;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.docker-step__pct{margin-left:auto;font-variant-numeric:tabular-nums;color:#5b8df7;flex:none}.docker-step.is-running .docker-step__dot{background:#5b8df7;box-shadow:0 0 0 3px #5b8df72e;animation:dpulse 1.2s ease-in-out infinite}.docker-step.is-running .docker-step__label{color:#e6edf3}.docker-step.is-done .docker-step__dot,.docker-step.is-skip .docker-step__dot{background:#10b981}.docker-step.is-skip{opacity:.7}.docker-step.is-retry .docker-step__dot{background:#f59e0b}.docker-step.is-error .docker-step__dot{background:#ef4444;box-shadow:0 0 0 3px #ef444426}.docker-step.is-error .docker-step__msg{color:#f7a3a3}@keyframes dpulse{0%,to{opacity:1}50%{opacity:.45}}.docker-setup__actions{display:flex;gap:8px;align-items:center}.docker-setup__actions .btn-primary{width:auto;padding:7px 16px;font-size:12.5px}.bcard__ver{font-size:11px;color:#8b949e;font-variant-numeric:tabular-nums}.bcard__chip--new{color:var(--accent-text);background:var(--accent-soft);border-color:var(--accent-line)}.bcard__opmsg{display:flex;align-items:center;gap:6px;margin-top:9px;font-size:11.5px;color:#9ca3af;word-break:break-word}.toast-host{position:fixed;right:16px;bottom:16px;z-index:9999;display:flex;flex-direction:column;gap:8px;max-width:min(360px,calc(100vw - 32px));pointer-events:none}.toast{pointer-events:auto;position:relative;display:flex;flex-direction:column;gap:6px;padding:10px 30px 10px 12px;font-size:12.5px;line-height:1.4;color:var(--text, #e6eaf0);background:#161a21f5;border:1px solid rgba(125,135,150,.22);border-left:3px solid var(--accent, #3b82f6);border-radius:10px;box-shadow:0 8px 28px #0000006b;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);animation:toast-in .18s ease}@keyframes toast-in{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}.toast[data-status=error]{border-left-color:#f87171}.toast[data-status=error] .toast__msg{color:#f87171}.toast[data-status=done]{border-left-color:#4ade80}.toast[data-status=done] .toast__msg{color:#4ade80}.toast__msg{word-break:break-word}.toast__x{position:absolute;top:6px;right:8px;background:none;border:none;cursor:pointer;padding:0;font-size:15px;line-height:1;color:var(--text-dim, #9da7b3)}.toast__x:hover{color:var(--text, #e6eaf0)}.toast__bar{display:block;height:4px;border-radius:2px;background:#7d879640;overflow:hidden}.toast__bar>span{display:block;height:100%;border-radius:2px;background:var(--accent, #3b82f6);transition:width .25s ease}.drawer-scrim{position:fixed;top:0;right:0;bottom:0;left:0;z-index:10000;display:flex;align-items:flex-end;justify-content:center;background:#06080c80;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px);animation:drawer-fade .18s ease}@keyframes drawer-fade{0%{opacity:0}to{opacity:1}}.drawer{width:min(560px,calc(100vw - 24px));max-height:76vh;display:flex;flex-direction:column;margin-bottom:12px;background:#14181ffa;border:1px solid rgba(125,135,150,.2);border-radius:16px 16px 12px 12px;box-shadow:0 -10px 44px #00000080;overflow:hidden;animation:drawer-up .24s cubic-bezier(.22,1,.36,1)}@keyframes drawer-up{0%{opacity:0;transform:translateY(28px)}to{opacity:1;transform:none}}.drawer__head{display:flex;align-items:center;justify-content:space-between;padding:14px 14px 12px 16px;border-bottom:1px solid rgba(125,135,150,.14)}.drawer__title{display:flex;align-items:center;gap:11px}.drawer__spark{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:8px;font-size:14px;font-weight:700;background:#5b8df729;color:var(--brand, #5b8df7)}.drawer__spark--done{background:#4ade8029;color:#4ade80}.drawer__spark--error{background:#f8717129;color:#f87171}.drawer__h{font-size:13.5px;font-weight:650;color:var(--text, #e6eaf0)}.drawer__sub{font-size:11.5px;color:var(--text-dim, #9da7b3);margin-top:1px}.drawer__x{background:none;border:none;cursor:pointer;padding:2px 6px;font-size:19px;line-height:1;color:var(--text-dim, #9da7b3);border-radius:6px}.drawer__x:hover:not(:disabled){color:var(--text, #e6eaf0);background:#7d87961f}.drawer__x:disabled{opacity:.35;cursor:default}.drawer__steps{display:flex;align-items:center;gap:0;padding:14px 18px 4px}.drawer__step{display:flex;align-items:center;gap:7px;color:var(--text-dim, #9da7b3);font-size:12px}.drawer__step-dot{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;font-size:11px;font-weight:700;border:1.5px solid rgba(125,135,150,.35);color:var(--text-dim, #9da7b3);background:transparent;flex:none}.drawer__step-bar{width:30px;height:1.5px;background:#7d879640;margin:0 8px}.drawer__step.is-active .drawer__step-dot{border-color:var(--brand, #5b8df7);color:var(--brand, #5b8df7);box-shadow:0 0 0 3px #5b8df72e}.drawer__step.is-active .drawer__step-label{color:var(--text, #e6eaf0)}.drawer__step.is-done .drawer__step-dot{border-color:#4ade80;color:#06210f;background:#4ade80}.drawer__step.is-done .drawer__step-bar{background:#4ade8080}.drawer__step.is-error .drawer__step-dot{border-color:#f87171;color:#f87171}.drawer__log{flex:1;min-height:96px;overflow-y:auto;margin:10px 14px;padding:10px 12px;background:#0a0c10b3;border:1px solid rgba(125,135,150,.12);border-radius:10px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:11.5px;line-height:1.7}.drawer__log--scroll{max-height:168px;scrollbar-width:thin;scrollbar-color:rgba(125,135,150,.4) transparent}.drawer__log--scroll::-webkit-scrollbar{width:7px}.drawer__log--scroll::-webkit-scrollbar-track{background:transparent;margin:4px 0}.drawer__log--scroll::-webkit-scrollbar-thumb{background:#7d879652;border-radius:6px;border:1.5px solid transparent;background-clip:padding-box}.drawer__log--scroll::-webkit-scrollbar-thumb:hover{background:#7d87968c;background-clip:padding-box}.drawer__dlbars{display:flex;flex-direction:column;gap:10px;margin:12px 14px 2px}.dlbar{padding:10px 12px;border-radius:10px;background:#0a0c108c;border:1px solid rgba(125,135,150,.14)}.dlbar__head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:7px}.dlbar__name{font-size:12.5px;font-weight:600;color:var(--text, #e6eaf0)}.dlbar__pct{font-size:11px;color:var(--text-dim, #9da7b3);font-variant-numeric:tabular-nums}.dlbar__track{height:6px;border-radius:4px;background:#7d879629;overflow:hidden}.dlbar__fill{height:100%;border-radius:4px;background:linear-gradient(90deg,#5b8df7,#2496ed);transition:width .2s ease}.dlbar__fill.is-done{background:#4ade80}.dlbar__url{margin-top:7px;font-size:10.5px;color:#6b7686;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.drawer__log-empty{color:var(--text-dim, #9da7b3)}.drawer__line{display:flex;align-items:baseline;gap:8px;padding:1px 0}.drawer__t{color:#6b7686;flex:none;font-variant-numeric:tabular-nums}.drawer__badge{flex:none;padding:0 6px;border-radius:4px;font-size:10px;font-weight:600;background:#5b8df729;color:#8fb0f5}.drawer__badge--swap{background:#f59e0b29;color:#f5b342}.drawer__badge--done{background:#4ade8029;color:#6ee79b}.drawer__linemsg{color:var(--text, #e6eaf0);word-break:break-word}.drawer__line[data-status=error] .drawer__linemsg{color:#f87171}.drawer__line[data-status=done] .drawer__linemsg{color:#6ee79b}.drawer__line[data-status=skip] .drawer__linemsg{color:var(--text-dim, #9da7b3)}.drawer__hint{margin:0 14px 8px;padding:8px 12px;background:#f59e0b1a;border:1px solid rgba(245,158,11,.28);border-radius:8px;color:#f5b342;font-size:11.5px;line-height:1.5}.drawer__foot{display:flex;align-items:center;gap:8px;padding:12px 14px;border-top:1px solid rgba(125,135,150,.14)}.drawer__foot-status{font-size:12.5px;color:var(--text-dim, #9da7b3);margin-right:auto}.drawer__foot-status.is-error{color:#f87171}.drawer__foot-status.is-done{color:#4ade80}.drawer__btn{padding:7px 16px;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:550;background:#7d879624;border:1px solid rgba(125,135,150,.2);color:var(--text, #e6eaf0)}.drawer__btn:hover{background:#7d879638}.drawer__btn.is-accent{background:var(--brand, #5b8df7);border-color:var(--brand, #5b8df7);color:#fff}.drawer__btn.is-accent:hover{filter:brightness(1.08)}.mitm-card{border:1px solid rgba(125,135,150,.22);border-radius:12px;padding:14px 16px;margin-bottom:14px;background:#1e242e66}.mitm-card--on{border-color:#4ade8059;background:#16281e66}.mitm-pill{position:fixed;top:64px;right:16px;z-index:9998;-webkit-app-region:no-drag;display:inline-flex;align-items:center;gap:8px;padding:4px 6px 4px 11px;border-radius:999px;width:fit-content;max-width:calc(100vw - 32px);background:#141e18d9;border:1px solid rgba(74,222,128,.3);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);box-shadow:0 4px 14px #0000004d;font-size:12px;line-height:1}.mitm-pill__dot{width:7px;height:7px;border-radius:50%;flex:none;background:#4ade80;box-shadow:0 0 0 3px #4ade8029}.mitm-pill__dot[data-busy="1"]{background:#9da7b3;box-shadow:0 0 0 3px #9da7b329}.mitm-pill__text{color:var(--text-dim, #9da7b3);white-space:nowrap}.mitm-pill__off{background:none;border:none;cursor:pointer;color:#6b7686;font-size:11.5px;padding:3px 7px;border-radius:999px}.mitm-pill__off:hover{color:#f87171;background:#f871711a}.mitm-card__head{display:flex;align-items:center;gap:8px;margin-bottom:6px}.mitm-card__dot{width:8px;height:8px;border-radius:50%;background:#9da7b3;flex:0 0 auto}.mitm-card__dot[data-state=on]{background:#4ade80;box-shadow:0 0 6px #4ade8099}.mitm-card__dot[data-state=warn]{background:#fbbf24}.mitm-card__dot[data-state=off]{background:#60a5fa}.mitm-card__title{font-size:14px;font-weight:600;color:var(--text, #e6edf3)}.mitm-card__desc{font-size:12.5px;line-height:1.5;color:var(--text-dim, #9da7b3);margin:0 0 10px}.mitm-card__note{color:#fbbf24}.mitm-card__error{font-size:12px;color:#f87171;margin-bottom:8px}.mitm-card__actions{display:flex;gap:8px}.mitm-card__btn{font-size:13px;padding:7px 16px;border-radius:8px;border:none;cursor:pointer;background:var(--accent, #3b82f6);color:#fff;font-weight:500}.mitm-card__btn:disabled{opacity:.6;cursor:default}.mitm-card__btn--ghost{background:transparent;border:1px solid rgba(125,135,150,.35);color:var(--text-dim, #9da7b3)}.mitm-card__sub{color:var(--text-dim, #9da7b3);opacity:.8;font-size:11.5px}.terms-gate{display:flex;align-items:center;justify-content:center;padding:24px}.terms-gate__panel,.terms-gate__panel *{-webkit-app-region:no-drag}.terms-gate__panel{position:relative;z-index:1;width:min(680px,94vw);max-height:90vh;display:flex;flex-direction:column;background:#141921eb;border:1px solid rgba(125,135,150,.22);border-radius:16px;padding:28px 30px;box-shadow:0 24px 60px #00000080}.terms-gate__title{font-size:20px;font-weight:700;margin:0 0 4px;color:var(--text, #e6edf3)}.terms-gate__subtitle{font-size:13px;color:var(--text-dim, #9da7b3);margin:0 0 16px}.terms-gate__body{overflow-y:auto;flex:1 1 auto;min-height:0;padding-right:8px;border-top:1px solid rgba(125,135,150,.15);border-bottom:1px solid rgba(125,135,150,.15);padding-top:14px;padding-bottom:14px}.terms-gate__h2{font-size:14px;font-weight:600;margin:0 0 10px;color:var(--text, #e6edf3)}.terms-gate__summary{margin:0 0 14px;padding-left:20px}.terms-gate__summary li{font-size:13px;line-height:1.6;color:var(--text-dim, #c2cbd6);margin-bottom:8px}.terms-gate__viewfull{background:none;border:none;color:var(--accent, #3b82f6);cursor:pointer;font-size:13px;padding:4px 0;text-decoration:underline}.terms-gate__fulltext{white-space:pre-wrap;word-break:break-word;font-size:12px;line-height:1.6;color:var(--text-dim, #b3bcc8);background:#0003;border-radius:8px;padding:14px;margin:10px 0 0;font-family:inherit}.terms-gate__scrollhint{font-size:12px;color:#fbbf24;text-align:center;margin:12px 0 0}.terms-gate__actions{display:flex;gap:12px;justify-content:flex-end;margin-top:18px}.terms-gate__btn{font-size:14px;padding:10px 22px;border-radius:9px;border:none;cursor:pointer;background:var(--accent, #3b82f6);color:#fff;font-weight:600}.terms-gate__btn:disabled{opacity:.45;cursor:not-allowed}.terms-gate__btn--ghost{background:transparent;border:1px solid rgba(125,135,150,.35);color:var(--text-dim, #9da7b3);font-weight:500}
@@ -6,8 +6,8 @@
6
6
  <link rel="icon" type="image/svg+xml" href="./favicon.svg" />
7
7
  <link rel="icon" type="image/png" sizes="256x256" href="./favicon-256.png" />
8
8
  <title>CiCy Desktop</title>
9
- <script type="module" crossorigin src="./assets/index-C7gQsfPP.js"></script>
10
- <link rel="stylesheet" crossorigin href="./assets/index-CKpaMBKz.css">
9
+ <script type="module" crossorigin src="./assets/index-B04YSZUc.js"></script>
10
+ <link rel="stylesheet" crossorigin href="./assets/index-Bs9ihcPL.css">
11
11
  </head>
12
12
  <body>
13
13
  <div id="root"></div>
@@ -13,6 +13,8 @@
13
13
  // along with src/sidecar/installer.js and src/sidecar/wsl.js.)
14
14
 
15
15
  const { ipcMain } = require("electron");
16
+ const fs = require("fs");
17
+ const os = require("os");
16
18
  const path = require("path");
17
19
  const sidecar = require("../sidecar/cicy-code");
18
20
  const docker = require("../sidecar/docker");
@@ -23,10 +25,23 @@ const PORT = Number(process.env.CICY_CODE_PORT || 8008);
23
25
  // :8009 (its own container + volume), alongside the native local daemon on
24
26
  // :8008. The homepage "Docker cicy-code" card owns its lifecycle; if Docker
25
27
  // Desktop is missing the card installs it first (installer downloads to the
26
- // user's Desktop).
28
+ // user's Desktop). The whole cicy home is persisted to a named volume so the
29
+ // entire container state survives recreation (主人: "把整个 docker 挂出来").
27
30
  const APP_PORT = Number(process.env.CICY_DOCKER_APP_PORT || 8009);
28
31
  const APP_CONTAINER = process.env.CICY_DOCKER_APP_CONTAINER || "cicy-code-docker";
29
- const APP_VOLUME = process.env.CICY_DOCKER_APP_VOLUME || "cicy-ai-docker-data";
32
+ const APP_VOLUME = process.env.CICY_DOCKER_APP_VOLUME || "cicy-team";
33
+ const APP_MOUNT = process.env.CICY_DOCKER_APP_MOUNT || "/home/cicy";
34
+ // The Docker-版 instance reaches the LLM through the cicy gateway, authenticated
35
+ // with the LOCAL 8008 team's api_token (主人: "key 用 local team 8008 的"). 8008
36
+ // is started by default on Windows and its token is already minted by the time
37
+ // the user opens the Docker card.
38
+ const GATEWAY_ENDPOINT = process.env.CICY_AI_GATEWAY_LLM_ENDPOINT || "https://gateway.cicy-ai.com";
39
+ function readLocalApiToken() {
40
+ try {
41
+ const p = path.join(os.homedir(), "cicy-ai", "global.json");
42
+ return String(JSON.parse(fs.readFileSync(p, "utf8")).api_token || "");
43
+ } catch { return ""; }
44
+ }
30
45
 
31
46
  let registered = false;
32
47
 
@@ -104,42 +119,86 @@ function register({ sidecarLogPath } = {}) {
104
119
  }
105
120
  });
106
121
 
122
+ // Common run options for the :8009 instance: its own container/volume, the
123
+ // whole-home mount, and the LLM gateway env keyed by the 8008 team's token.
124
+ const appOpts = () => {
125
+ const token = readLocalApiToken();
126
+ const env = { CICY_AI_GATEWAY_LLM_ENDPOINT: GATEWAY_ENDPOINT };
127
+ if (token) env.CICY_AI_GATEWAY_LLM_API_KEY = token;
128
+ return { port: APP_PORT, container: APP_CONTAINER, volume: APP_VOLUME, mountTarget: APP_MOUNT, env };
129
+ };
130
+ // Register the running :8009 instance as a (custom) team so the card's "打开"
131
+ // reuses the token-injected open/reload flow. addTeam dedups by host:port.
132
+ const registerAppTeam = async () => {
133
+ try {
134
+ const lt = require("./local-teams");
135
+ const tok = await docker.readContainerToken(APP_PORT);
136
+ await lt.addTeam({ base_url: `http://127.0.0.1:${APP_PORT}`, name: "Docker cicy-code", ...(tok ? { api_token: tok } : {}) });
137
+ } catch { /* best-effort — the container itself is up */ }
138
+ };
139
+
107
140
  // One-click bootstrap of the Docker-版 instance: install Docker Desktop if
108
- // missing (installer → user's Desktop), load the image, start the :8009
109
- // container (its own name/volume), wait for health. Streams phase/progress on
110
- // 'docker:app-progress' so the card's modal mirrors the cicy-code 升级 modal.
141
+ // missing (installer → Desktop) WHILE downloading the R2 image (→ ~/Downloads)
142
+ // in parallel, import it, start the :8009 container, wait for health. Streams
143
+ // phase/progress on 'docker:app-progress' so the card's modal mirrors the
144
+ // cicy-code 升级 modal. Idempotent + resumable → the modal's 重试 just re-runs.
111
145
  ipcMain.handle("docker:app-bootstrap", async (e) => {
112
146
  if (process.platform !== "win32") return { ok: false, error: "Docker cicy-code is Windows-only" };
113
147
  try {
114
148
  const installDest = path.join(docker.desktopDir(), "Docker Desktop Installer.exe");
115
149
  const result = await docker.bootstrap({
116
- port: APP_PORT, container: APP_CONTAINER, volume: APP_VOLUME, installDest,
150
+ ...appOpts(), installDest,
117
151
  onProgress: (ev) => { try { e.sender.send("docker:app-progress", ev); } catch {} },
118
152
  });
119
- // Healthy register :8009 as a (custom) team so the card's "打开" reuses
120
- // the token-injected open/reload flow. addTeam dedups by host:port.
121
- if (result && result.ok) {
122
- try {
123
- const lt = require("./local-teams");
124
- const tok = await docker.readContainerToken(APP_PORT);
125
- await lt.addTeam({
126
- base_url: `http://127.0.0.1:${APP_PORT}`, name: "Docker cicy-code",
127
- ...(tok ? { api_token: tok } : {}),
128
- });
129
- } catch { /* best-effort — the container itself is up */ }
130
- }
153
+ if (result && result.ok) await registerAppTeam();
131
154
  return result;
132
155
  } catch (err) {
133
156
  return { ok: false, error: err.message };
134
157
  }
135
158
  });
136
159
 
137
- // Stop + remove the :8009 Docker container (card's "停止").
160
+ // menu 重启: `docker restart` the :8009 container, wait for health.
161
+ ipcMain.handle("docker:app-restart", async () => {
162
+ try {
163
+ await docker.restart({ container: APP_CONTAINER });
164
+ const ok = await docker.waitUntil(() => docker.probeHealth(APP_PORT), { totalMs: 60000, everyMs: 2000 });
165
+ return { ok };
166
+ } catch (e) { return { ok: false, error: e.message }; }
167
+ });
168
+
169
+ // ⋯ menu → 停止: graceful `docker stop` (keeps the container; data persists in
170
+ // the named volume). The card's 启动 path re-creates/starts it.
138
171
  ipcMain.handle("docker:app-stop", async () => {
139
- try { await docker.stop({ container: APP_CONTAINER }); return { ok: true }; }
172
+ try { await docker.stopContainer({ container: APP_CONTAINER }); return { ok: true }; }
140
173
  catch (e) { return { ok: false, error: e.message }; }
141
174
  });
142
175
 
176
+ // ⋯ menu → 升级: re-pull the latest R2 image (→ ~/Downloads, resume/skip + bad-
177
+ // partial delete), import it, re-create the :8009 container on the new image.
178
+ // Streams on 'docker:app-progress' so the same modal shows the upgrade.
179
+ ipcMain.handle("docker:app-upgrade", async (e) => {
180
+ if (process.platform !== "win32") return { ok: false, error: "Docker cicy-code is Windows-only" };
181
+ const emit = (ev) => { try { e.sender.send("docker:app-progress", ev); } catch {} };
182
+ try {
183
+ if (!(await docker.dockerOk())) { emit({ phase: "done", status: "error", message: "Docker 未运行" }); return { ok: false, error: "docker_not_running" }; }
184
+ const tmp = await docker.downloadImageTarball({ emit });
185
+ await docker.loadImageFromTarball(tmp, { emit });
186
+ emit({ phase: "image", status: "done", message: "镜像已更新" });
187
+ emit({ phase: "container", status: "running", message: "用新镜像重建容器…" });
188
+ await docker.stop({ container: APP_CONTAINER });
189
+ const child = await docker.start(appOpts());
190
+ if (!child) { emit({ phase: "done", status: "error", message: "容器启动失败" }); return { ok: false, error: "container_start_failed" }; }
191
+ emit({ phase: "health", status: "running", message: "等待就绪…" });
192
+ const ok = await docker.waitUntil(() => docker.probeHealth(APP_PORT), { totalMs: 120000, everyMs: 3000 });
193
+ emit({ phase: "done", status: ok ? "done" : "error", message: ok ? "升级完成 🎉" : "启动了但 :8009 还没响应" });
194
+ if (ok) await registerAppTeam();
195
+ return { ok };
196
+ } catch (err) {
197
+ emit({ phase: "done", status: "error", message: `升级失败:${err.message}` });
198
+ return { ok: false, error: err.message };
199
+ }
200
+ });
201
+
143
202
  // Start (or reuse) the cicy-code daemon. probeExisting inside start() reuses
144
203
  // a healthy :8008; otherwise it spawns `npx cicy-code` / the Docker container.
145
204
  ipcMain.handle("sidecar:start", async () => {
@@ -1,7 +1,7 @@
1
1
  const fs = require("fs");
2
2
  const os = require("os");
3
3
  const path = require("path");
4
- const { spawn } = require("child_process");
4
+ const { spawn, execFileSync } = require("child_process");
5
5
  const { isPortOpen } = require("../utils/process-utils");
6
6
  const { waitForDebugger, getVersion } = require("./chrome-cdp-client");
7
7
 
@@ -73,19 +73,79 @@ function isDirectPath(binaryPath) {
73
73
  return binaryPath.includes(path.sep) || (process.platform === "win32" && /^[a-zA-Z]:\\/.test(binaryPath));
74
74
  }
75
75
 
76
+ // Windows: Chrome registers its exact install path under "App Paths" on install,
77
+ // independent of where it landed (per-user vs per-machine, custom drive). This is
78
+ // far more reliable than the %ProgramFiles% guesses — those miss per-user installs
79
+ // and break when the launching process runs with a stripped env (e.g. the
80
+ // StartElectron scheduled task), which is why an installed Chrome can still read
81
+ // as "not found".
82
+ function queryWindowsChromeFromRegistry() {
83
+ if (process.platform !== "win32") return null;
84
+ const keys = [
85
+ "HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe",
86
+ "HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe",
87
+ "HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths\\chrome.exe",
88
+ ];
89
+ for (const key of keys) {
90
+ try {
91
+ const out = execFileSync("reg", ["query", key, "/ve"], {
92
+ encoding: "utf8",
93
+ windowsHide: true,
94
+ timeout: 5000,
95
+ });
96
+ const m = out.match(/REG_SZ\s+(.+?\.exe)\s*$/im);
97
+ if (m && fs.existsSync(m[1].trim())) return m[1].trim();
98
+ } catch (_) {}
99
+ }
100
+ return null;
101
+ }
102
+
103
+ // PATH lookup for a bare command — `where` on Windows, `which` on posix. Returns
104
+ // the first existing match, else null.
105
+ function whichBinary(cmd) {
106
+ try {
107
+ const tool = process.platform === "win32" ? "where" : "which";
108
+ const out = execFileSync(tool, [cmd], { encoding: "utf8", windowsHide: true, timeout: 5000 });
109
+ const first = out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0];
110
+ if (first && fs.existsSync(first)) return first;
111
+ } catch (_) {}
112
+ return null;
113
+ }
114
+
76
115
  function resolveChromeBinary(binaryPath) {
77
- const candidates = [binaryPath, ...getBinaryCandidates()].filter(Boolean);
116
+ // 1) Explicit override (config.chromeBinary / --chrome-binary) that exists.
117
+ if (binaryPath && isDirectPath(binaryPath) && fs.existsSync(binaryPath)) return binaryPath;
118
+
119
+ // 2) Windows registry App Paths — authoritative, install-location independent.
120
+ const regPath = queryWindowsChromeFromRegistry();
121
+ if (regPath) return regPath;
78
122
 
123
+ // 3) Platform candidates: concrete paths checked for existence; bare commands
124
+ // resolved via PATH (where/which) so we only accept a Chrome that's actually
125
+ // present.
126
+ const candidates = [binaryPath, ...getBinaryCandidates()].filter(Boolean);
127
+ const bareCommands = [];
79
128
  for (const candidate of candidates) {
80
129
  if (isDirectPath(candidate)) {
81
- if (fs.existsSync(candidate)) {
82
- return candidate;
83
- }
84
- continue;
130
+ if (fs.existsSync(candidate)) return candidate;
131
+ } else {
132
+ bareCommands.push(candidate);
133
+ const resolved = whichBinary(candidate);
134
+ if (resolved) return resolved;
85
135
  }
86
- return candidate;
87
136
  }
88
137
 
138
+ // 4) Name-based last resort.
139
+ const byName =
140
+ whichBinary(process.platform === "win32" ? "chrome" : "google-chrome") ||
141
+ whichBinary(process.platform === "win32" ? "chrome.exe" : "chromium");
142
+ if (byName) return byName;
143
+
144
+ // 5) Nothing concrete found. On posix, let spawn try the first bare command
145
+ // (covers exotic PATH setups where `which` itself isn't available); on
146
+ // Windows everything is a concrete path, so fail with a clear message.
147
+ if (bareCommands.length) return bareCommands[0];
148
+
89
149
  throw new Error(
90
150
  "Chrome/Chromium binary not found. Please configure chromeBinary or --chrome-binary."
91
151
  );
@@ -121,5 +121,37 @@
121
121
  "scrollHint": "Please read to the end to continue",
122
122
  "menu": "Terms of Use",
123
123
  "close": "Close"
124
+ },
125
+ "docker": {
126
+ "title": "Docker cicy-code",
127
+ "install": "Download & Install",
128
+ "start": "Start",
129
+ "running": "Running · :8009",
130
+ "notRunning": "Stopped · click Start",
131
+ "notInstalled": "Docker Desktop not installed",
132
+ "working": "Working…",
133
+ "ready": "Docker cicy-code is ready",
134
+ "failed": "Install failed",
135
+ "upgraded": "Upgraded to latest",
136
+ "upgradeFailed": "Upgrade failed",
137
+ "manage": "Manage Docker cicy-code",
138
+ "restart": "Restart",
139
+ "restarted": "Restarted",
140
+ "restarting": "Restarting…",
141
+ "stop": "Stop",
142
+ "stopped": "Stopped",
143
+ "stoping": "Stopping…",
144
+ "upgrade": "Upgrade (pull latest image)",
145
+ "opFailed": "Operation failed",
146
+ "setupTitle": "Install Docker cicy-code",
147
+ "busy": "In progress",
148
+ "preparing": "Preparing…",
149
+ "installing2": "Installing…",
150
+ "background": "Continue in background"
151
+ },
152
+ "common": {
153
+ "close": "Close",
154
+ "retry": "Retry",
155
+ "done": "Done"
124
156
  }
125
157
  }
@@ -120,5 +120,37 @@
120
120
  "scrollHint": "Veuillez lire jusqu'au bout pour continuer",
121
121
  "menu": "Conditions d'utilisation",
122
122
  "close": "Fermer"
123
+ },
124
+ "docker": {
125
+ "title": "Docker cicy-code",
126
+ "install": "Télécharger et installer",
127
+ "start": "Démarrer",
128
+ "running": "En cours · :8009",
129
+ "notRunning": "Arrêté · cliquez sur Démarrer",
130
+ "notInstalled": "Docker Desktop non installé",
131
+ "working": "En cours…",
132
+ "ready": "Docker cicy-code est prêt",
133
+ "failed": "Échec de l'installation",
134
+ "upgraded": "Mis à jour",
135
+ "upgradeFailed": "Échec de la mise à niveau",
136
+ "manage": "Gérer Docker cicy-code",
137
+ "restart": "Redémarrer",
138
+ "restarted": "Redémarré",
139
+ "restarting": "Redémarrage…",
140
+ "stop": "Arrêter",
141
+ "stopped": "Arrêté",
142
+ "stoping": "Arrêt…",
143
+ "upgrade": "Mettre à niveau (dernière image)",
144
+ "opFailed": "Échec de l'opération",
145
+ "setupTitle": "Installer Docker cicy-code",
146
+ "busy": "En cours",
147
+ "preparing": "Préparation…",
148
+ "installing2": "Installation…",
149
+ "background": "Continuer en arrière-plan"
150
+ },
151
+ "common": {
152
+ "close": "Fermer",
153
+ "retry": "Réessayer",
154
+ "done": "Terminé"
123
155
  }
124
156
  }
@@ -120,5 +120,37 @@
120
120
  "scrollHint": "続行するには最後までお読みください",
121
121
  "menu": "利用規約",
122
122
  "close": "閉じる"
123
+ },
124
+ "docker": {
125
+ "title": "Docker cicy-code",
126
+ "install": "ダウンロードしてインストール",
127
+ "start": "起動",
128
+ "running": "実行中 · :8009",
129
+ "notRunning": "停止中 · 「起動」をクリック",
130
+ "notInstalled": "Docker Desktop 未インストール",
131
+ "working": "処理中…",
132
+ "ready": "Docker cicy-code の準備完了",
133
+ "failed": "インストール失敗",
134
+ "upgraded": "最新へ更新しました",
135
+ "upgradeFailed": "アップグレード失敗",
136
+ "manage": "Docker cicy-code を管理",
137
+ "restart": "再起動",
138
+ "restarted": "再起動しました",
139
+ "restarting": "再起動中…",
140
+ "stop": "停止",
141
+ "stopped": "停止しました",
142
+ "stoping": "停止中…",
143
+ "upgrade": "アップグレード(最新イメージ取得)",
144
+ "opFailed": "操作に失敗しました",
145
+ "setupTitle": "Docker cicy-code をインストール",
146
+ "busy": "進行中",
147
+ "preparing": "準備中…",
148
+ "installing2": "インストール中…",
149
+ "background": "バックグラウンドで継続"
150
+ },
151
+ "common": {
152
+ "close": "閉じる",
153
+ "retry": "再試行",
154
+ "done": "完了"
123
155
  }
124
156
  }
@@ -121,5 +121,37 @@
121
121
  "scrollHint": "请阅读至底部以继续",
122
122
  "menu": "用户协议",
123
123
  "close": "关闭"
124
+ },
125
+ "docker": {
126
+ "title": "Docker cicy-code",
127
+ "install": "下载安装",
128
+ "start": "启动",
129
+ "running": "运行中 · :8009",
130
+ "notRunning": "未启动 · 点「启动」",
131
+ "notInstalled": "Docker Desktop 未安装",
132
+ "working": "处理中…",
133
+ "ready": "Docker cicy-code 已就绪",
134
+ "failed": "安装失败",
135
+ "upgraded": "已升级到最新",
136
+ "upgradeFailed": "升级失败",
137
+ "manage": "管理 Docker cicy-code",
138
+ "restart": "重启",
139
+ "restarted": "已重启",
140
+ "restarting": "重启中…",
141
+ "stop": "停止",
142
+ "stopped": "已停止",
143
+ "stoping": "停止中…",
144
+ "upgrade": "升级(拉取最新镜像)",
145
+ "opFailed": "操作失败",
146
+ "setupTitle": "安装 Docker cicy-code",
147
+ "busy": "进行中",
148
+ "preparing": "准备中…",
149
+ "installing2": "安装进行中…",
150
+ "background": "在后台继续"
151
+ },
152
+ "common": {
153
+ "close": "关闭",
154
+ "retry": "重试",
155
+ "done": "完成"
124
156
  }
125
157
  }