cicy-desktop 2.1.69 → 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 (61) 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 +4 -2
  12. package/src/sidecar/runtime.js +7 -3
  13. package/workers/render/src/App.css +156 -10
  14. package/workers/render/src/App.jsx +254 -39
  15. package/.env.dev +0 -7
  16. package/src/backends/homepage-react/assets/index-B8gGhz8B.js +0 -365
  17. package/workers/render.bak.20260528-2338/DESIGN_v2.md +0 -254
  18. package/workers/render.bak.20260528-2338/index.html +0 -12
  19. package/workers/render.bak.20260528-2338/package-lock.json +0 -827
  20. package/workers/render.bak.20260528-2338/package.json +0 -19
  21. package/workers/render.bak.20260528-2338/public/_headers +0 -5
  22. package/workers/render.bak.20260528-2338/public/manifest.json +0 -6
  23. package/workers/render.bak.20260528-2338/src/App.css +0 -224
  24. package/workers/render.bak.20260528-2338/src/App.jsx +0 -1028
  25. package/workers/render.bak.20260528-2338/src/api.js +0 -285
  26. package/workers/render.bak.20260528-2338/src/cicycode-ops.js +0 -222
  27. package/workers/render.bak.20260528-2338/src/components/BackendCard.css +0 -299
  28. package/workers/render.bak.20260528-2338/src/components/BackendCard.jsx +0 -133
  29. package/workers/render.bak.20260528-2338/src/components/BackendModal.css +0 -161
  30. package/workers/render.bak.20260528-2338/src/components/BackendModal.jsx +0 -199
  31. package/workers/render.bak.20260528-2338/src/components/Button.css +0 -72
  32. package/workers/render.bak.20260528-2338/src/components/Button.jsx +0 -37
  33. package/workers/render.bak.20260528-2338/src/components/Card.css +0 -42
  34. package/workers/render.bak.20260528-2338/src/components/Card.jsx +0 -21
  35. package/workers/render.bak.20260528-2338/src/components/Icon.jsx +0 -30
  36. package/workers/render.bak.20260528-2338/src/components/Menu.css +0 -55
  37. package/workers/render.bak.20260528-2338/src/components/Menu.jsx +0 -91
  38. package/workers/render.bak.20260528-2338/src/components/SidecarBanner.css +0 -79
  39. package/workers/render.bak.20260528-2338/src/components/SidecarBanner.jsx +0 -84
  40. package/workers/render.bak.20260528-2338/src/components/StatusChip.css +0 -19
  41. package/workers/render.bak.20260528-2338/src/components/StatusChip.jsx +0 -31
  42. package/workers/render.bak.20260528-2338/src/components/Toast.css +0 -31
  43. package/workers/render.bak.20260528-2338/src/components/Toast.jsx +0 -23
  44. package/workers/render.bak.20260528-2338/src/components/WslSetupBanner.css +0 -464
  45. package/workers/render.bak.20260528-2338/src/components/WslSetupBanner.jsx +0 -716
  46. package/workers/render.bak.20260528-2338/src/dockerInstaller.js +0 -0
  47. package/workers/render.bak.20260528-2338/src/i18n/en.json +0 -116
  48. package/workers/render.bak.20260528-2338/src/i18n/fr.json +0 -116
  49. package/workers/render.bak.20260528-2338/src/i18n/index.js +0 -69
  50. package/workers/render.bak.20260528-2338/src/i18n/ja.json +0 -116
  51. package/workers/render.bak.20260528-2338/src/i18n/zh-CN.json +0 -121
  52. package/workers/render.bak.20260528-2338/src/main.js +0 -475
  53. package/workers/render.bak.20260528-2338/src/main.jsx +0 -18
  54. package/workers/render.bak.20260528-2338/src/style.css +0 -275
  55. package/workers/render.bak.20260528-2338/src/styles/base.css +0 -98
  56. package/workers/render.bak.20260528-2338/src/styles/tokens.css +0 -90
  57. package/workers/render.bak.20260528-2338/src/tos.js +0 -72
  58. package/workers/render.bak.20260528-2338/src/worker.js +0 -40
  59. package/workers/render.bak.20260528-2338/src/wslInstaller.js +0 -1563
  60. package/workers/render.bak.20260528-2338/vite.config.js +0 -36
  61. 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 };
@@ -28,7 +28,7 @@ const docker = require("./docker"); // ensureDownloaded/withRetry/waitUntil/prob
28
28
  // dev/联调 only — read LAZILY (not at require time) so tests/tools can set the
29
29
  // env var after loading the module.
30
30
  const devExeUrl = () => process.env.CICY_CODE_EXE_URL || "";
31
- const EXE_PKG = process.env.CICY_CODE_EXE_PKG || "cicy-code-win32-x64";
31
+ const EXE_PKG = process.env.CICY_CODE_EXE_PKG || "cicy-code-windows-x64"; // npm spam filter 403s 'win32' names
32
32
  const REGISTRY = process.env.CICY_NPM_REGISTRY || "https://registry.npmmirror.com";
33
33
  const BIN_DIR = path.join(os.homedir(), "cicy-ai", "bin");
34
34
  // npm prefix dir owned by the sidecar (isolated from the user's global npm).
@@ -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)"}`);
@@ -30,8 +30,13 @@ const VERSIONS_JSON = path.join(RUNTIME_DIR, "versions.json");
30
30
  const REGISTRY = process.env.CICY_NPM_REGISTRY || "https://registry.npmmirror.com";
31
31
  const IS_WIN = process.platform === "win32";
32
32
 
33
+ // npm subpackage platform suffix. NOTE: Windows is "windows" not "win32" —
34
+ // npm's spam filter 403s NEW package names containing 'win32', so EVERY cicy
35
+ // subpackage (code/mihomo/msys2) is published as *-windows-*. This must match
36
+ // the published names exactly or both the bundled-dir lookup AND the npm-pull
37
+ // fallback fail (a 404 that stranded first start on Windows).
33
38
  function plat() {
34
- const osStr = IS_WIN ? "win32" : process.platform === "darwin" ? "darwin" : "linux";
39
+ const osStr = IS_WIN ? "windows" : process.platform === "darwin" ? "darwin" : "linux";
35
40
  const archStr = process.arch === "arm64" ? "arm64" : "x64";
36
41
  return `${osStr}-${archStr}`;
37
42
  }
@@ -46,8 +51,7 @@ const COMPONENTS = {
46
51
  },
47
52
  "mihomo": {
48
53
  kind: "bin",
49
- // npm spam filter 403s new names containing 'win32' → windows-* naming
50
- pkg: () => `cicy-mihomo-${plat().replace("win32", "windows")}`,
54
+ pkg: () => `cicy-mihomo-${plat()}`,
51
55
  bin: () => (IS_WIN ? "mihomo.exe" : "mihomo"),
52
56
  },
53
57
  "msys2": {
@@ -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); }