cicy-desktop 2.1.70 → 2.1.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/package.json +7 -7
  2. package/src/backends/homepage-react/assets/index-BpljolQs.js +365 -0
  3. package/src/backends/homepage-react/assets/{index-BniEbx_j.css → index-C9AZlTew.css} +1 -1
  4. package/src/backends/homepage-react/index.html +2 -2
  5. package/src/backends/local-teams.js +42 -4
  6. package/src/backends/sidecar-ipc.js +23 -1
  7. package/src/i18n/locales/en.json +9 -7
  8. package/src/i18n/locales/zh-CN.json +9 -7
  9. package/src/sidecar/cicy-code.js +54 -111
  10. package/src/sidecar/localbin.js +133 -0
  11. package/src/sidecar/native.js +3 -1
  12. package/workers/render/src/App.css +156 -10
  13. package/workers/render/src/App.jsx +254 -39
  14. package/.env.dev +0 -7
  15. package/src/backends/homepage-react/assets/index-B8gGhz8B.js +0 -365
  16. package/workers/render.bak.20260528-2338/DESIGN_v2.md +0 -254
  17. package/workers/render.bak.20260528-2338/index.html +0 -12
  18. package/workers/render.bak.20260528-2338/package-lock.json +0 -827
  19. package/workers/render.bak.20260528-2338/package.json +0 -19
  20. package/workers/render.bak.20260528-2338/public/_headers +0 -5
  21. package/workers/render.bak.20260528-2338/public/manifest.json +0 -6
  22. package/workers/render.bak.20260528-2338/src/App.css +0 -224
  23. package/workers/render.bak.20260528-2338/src/App.jsx +0 -1028
  24. package/workers/render.bak.20260528-2338/src/api.js +0 -285
  25. package/workers/render.bak.20260528-2338/src/cicycode-ops.js +0 -222
  26. package/workers/render.bak.20260528-2338/src/components/BackendCard.css +0 -299
  27. package/workers/render.bak.20260528-2338/src/components/BackendCard.jsx +0 -133
  28. package/workers/render.bak.20260528-2338/src/components/BackendModal.css +0 -161
  29. package/workers/render.bak.20260528-2338/src/components/BackendModal.jsx +0 -199
  30. package/workers/render.bak.20260528-2338/src/components/Button.css +0 -72
  31. package/workers/render.bak.20260528-2338/src/components/Button.jsx +0 -37
  32. package/workers/render.bak.20260528-2338/src/components/Card.css +0 -42
  33. package/workers/render.bak.20260528-2338/src/components/Card.jsx +0 -21
  34. package/workers/render.bak.20260528-2338/src/components/Icon.jsx +0 -30
  35. package/workers/render.bak.20260528-2338/src/components/Menu.css +0 -55
  36. package/workers/render.bak.20260528-2338/src/components/Menu.jsx +0 -91
  37. package/workers/render.bak.20260528-2338/src/components/SidecarBanner.css +0 -79
  38. package/workers/render.bak.20260528-2338/src/components/SidecarBanner.jsx +0 -84
  39. package/workers/render.bak.20260528-2338/src/components/StatusChip.css +0 -19
  40. package/workers/render.bak.20260528-2338/src/components/StatusChip.jsx +0 -31
  41. package/workers/render.bak.20260528-2338/src/components/Toast.css +0 -31
  42. package/workers/render.bak.20260528-2338/src/components/Toast.jsx +0 -23
  43. package/workers/render.bak.20260528-2338/src/components/WslSetupBanner.css +0 -464
  44. package/workers/render.bak.20260528-2338/src/components/WslSetupBanner.jsx +0 -716
  45. package/workers/render.bak.20260528-2338/src/dockerInstaller.js +0 -0
  46. package/workers/render.bak.20260528-2338/src/i18n/en.json +0 -116
  47. package/workers/render.bak.20260528-2338/src/i18n/fr.json +0 -116
  48. package/workers/render.bak.20260528-2338/src/i18n/index.js +0 -69
  49. package/workers/render.bak.20260528-2338/src/i18n/ja.json +0 -116
  50. package/workers/render.bak.20260528-2338/src/i18n/zh-CN.json +0 -121
  51. package/workers/render.bak.20260528-2338/src/main.js +0 -475
  52. package/workers/render.bak.20260528-2338/src/main.jsx +0 -18
  53. package/workers/render.bak.20260528-2338/src/style.css +0 -275
  54. package/workers/render.bak.20260528-2338/src/styles/base.css +0 -98
  55. package/workers/render.bak.20260528-2338/src/styles/tokens.css +0 -90
  56. package/workers/render.bak.20260528-2338/src/tos.js +0 -72
  57. package/workers/render.bak.20260528-2338/src/worker.js +0 -40
  58. package/workers/render.bak.20260528-2338/src/wslInstaller.js +0 -1563
  59. package/workers/render.bak.20260528-2338/vite.config.js +0 -36
  60. package/workers/render.bak.20260528-2338/wrangler.toml +0 -17
@@ -0,0 +1,133 @@
1
+ // ~/.local/bin install model for the cicy-code daemon binary.
2
+ //
3
+ // 主人指令 (2026-06): cicy-desktop OWNS the binary. It is bundled per-platform
4
+ // (an optionalDependency of cicy-desktop). On first run we copy the bundled,
5
+ // version-named binary into ~/.local/bin/cicy-code-<ver>-<plat> and point
6
+ // ~/.local/bin/cicy-code at it (symlink on mac/linux; a plain COPY on Windows —
7
+ // symlink/junction perms there are a minefield). The daemon is ALWAYS run from
8
+ // that stable ~/.local/bin/cicy-code path — never `npx cicy-code`, which would
9
+ // reuse a stale globally-installed copy and shadow updates.
10
+ //
11
+ // Updates use npm ONLY as a download channel: `npm pack` the per-platform
12
+ // subpackage (sha512-verified), extract the binary, copy it in as a NEW
13
+ // version-named file, then re-point the cicy-code link (re-copy on Windows).
14
+
15
+ const fs = require("fs");
16
+ const os = require("os");
17
+ const path = require("path");
18
+ const { execFile } = require("child_process");
19
+
20
+ const IS_WIN = process.platform === "win32";
21
+ const REGISTRY = process.env.CICY_NPM_REGISTRY || "https://registry.npmmirror.com";
22
+ const LOCAL_BIN = path.join(os.homedir(), ".local", "bin");
23
+
24
+ function plat() {
25
+ const osStr = IS_WIN ? "windows" : process.platform === "darwin" ? "darwin" : "linux";
26
+ const arch = process.arch === "arm64" ? "arm64" : "x64";
27
+ return `${osStr}-${arch}`;
28
+ }
29
+ const PKG = () => `cicy-code-${plat()}`;
30
+ const BIN = IS_WIN ? "cicy-code.exe" : "cicy-code";
31
+ const LINK = path.join(LOCAL_BIN, IS_WIN ? "cicy-code.exe" : "cicy-code");
32
+ const versioned = (ver) => path.join(LOCAL_BIN, `cicy-code-${ver}-${plat()}${IS_WIN ? ".exe" : ""}`);
33
+
34
+ function npmExec(args, timeout = 600000) {
35
+ return new Promise((resolve, reject) => {
36
+ execFile("npm", args, { windowsHide: true, timeout, shell: IS_WIN }, (err, stdout, stderr) =>
37
+ err ? reject(new Error(String(stderr || err.message).slice(0, 300))) : resolve(String(stdout)));
38
+ });
39
+ }
40
+
41
+ // Latest published version of the per-platform subpackage.
42
+ async function latestVersion() {
43
+ return (await npmExec(["view", PKG(), "version", `--registry=${REGISTRY}`], 30000)).trim();
44
+ }
45
+
46
+ // Point ~/.local/bin/cicy-code at a version-named binary: symlink on POSIX, a
47
+ // plain copy on Windows.
48
+ function linkTo(verBinPath) {
49
+ fs.mkdirSync(LOCAL_BIN, { recursive: true });
50
+ try { fs.rmSync(LINK, { force: true }); } catch {}
51
+ if (IS_WIN) {
52
+ fs.copyFileSync(verBinPath, LINK);
53
+ } else {
54
+ fs.symlinkSync(verBinPath, LINK);
55
+ }
56
+ return LINK;
57
+ }
58
+
59
+ function placeBinary(srcBin, ver) {
60
+ if (!fs.existsSync(srcBin)) throw new Error(`source binary missing: ${srcBin}`);
61
+ fs.mkdirSync(LOCAL_BIN, { recursive: true });
62
+ const dst = versioned(ver);
63
+ fs.copyFileSync(srcBin, dst);
64
+ if (!IS_WIN) fs.chmodSync(dst, 0o755);
65
+ linkTo(dst);
66
+ return { exe: LINK, target: dst, version: ver };
67
+ }
68
+
69
+ // The bundled per-platform subpackage shipped inside cicy-desktop (zero network).
70
+ function bundledPkgDir() {
71
+ const candidates = [
72
+ path.join(__dirname, "..", "..", "node_modules", PKG()), // npm install layout
73
+ path.join(process.resourcesPath || "", "runtime-pkgs", PKG()), // packaged (NSIS/dmg) layout
74
+ ];
75
+ for (const p of candidates) {
76
+ try { if (fs.existsSync(path.join(p, "package.json"))) return p; } catch {}
77
+ }
78
+ return null;
79
+ }
80
+
81
+ // Install the binary from the bundled subpackage. null when not bundled.
82
+ function fromBundle() {
83
+ const dir = bundledPkgDir();
84
+ if (!dir) return null;
85
+ let ver;
86
+ try { ver = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")).version; } catch { return null; }
87
+ const src = path.join(dir, BIN);
88
+ if (!fs.existsSync(src)) return null;
89
+ if (fs.existsSync(versioned(ver))) { linkTo(versioned(ver)); return { exe: LINK, version: ver }; }
90
+ return placeBinary(src, ver);
91
+ }
92
+
93
+ // Download <pkg>@<ver> via `npm pack` and install it into ~/.local/bin. npm is
94
+ // ONLY the download channel — we copy the binary out and run it from ~/.local/bin.
95
+ async function fetchToLocalBin(ver, { emit } = {}) {
96
+ const e = emit || (() => {});
97
+ if (fs.existsSync(versioned(ver))) { linkTo(versioned(ver)); return { exe: LINK, version: ver }; }
98
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "cicy-cc-"));
99
+ try {
100
+ e({ phase: "download", status: "running", message: `下载 cicy-code ${ver}…` });
101
+ const out = await npmExec(["pack", `${PKG()}@${ver}`, `--registry=${REGISTRY}`, "--pack-destination", tmp]);
102
+ const tgz = path.join(tmp, out.trim().split("\n").pop().trim());
103
+ await new Promise((resolve, reject) =>
104
+ execFile("tar", ["-xzf", tgz, "-C", tmp], { windowsHide: true, timeout: 120000 }, (err) => (err ? reject(err) : resolve())));
105
+ const res = placeBinary(path.join(tmp, "package", BIN), ver);
106
+ e({ phase: "download", status: "done", message: `cicy-code ${ver} 就绪` });
107
+ return res;
108
+ } finally {
109
+ fs.rmSync(tmp, { recursive: true, force: true });
110
+ }
111
+ }
112
+
113
+ // ~/.local/bin/cicy-code, if it exists.
114
+ function currentLink() {
115
+ return fs.existsSync(LINK) ? LINK : null;
116
+ }
117
+
118
+ // Ensure ~/.local/bin/cicy-code exists and points at a usable binary.
119
+ // - already linked → reuse (unless force)
120
+ // - else bundled subpackage (zero network, the "pre-installed" path)
121
+ // - else download latest (or a pinned version) via npm
122
+ async function ensure({ version = null, force = false, emit = null } = {}) {
123
+ if (!force && currentLink()) return { exe: LINK };
124
+ const pin = version && version !== "latest" ? version : null;
125
+ if (!force && !pin) {
126
+ const b = fromBundle();
127
+ if (b) return b;
128
+ }
129
+ const ver = pin || (await latestVersion());
130
+ return fetchToLocalBin(ver, { emit });
131
+ }
132
+
133
+ module.exports = { LOCAL_BIN, LINK, plat, versioned, latestVersion, fromBundle, fetchToLocalBin, currentLink, ensure };
@@ -140,7 +140,9 @@ async function start({ port = 8008, logPath = null, emit, version = null } = {})
140
140
  PORT: String(port),
141
141
  CICY_CODE_PORT: String(port),
142
142
  };
143
- const child = spawn(exe, [], { stdio, detached: true, windowsHide: true, env });
143
+ // --helper=1: on Windows, boot as the single headless cicy 团队助手 on w-1001
144
+ // (开机即团队助手). The flag is a no-op on cicy-code builds that don't support it.
145
+ const child = spawn(exe, ["--helper=1"], { stdio, detached: true, windowsHide: true, env });
144
146
  child.unref();
145
147
  try { fs.writeFileSync(PID_FILE, String(child.pid)); } catch {}
146
148
  console.log(`[native-sidecar] spawned ${exe} pid=${child.pid} port=${port} log=${logPath || "(none)"}`);
@@ -734,24 +734,145 @@ body {
734
734
  word-break: break-word;
735
735
  }
736
736
 
737
- /* live op progress on the local team card (更新 download %/phase stream) */
738
- .bcard__prog {
739
- display: flex; flex-direction: column; gap: 4px;
740
- margin-top: 6px; font-size: 12px; line-height: 1.35;
741
- color: var(--text-dim, #9da7b3);
742
- }
743
- .bcard__prog[data-status="error"] .bcard__progmsg { color: #f87171; }
744
- .bcard__prog[data-status="done"] .bcard__progmsg { color: #4ade80; }
745
- .bcard__progbar {
737
+ /* Toast: floating op feedback (更新/启动/重启 progress + result), bottom-right,
738
+ stacked. Replaces the old in-card progress line — feedback floats over the UI. */
739
+ .toast-host {
740
+ position: fixed; right: 16px; bottom: 16px; z-index: 9999;
741
+ display: flex; flex-direction: column; gap: 8px;
742
+ max-width: min(360px, calc(100vw - 32px)); pointer-events: none;
743
+ }
744
+ .toast {
745
+ pointer-events: auto; position: relative;
746
+ display: flex; flex-direction: column; gap: 6px;
747
+ padding: 10px 30px 10px 12px;
748
+ font-size: 12.5px; line-height: 1.4; color: var(--text, #e6eaf0);
749
+ background: rgba(22, 26, 33, 0.96);
750
+ border: 1px solid rgba(125, 135, 150, 0.22);
751
+ border-left: 3px solid var(--accent, #3b82f6);
752
+ border-radius: 10px;
753
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.42);
754
+ backdrop-filter: blur(8px);
755
+ animation: toast-in 0.18s ease;
756
+ }
757
+ @keyframes toast-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
758
+ .toast[data-status="error"] { border-left-color: #f87171; }
759
+ .toast[data-status="error"] .toast__msg { color: #f87171; }
760
+ .toast[data-status="done"] { border-left-color: #4ade80; }
761
+ .toast[data-status="done"] .toast__msg { color: #4ade80; }
762
+ .toast__msg { word-break: break-word; }
763
+ .toast__x {
764
+ position: absolute; top: 6px; right: 8px;
765
+ background: none; border: none; cursor: pointer; padding: 0;
766
+ font-size: 15px; line-height: 1; color: var(--text-dim, #9da7b3);
767
+ }
768
+ .toast__x:hover { color: var(--text, #e6eaf0); }
769
+ .toast__bar {
746
770
  display: block; height: 4px; border-radius: 2px;
747
771
  background: rgba(125, 135, 150, 0.25); overflow: hidden;
748
772
  }
749
- .bcard__progbar > span {
773
+ .toast__bar > span {
750
774
  display: block; height: 100%; border-radius: 2px;
751
775
  background: var(--accent, #3b82f6);
752
776
  transition: width 0.25s ease;
753
777
  }
754
778
 
779
+ /* ── 更新 drawer: bottom sheet with live log + 阶段 + 重试 ───────────────── */
780
+ .drawer-scrim {
781
+ position: fixed; inset: 0; z-index: 10000;
782
+ display: flex; align-items: flex-end; justify-content: center;
783
+ background: rgba(6, 8, 12, 0.5); backdrop-filter: blur(2px);
784
+ animation: drawer-fade 0.18s ease;
785
+ }
786
+ @keyframes drawer-fade { from { opacity: 0; } to { opacity: 1; } }
787
+ .drawer {
788
+ width: min(560px, calc(100vw - 24px)); max-height: 76vh;
789
+ display: flex; flex-direction: column;
790
+ margin-bottom: 12px;
791
+ background: rgba(20, 24, 31, 0.98);
792
+ border: 1px solid rgba(125, 135, 150, 0.2);
793
+ border-radius: 16px 16px 12px 12px;
794
+ box-shadow: 0 -10px 44px rgba(0, 0, 0, 0.5);
795
+ overflow: hidden;
796
+ animation: drawer-up 0.24s cubic-bezier(0.22, 1, 0.36, 1);
797
+ }
798
+ @keyframes drawer-up { from { opacity: 0; transform: translateY(28px); } to { opacity: 1; transform: none; } }
799
+ .drawer__head {
800
+ display: flex; align-items: center; justify-content: space-between;
801
+ padding: 14px 14px 12px 16px;
802
+ border-bottom: 1px solid rgba(125, 135, 150, 0.14);
803
+ }
804
+ .drawer__title { display: flex; align-items: center; gap: 11px; }
805
+ .drawer__spark {
806
+ display: inline-flex; align-items: center; justify-content: center;
807
+ width: 26px; height: 26px; border-radius: 8px; font-size: 14px; font-weight: 700;
808
+ background: rgba(91, 141, 247, 0.16); color: var(--brand, #5b8df7);
809
+ }
810
+ .drawer__spark--done { background: rgba(74, 222, 128, 0.16); color: #4ade80; }
811
+ .drawer__spark--error { background: rgba(248, 113, 113, 0.16); color: #f87171; }
812
+ .drawer__h { font-size: 13.5px; font-weight: 650; color: var(--text, #e6eaf0); }
813
+ .drawer__sub { font-size: 11.5px; color: var(--text-dim, #9da7b3); margin-top: 1px; }
814
+ .drawer__x {
815
+ background: none; border: none; cursor: pointer; padding: 2px 6px;
816
+ font-size: 19px; line-height: 1; color: var(--text-dim, #9da7b3); border-radius: 6px;
817
+ }
818
+ .drawer__x:hover:not(:disabled) { color: var(--text, #e6eaf0); background: rgba(125, 135, 150, 0.12); }
819
+ .drawer__x:disabled { opacity: 0.35; cursor: default; }
820
+
821
+ .drawer__steps { display: flex; align-items: center; gap: 0; padding: 14px 18px 4px; }
822
+ .drawer__step { display: flex; align-items: center; gap: 7px; color: var(--text-dim, #9da7b3); font-size: 12px; }
823
+ .drawer__step-dot {
824
+ display: inline-flex; align-items: center; justify-content: center;
825
+ width: 20px; height: 20px; border-radius: 50%; font-size: 11px; font-weight: 700;
826
+ border: 1.5px solid rgba(125, 135, 150, 0.35); color: var(--text-dim, #9da7b3);
827
+ background: transparent; flex: none;
828
+ }
829
+ .drawer__step-bar { width: 30px; height: 1.5px; background: rgba(125, 135, 150, 0.25); margin: 0 8px; }
830
+ .drawer__step.is-active .drawer__step-dot { border-color: var(--brand, #5b8df7); color: var(--brand, #5b8df7); box-shadow: 0 0 0 3px rgba(91, 141, 247, 0.18); }
831
+ .drawer__step.is-active .drawer__step-label { color: var(--text, #e6eaf0); }
832
+ .drawer__step.is-done .drawer__step-dot { border-color: #4ade80; color: #06210f; background: #4ade80; }
833
+ .drawer__step.is-done .drawer__step-bar { background: rgba(74, 222, 128, 0.5); }
834
+ .drawer__step.is-error .drawer__step-dot { border-color: #f87171; color: #f87171; }
835
+
836
+ .drawer__log {
837
+ flex: 1; min-height: 96px; overflow-y: auto;
838
+ margin: 10px 14px; padding: 10px 12px;
839
+ background: rgba(10, 12, 16, 0.7); border: 1px solid rgba(125, 135, 150, 0.12); border-radius: 10px;
840
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11.5px; line-height: 1.7;
841
+ }
842
+ .drawer__log-empty { color: var(--text-dim, #9da7b3); }
843
+ .drawer__line { display: flex; align-items: baseline; gap: 8px; padding: 1px 0; }
844
+ .drawer__t { color: #6b7686; flex: none; font-variant-numeric: tabular-nums; }
845
+ .drawer__badge {
846
+ flex: none; padding: 0 6px; border-radius: 4px; font-size: 10px; font-weight: 600;
847
+ background: rgba(91, 141, 247, 0.16); color: #8fb0f5;
848
+ }
849
+ .drawer__badge--swap { background: rgba(245, 158, 11, 0.16); color: #f5b342; }
850
+ .drawer__badge--done { background: rgba(74, 222, 128, 0.16); color: #6ee79b; }
851
+ .drawer__linemsg { color: var(--text, #e6eaf0); word-break: break-word; }
852
+ .drawer__line[data-status="error"] .drawer__linemsg { color: #f87171; }
853
+ .drawer__line[data-status="done"] .drawer__linemsg { color: #6ee79b; }
854
+ .drawer__line[data-status="skip"] .drawer__linemsg { color: var(--text-dim, #9da7b3); }
855
+
856
+ .drawer__hint {
857
+ margin: 0 14px 8px; padding: 8px 12px;
858
+ background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.28); border-radius: 8px;
859
+ color: #f5b342; font-size: 11.5px; line-height: 1.5;
860
+ }
861
+ .drawer__foot {
862
+ display: flex; align-items: center; gap: 8px;
863
+ padding: 12px 14px; border-top: 1px solid rgba(125, 135, 150, 0.14);
864
+ }
865
+ .drawer__foot-status { font-size: 12.5px; color: var(--text-dim, #9da7b3); margin-right: auto; }
866
+ .drawer__foot-status.is-error { color: #f87171; }
867
+ .drawer__foot-status.is-done { color: #4ade80; }
868
+ .drawer__btn {
869
+ padding: 7px 16px; border-radius: 8px; cursor: pointer; font-size: 12.5px; font-weight: 550;
870
+ background: rgba(125, 135, 150, 0.14); border: 1px solid rgba(125, 135, 150, 0.2); color: var(--text, #e6eaf0);
871
+ }
872
+ .drawer__btn:hover { background: rgba(125, 135, 150, 0.22); }
873
+ .drawer__btn.is-accent { background: var(--brand, #5b8df7); border-color: var(--brand, #5b8df7); color: #fff; }
874
+ .drawer__btn.is-accent:hover { filter: brightness(1.08); }
875
+
755
876
  /* HTTPS 审计 CA 授权卡片 (合规 opt-in) */
756
877
  .mitm-card {
757
878
  border: 1px solid rgba(125, 135, 150, 0.22);
@@ -761,6 +882,31 @@ body {
761
882
  background: rgba(30, 36, 46, 0.4);
762
883
  }
763
884
  .mitm-card--on { border-color: rgba(74, 222, 128, 0.35); background: rgba(22, 40, 30, 0.4); }
885
+
886
+ /* 已启用 = 低调小 pill,固定在右上角,不占首页正文 */
887
+ .mitm-pill {
888
+ position: fixed; top: 64px; right: 16px; z-index: 9998;
889
+ -webkit-app-region: no-drag;
890
+ display: inline-flex; align-items: center; gap: 8px;
891
+ padding: 4px 6px 4px 11px;
892
+ border-radius: 999px; width: fit-content; max-width: calc(100vw - 32px);
893
+ background: rgba(20, 30, 24, 0.85);
894
+ border: 1px solid rgba(74, 222, 128, 0.3);
895
+ backdrop-filter: blur(8px);
896
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.3);
897
+ font-size: 12px; line-height: 1;
898
+ }
899
+ .mitm-pill__dot {
900
+ width: 7px; height: 7px; border-radius: 50%; flex: none;
901
+ background: #4ade80; box-shadow: 0 0 0 3px rgba(74, 222, 128, 0.16);
902
+ }
903
+ .mitm-pill__dot[data-busy="1"] { background: #9da7b3; box-shadow: 0 0 0 3px rgba(157, 167, 179, 0.16); }
904
+ .mitm-pill__text { color: var(--text-dim, #9da7b3); white-space: nowrap; }
905
+ .mitm-pill__off {
906
+ background: none; border: none; cursor: pointer;
907
+ color: #6b7686; font-size: 11.5px; padding: 3px 7px; border-radius: 999px;
908
+ }
909
+ .mitm-pill__off:hover { color: #f87171; background: rgba(248, 113, 113, 0.1); }
764
910
  .mitm-card__head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
765
911
  .mitm-card__dot { width: 8px; height: 8px; border-radius: 50%; background: #9da7b3; flex: 0 0 auto; }
766
912
  .mitm-card__dot[data-state="on"] { background: #4ade80; box-shadow: 0 0 6px rgba(74, 222, 128, 0.6); }