cicy-desktop 2.1.184 → 2.1.186

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.
@@ -40,12 +40,21 @@ jobs:
40
40
  # linux-arm64 用户的包里塞的是 x64 二进制。强制把两个 linux 架构都装进 node_modules,
41
41
  # electron-builder 两架构都打包,localbin 按用户机器选。必须在所有 npm install 之后。
42
42
  - name: Bundle BOTH linux arches of cicy-code/mihomo (runner is single-arch)
43
+ shell: bash
43
44
  run: |
44
- pkgs=$(node -e "const o=require('./package.json').optionalDependencies||{}; const w=['cicy-code-linux-x64','cicy-code-linux-arm64','cicy-mihomo-linux-x64','cicy-mihomo-linux-arm64']; console.log(w.filter(p=>o[p]).map(p=>p+'@'+o[p]).join(' '))")
45
- echo "force-installing both arches: $pkgs"
46
- npm install --no-save --no-audit --force $pkgs
45
+ set -e
46
+ # npm pack(不看 os/cpu/lockfile)+ 解压,绕开 `npm install --force` 在已有 lockfile 上的 no-op。
47
+ for pkg in cicy-code-linux-x64 cicy-code-linux-arm64 cicy-mihomo-linux-x64 cicy-mihomo-linux-arm64; do
48
+ ver=$(node -e "const o=require('./package.json').optionalDependencies||{}; process.stdout.write(o['$pkg']||'')")
49
+ [ -z "$ver" ] && { echo "skip $pkg"; continue; }
50
+ bin=cicy-code; case "$pkg" in cicy-mihomo*) bin=mihomo;; esac
51
+ if [ -f "node_modules/$pkg/$bin" ]; then echo "$pkg present"; continue; fi
52
+ echo "npm pack $pkg@$ver → node_modules/$pkg"
53
+ tmp=$(mktemp -d); ( cd "$tmp" && npm pack "$pkg@$ver" --silent ); tar -xzf "$tmp"/*.tgz -C "$tmp"
54
+ rm -rf "node_modules/$pkg"; mkdir -p "node_modules/$pkg"; cp -R "$tmp/package/." "node_modules/$pkg/"; rm -rf "$tmp"
55
+ done
47
56
  ls node_modules | grep -E 'cicy-(code|mihomo)-linux' || true
48
- for a in x64 arm64; do test -f "node_modules/cicy-code-linux-$a/cicy-code" || { echo "::error::cicy-code-linux-$a binary missing"; exit 1; }; done
57
+ for a in x64 arm64; do test -f "node_modules/cicy-code-linux-$a/cicy-code" || { echo "::error::cicy-code-linux-$a missing"; exit 1; }; done
49
58
 
50
59
  # NOTE: the homepage SPA is rebuilt automatically by the prebuild:linux npm
51
60
  # hook (scripts/build-homepage.cjs) right before `npm run build:linux` below,
@@ -44,13 +44,22 @@ jobs:
44
44
  # 装进 node_modules(--force 绕过 EBADPLATFORM),让 electron-builder 两架构都打包,
45
45
  # localbin 按用户机器选对应那份。必须在所有 npm install 之后(否则被剪枝)。
46
46
  - name: Bundle BOTH macOS arches of cicy-code/mihomo (runner is single-arch)
47
+ shell: bash
47
48
  run: |
48
- pkgs=$(node -e "const o=require('./package.json').optionalDependencies||{}; const w=['cicy-code-darwin-x64','cicy-code-darwin-arm64','cicy-mihomo-darwin-x64','cicy-mihomo-darwin-arm64']; console.log(w.filter(p=>o[p]).map(p=>p+'@'+o[p]).join(' '))")
49
- echo "force-installing both arches: $pkgs"
50
- npm install --no-save --no-audit --force $pkgs
51
- echo "--- bundled darwin subpackages ---"
52
- ls node_modules | grep -E 'cicy-(code|mihomo)-darwin' || true
53
- for a in x64 arm64; do test -f "node_modules/cicy-code-darwin-$a/cicy-code" || { echo "::error::cicy-code-darwin-$a binary missing after force-install"; exit 1; }; done
49
+ set -e
50
+ # `npm install --force` 在已有 lockfile 的项目里会判 "up to date" 跳过非本机架构
51
+ # → 改用 `npm pack`(纯按名下 tarball,不看 os/cpu/lockfile)+ 解压进 node_modules。
52
+ for pkg in cicy-code-darwin-x64 cicy-code-darwin-arm64 cicy-mihomo-darwin-x64 cicy-mihomo-darwin-arm64; do
53
+ ver=$(node -e "const o=require('./package.json').optionalDependencies||{}; process.stdout.write(o['$pkg']||'')")
54
+ [ -z "$ver" ] && { echo "skip $pkg (not pinned)"; continue; }
55
+ bin=cicy-code; case "$pkg" in cicy-mihomo*) bin=mihomo;; esac
56
+ if [ -f "node_modules/$pkg/$bin" ]; then echo "$pkg present (native arch)"; continue; fi
57
+ echo "npm pack $pkg@$ver → node_modules/$pkg"
58
+ tmp=$(mktemp -d); ( cd "$tmp" && npm pack "$pkg@$ver" --silent ); tar -xzf "$tmp"/*.tgz -C "$tmp"
59
+ rm -rf "node_modules/$pkg"; mkdir -p "node_modules/$pkg"; cp -R "$tmp/package/." "node_modules/$pkg/"; rm -rf "$tmp"
60
+ done
61
+ echo "--- bundled darwin subpackages ---"; ls node_modules | grep -E 'cicy-(code|mihomo)-darwin' || true
62
+ for a in x64 arm64; do test -f "node_modules/cicy-code-darwin-$a/cicy-code" || { echo "::error::cicy-code-darwin-$a binary missing"; exit 1; }; done
54
63
 
55
64
  # NOTE: the homepage SPA is rebuilt automatically by the prebuild:mac npm
56
65
  # hook (scripts/build-homepage.cjs) right before `npm run build:mac` below,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cicy-desktop",
3
- "version": "2.1.184",
3
+ "version": "2.1.186",
4
4
  "description": "CiCy - AI-powered operating system browser",
5
5
  "main": "src/main.js",
6
6
  "bin": {
package/src/main.js CHANGED
@@ -881,32 +881,37 @@ electronApp.whenReady().then(async () => {
881
881
  // colima VM 在 16G mac 上把内存压垮被 jetsam SIGKILL。Windows 仍走 docker(WSL :8009,
882
882
  // 由 sidecar-ipc 管),不在这里起 native。
883
883
  if (process.platform !== "win32") {
884
- // Start bundled cicy-code daemon as a sidecar. Reuses an existing instance on
885
- // :8008 if one is already running (probeExisting → adopt, never double-spawn).
884
+ const sidecarPort = Number(process.env.CICY_CODE_PORT || 8008);
885
+ const lt = require("./backends/local-teams");
886
+
887
+ // 占位卡(主人: "先有一个 :8008 占位"): 开机**立刻无条件**注册本地团队,homepage 永远有
888
+ // 这张卡 —— :8008 没起来时显示「未运行」+ 启动/安装入口(不会再空首页);起来后下面的
889
+ // probe 循环 upsert 一次,状态翻 running + 触发 gateway key/teamId 注入。addTeam 幂等。
890
+ lt.addTeam({ base_url: `http://127.0.0.1:${sidecarPort}`, name: i18n.t("localTeams.defaultName") })
891
+ .then((r) => { if (r && r.id) log.info(`[Sidecar] local team placeholder registered (${r.id})`); })
892
+ .catch((e) => log.warn(`[Sidecar] placeholder register failed: ${e.message}`));
893
+
894
+ // 起 native cicy-code(npx cicy-code,见 cicy-code.js)。已在跑就 adopt,不双开。
886
895
  cicyCodeSidecar
887
896
  .start({ logPath: path.join(os.homedir(), "logs", "cicy-code-sidecar.log") })
888
897
  .then((c) => { if (c) log.info(`[Sidecar] cicy-code spawned pid=${c.pid}`); })
889
898
  .catch((e) => log.warn(`[Sidecar] cicy-code start failed: ${e.message}`));
890
899
  startSidecarWatchdog();
891
900
 
892
- // Auto-register the local sidecar as 本地团队 once :8008 answers (主人: 一装好就要
893
- // 有占位卡)。addTeam upserts by host:port + auto-fills api_token, so re-runs are
894
- // no-ops. A fresh boot may seed/npm-pull the binary first, so probe up to ~90s.
901
+ // :8008 起来后 upsert 一次:刷新卡状态 + addTeam 去云端拿这个本地 team gateway
902
+ // key + teamId 注入 global.json(主人: 启动之后先去拿 llm api_key team id)。
895
903
  (async () => {
896
- const sidecarPort = Number(process.env.CICY_CODE_PORT || 8008);
897
- const lt = require("./backends/local-teams");
898
- for (let i = 0; i < 30; i++) {
904
+ for (let i = 0; i < 40; i++) {
899
905
  try {
900
906
  if (await cicyCodeSidecar.probeExisting(sidecarPort)) {
901
907
  const r = await lt.addTeam({ base_url: `http://127.0.0.1:${sidecarPort}`, name: i18n.t("localTeams.defaultName") });
902
- if (r && r.ok) log.info(`[Sidecar] local team ${r.upserted ? "refreshed" : "registered"} (${r.id})`);
903
- else log.warn(`[Sidecar] local team auto-register failed: ${r && r.error}`);
908
+ if (r && r.id) log.info(`[Sidecar] local team ready (${r.id}) — key/teamId synced`);
904
909
  return;
905
910
  }
906
- } catch (e) { log.warn(`[Sidecar] local team auto-register error: ${e.message}`); }
911
+ } catch (e) { log.warn(`[Sidecar] local team refresh error: ${e.message}`); }
907
912
  await new Promise((res) => setTimeout(res, 3000));
908
913
  }
909
- log.warn(`[Sidecar] local team auto-register gave up — :${sidecarPort} never came up`);
914
+ log.warn(`[Sidecar] :${sidecarPort} never came up within ~2min (card stays as 未运行)`);
910
915
  })();
911
916
  }
912
917
 
@@ -18,7 +18,56 @@ const os = require("os");
18
18
  const http = require("http");
19
19
  const net = require("net");
20
20
  const path = require("path");
21
- const { spawn, execFileSync } = require("child_process");
21
+ const { spawn, execFileSync, execFile } = require("child_process");
22
+
23
+ // ── Node runtime bootstrap ───────────────────────────────────────────────────
24
+ // 主人(2026-06): native cicy-code 走 `npx cicy-code`,需要一个**可用**的 Node。但用户
25
+ // 机器可能没 node,或 node 太老(实测 josephs 是 node v13/npx6,老 npx 跑不动)。所以:
26
+ // 系统有 node≥20 就用;没有/太老 → 下载 Node 24 到 ~/cicy-ai/runtime/node(免 sudo,
27
+ // 用户自己拥有);下载也失败 → 提示用户去 nodejs.org 自己装。
28
+ const NODE_VER = process.env.CICY_NODE_VERSION || "v24.18.0";
29
+ const NODE_HOME = path.join(os.homedir(), "cicy-ai", "runtime", "node");
30
+ const NODE_SEARCH = ["/usr/local/bin", "/opt/homebrew/bin", path.join(os.homedir(), ".local", "bin"), path.join(NODE_HOME, "bin")];
31
+ function nodeMajor(bin) {
32
+ try { const m = String(execFileSync(bin, ["-v"], { encoding: "utf8", timeout: 5000 })).match(/v(\d+)\./); return m ? Number(m[1]) : 0; } catch { return 0; }
33
+ }
34
+ function findUsableNode() {
35
+ for (const d of NODE_SEARCH) {
36
+ const node = path.join(d, "node"), npx = path.join(d, "npx");
37
+ try { if (fs.existsSync(node) && fs.existsSync(npx) && nodeMajor(node) >= 20) return d; } catch {}
38
+ }
39
+ return null;
40
+ }
41
+ const pexec = (cmd, args, timeout) => new Promise((res, rej) => execFile(cmd, args, { timeout, windowsHide: true }, (e) => (e ? rej(e) : res())));
42
+ // Returns a bin dir with node+npx (node≥20), or null. emit streams progress to a drawer.
43
+ async function ensureNode({ emit } = {}) {
44
+ const e = emit || (() => {});
45
+ let dir = findUsableNode();
46
+ if (dir) return dir;
47
+ const arch = process.arch === "arm64" ? "arm64" : "x64";
48
+ const fname = `node-${NODE_VER}-darwin-${arch}.tar.gz`;
49
+ const urls = [
50
+ `https://cdn.npmmirror.com/binaries/node/${NODE_VER}/${fname}`, // CN-fast
51
+ `https://nodejs.org/dist/${NODE_VER}/${fname}`, // 官方兜底
52
+ ];
53
+ e({ phase: "node", status: "running", message: `未检测到可用 Node,正在安装 Node ${NODE_VER}(约 50MB)…` });
54
+ fs.mkdirSync(NODE_HOME, { recursive: true });
55
+ const tmp = path.join(os.tmpdir(), `cicy-${fname}`);
56
+ for (const url of urls) {
57
+ try {
58
+ await pexec("curl", ["-fL", "--retry", "2", "-o", tmp, url], 300000);
59
+ await pexec("tar", ["-xzf", tmp, "-C", NODE_HOME, "--strip-components", "1"], 120000);
60
+ if (fs.existsSync(path.join(NODE_HOME, "bin", "node")) && fs.existsSync(path.join(NODE_HOME, "bin", "npx"))) {
61
+ try { fs.unlinkSync(tmp); } catch {}
62
+ e({ phase: "node", status: "done", message: `Node ${NODE_VER} 安装完成` });
63
+ return path.join(NODE_HOME, "bin");
64
+ }
65
+ } catch (err) { console.warn(`[cicy-code-sidecar] node install failed (${url}): ${err.message}`); }
66
+ }
67
+ try { fs.unlinkSync(tmp); } catch {}
68
+ e({ phase: "node", status: "error", message: "Node 自动安装失败 —— 请打开 https://nodejs.org 下载安装 Node(LTS),装好后点「重试」" });
69
+ return null;
70
+ }
22
71
 
23
72
  const DEFAULT_PORT = Number(process.env.CICY_CODE_PORT || 8008);
24
73
 
@@ -91,13 +140,11 @@ async function startFromRuntime({ logPath, port }) {
91
140
  return c;
92
141
  }
93
142
 
94
- async function start({ logPath, port = DEFAULT_PORT, force = false, version = null } = {}) {
143
+ async function start({ logPath, port = DEFAULT_PORT, force = false, version = null, emit = null } = {}) {
95
144
  if (child && !force) return child;
96
145
 
97
- // 主人(2026-06 方向回调): mac 资源吃不消 docker —— colima VM 16G 机上把内存压垮,
98
- // jetsam 拿 SIGKILL 乱杀进程(cicy-code 被误伤)。所以 macOS/Linux 改回 native cicy-code
99
- // (:8008,本机直接跑二进制,省掉 4–8G 的 VM)。Windows 仍走 docker(WSL :8009,由
100
- // sidecar-ipc 管),native 一律不起。
146
+ // 主人(2026-06 方向回调): mac 资源吃不消 docker(colima VM 压垮内存被 jetsam 杀)→
147
+ // macOS/Linux 改回 native cicy-code(:8008,走 `npx cicy-code`)。Windows 仍走 docker。
101
148
  if (process.platform === "win32") return null;
102
149
 
103
150
  if (!force && await probeExisting(port)) {
@@ -105,47 +152,35 @@ async function start({ logPath, port = DEFAULT_PORT, force = false, version = nu
105
152
  return null;
106
153
  }
107
154
 
108
- // UNIFIED model (主人指令): run our OWN binary at ~/.local/bin/cicy-code on
109
- // every platform never `npx cicy-code` (which reuses a stale globally-installed
110
- // copy and shadows updates). localbin seeds from the bundled subpackage on first
111
- // run (zero network) and uses npm ONLY as a download channel for updates; the run
112
- // path is always stable.
113
- const localbin = require("./localbin");
114
- let exe = localbin.currentLink();
115
- if (!exe) {
116
- try { exe = (await localbin.ensure({ version }))?.exe; }
117
- catch (e) { console.warn(`[cicy-code-sidecar] localbin ensure failed: ${e.message}`); }
118
- } else {
119
- // Present — let ensure() do a zero-network bundle upgrade if cicy-desktop
120
- // itself was updated and now ships a newer cicy-code (版本高了就更新).
121
- try { await localbin.ensure({ version }); } catch {}
122
- }
123
- if (!exe) { console.warn("[cicy-code-sidecar] no cicy-code binary available"); return null; }
124
-
125
- // 主人: 不再 seed host mihomo —— docker-only 后 mihomo 只在容器里跑(host 不用)。
126
- // 内置的 cicy-mihomo-<plat> 依赖已从 package.json 移除。
155
+ // 1) 确保有可用 Node(系统 node≥20,否则装 Node 24;再不行提示用户去 nodejs.org 自己装)。
156
+ const nodeBinDir = await ensureNode({ emit });
157
+ if (!nodeBinDir) { console.warn("[cicy-code-sidecar] no usable Node cannot npx cicy-code"); return null; }
127
158
 
159
+ // 2) `npx cicy-code` —— npm 按本机真实架构拉 cicy-code-<plat>,文件用户自己拥有(无跨架构/
160
+ // 权限坑)。用上面那个 Node 的 npx,并把它的 bin 放 PATH 首位让 npx 找到自己的 node/npm。
128
161
  let stdio = ["ignore", "ignore", "ignore"];
129
162
  if (logPath) {
130
163
  fs.mkdirSync(path.dirname(logPath), { recursive: true });
131
164
  const fd = fs.openSync(logPath, "a");
132
165
  stdio = ["ignore", fd, fd];
133
166
  }
167
+ const npxAbs = path.join(nodeBinDir, "npx");
168
+ const childPath = `${nodeBinDir}:${NODE_SEARCH.join(":")}:${process.env.PATH || ""}`;
169
+ const registry = process.env.CICY_NPM_REGISTRY || "https://registry.npmmirror.com";
134
170
  const env = {
135
171
  ...process.env,
136
172
  CICY_CODE_PORT: String(port),
137
173
  PORT: String(port),
174
+ npm_config_registry: registry,
175
+ PATH: childPath, // 给 npx 自己找 node/npm
138
176
  };
139
- // --helper removed (主人指令): Windows now runs cicy-code in normal mode (full
140
- // tmux-based multi-agent), same as mac/linux — no longer the single headless
141
- // 团队助手.
142
- const args = ["--desktop"];
143
- // 退出保活(主人):mac/linux 把 native cicy-code spawn 成 detached + unref,让它脱离
144
- // Electron 的进程组——退出 App 不带走 daemon(及其 tmux agent),下次启动 probeExisting
145
- // 探到就 adopt。Windows 不走这条(docker)。
177
+ const spec = version ? `cicy-code@${version}`
178
+ : (process.env.CICY_CODE_VERSION ? `cicy-code@${process.env.CICY_CODE_VERSION}` : "cicy-code");
179
+ emit && emit({ phase: "cicy-code", status: "running", message: "启动 cicy-code(首次会下载 + 装依赖,请稍候)…" });
180
+ // 退出保活(主人):detached + unref → 关 App 不带走 daemon(及其 tmux agent),下次 adopt。
146
181
  const detached = process.platform !== "win32";
147
- child = spawn(exe, args, { stdio, detached, windowsHide: true, env });
148
- console.log(`[cicy-code-sidecar] spawned ${exe} ${args.join(" ")} pid=${child.pid} port=${port} detached=${detached} log=${logPath || "(none)"}`);
182
+ child = spawn(npxAbs, ["-y", spec], { stdio, detached, windowsHide: true, env });
183
+ console.log(`[cicy-code-sidecar] spawned ${npxAbs} -y ${spec} pid=${child.pid} port=${port} registry=${registry} detached=${detached} log=${logPath || "(none)"}`);
149
184
  if (detached) { try { child.unref(); } catch {} }
150
185
 
151
186
  child.on("exit", (code, signal) => {