cicy-desktop 2.1.30 → 2.1.31
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.
- package/package.json +1 -1
- package/src/sidecar/cicy-code.js +48 -66
- package/src/sidecar/docker.js +113 -0
package/package.json
CHANGED
package/src/sidecar/cicy-code.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
// Discover / probe / spawn the cicy-code daemon for the Electron app.
|
|
2
2
|
//
|
|
3
|
-
// Principle (2026-
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
// OR by the cloud Team Helper agent when it finishes onboarding.
|
|
10
|
-
// 3. (no-op) if neither, return null — the homepage's Team Helper card
|
|
11
|
-
// will guide the user through install. No "bundled" fallback exists.
|
|
3
|
+
// Principle (2026-06): the daemon is run via `npx cicy-code` — a single
|
|
4
|
+
// source of truth. cicy-desktop neither bundles nor downloads a binary; the
|
|
5
|
+
// per-version binary is fetched from npm by the launcher (CN: npmmirror).
|
|
6
|
+
// 1. An already-running instance on :8008 (user-run, npx, surviving from a
|
|
7
|
+
// previous launch). probeExisting wins → reuse, never double-spawn.
|
|
8
|
+
// 2. Otherwise spawn `npx cicy-code` on mac/linux.
|
|
12
9
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
10
|
+
// This replaced the old src/sidecar/installer.js binary (~/.local/bin/
|
|
11
|
+
// cicy-code), which raced the npx-launched daemon for :8008. The in-app
|
|
12
|
+
// installer is no longer the daemon source.
|
|
13
|
+
//
|
|
14
|
+
// Windows is WSL2-hosted via src/sidecar/wsl.js; start() delegates there on
|
|
15
|
+
// win32 (npx-in-WSL migration tracked separately).
|
|
15
16
|
|
|
16
17
|
const fs = require("fs");
|
|
17
18
|
const http = require("http");
|
|
@@ -20,32 +21,6 @@ const { spawn } = require("child_process");
|
|
|
20
21
|
|
|
21
22
|
const DEFAULT_PORT = Number(process.env.CICY_CODE_PORT || 8008);
|
|
22
23
|
|
|
23
|
-
function platformDir() {
|
|
24
|
-
if (process.platform === "darwin") return "darwin";
|
|
25
|
-
if (process.platform === "linux") return "linux";
|
|
26
|
-
return null;
|
|
27
|
-
}
|
|
28
|
-
function archDir() {
|
|
29
|
-
if (process.arch === "arm64") return "arm64";
|
|
30
|
-
if (process.arch === "x64") return "x64";
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function bundledBinaryPath() {
|
|
35
|
-
const plat = platformDir();
|
|
36
|
-
const arch = archDir();
|
|
37
|
-
if (!plat || !arch) return null;
|
|
38
|
-
// Only the user-installed copy is considered. There is intentionally
|
|
39
|
-
// no <App>/Contents/Resources/cicy-code fallback — cicy-desktop no
|
|
40
|
-
// longer bundles the daemon (2026-05-29 principle).
|
|
41
|
-
try {
|
|
42
|
-
const installer = require("./installer");
|
|
43
|
-
const userBin = installer.userBinary();
|
|
44
|
-
if (userBin && fs.existsSync(userBin)) return userBin;
|
|
45
|
-
} catch {}
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
24
|
function probeExisting(port = DEFAULT_PORT, timeoutMs = 500) {
|
|
50
25
|
return new Promise(resolve => {
|
|
51
26
|
const req = http.get(
|
|
@@ -71,36 +46,25 @@ async function start({ logPath, port = DEFAULT_PORT, force = false } = {}) {
|
|
|
71
46
|
}
|
|
72
47
|
|
|
73
48
|
if (process.platform === "win32") {
|
|
74
|
-
// Windows
|
|
75
|
-
//
|
|
49
|
+
// Windows runs cicy-code in Docker Desktop (the container's entrypoint
|
|
50
|
+
// npx-installs cicy-code). The docker module owns image-load-from-R2 +
|
|
51
|
+
// container run; here we just delegate. (Replaced the old WSL path.)
|
|
76
52
|
try {
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
if (!
|
|
80
|
-
console.warn(
|
|
53
|
+
const docker = require("./docker");
|
|
54
|
+
const r = await docker.start({ port });
|
|
55
|
+
if (!r) {
|
|
56
|
+
console.warn("[cicy-code-sidecar] Docker not ready — homepage will guide install");
|
|
81
57
|
return null;
|
|
82
58
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
const r = await wsl.start({ port, force });
|
|
88
|
-
// Treat WSL-internal pid as the child token so the outer code knows we're up.
|
|
89
|
-
child = { wsl: true, pid: r.pid };
|
|
90
|
-
console.log(`[cicy-code-sidecar] started inside WSL pid=${r.pid}`);
|
|
59
|
+
child = r; // { docker:true, container, id }
|
|
60
|
+
console.log(`[cicy-code-sidecar] started in Docker container ${r.container} (${r.id})`);
|
|
91
61
|
return child;
|
|
92
62
|
} catch (e) {
|
|
93
|
-
console.warn(`[cicy-code-sidecar]
|
|
63
|
+
console.warn(`[cicy-code-sidecar] Docker start failed: ${e.message}`);
|
|
94
64
|
return null;
|
|
95
65
|
}
|
|
96
66
|
}
|
|
97
67
|
|
|
98
|
-
const bin = bundledBinaryPath();
|
|
99
|
-
if (!bin || !fs.existsSync(bin)) {
|
|
100
|
-
console.warn(`[cicy-code-sidecar] no daemon binary found (user has not run the in-app installer or the cloud Team Helper); homepage's Team Helper card will guide install`);
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
68
|
let stdio = ["ignore", "ignore", "ignore"];
|
|
105
69
|
if (logPath) {
|
|
106
70
|
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
@@ -108,12 +72,25 @@ async function start({ logPath, port = DEFAULT_PORT, force = false } = {}) {
|
|
|
108
72
|
stdio = ["ignore", fd, fd];
|
|
109
73
|
}
|
|
110
74
|
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
//
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
75
|
+
// Run the daemon via `npx cicy-code` — no bundled/downloaded binary. The
|
|
76
|
+
// launcher fetches the per-version binary from npm (default npmmirror for
|
|
77
|
+
// CN; override with CICY_NPM_REGISTRY) and does its own :8008 port hygiene.
|
|
78
|
+
// cicy-code reads PORT; we also set CICY_CODE_PORT and override the parent's
|
|
79
|
+
// PORT (the worker sets it to its own listen port, e.g. 8101) so it doesn't
|
|
80
|
+
// leak in and clash with the worker's HTTP server.
|
|
81
|
+
const registry = process.env.CICY_NPM_REGISTRY || "https://registry.npmmirror.com";
|
|
82
|
+
const env = {
|
|
83
|
+
...process.env,
|
|
84
|
+
CICY_CODE_PORT: String(port),
|
|
85
|
+
PORT: String(port),
|
|
86
|
+
npm_config_registry: registry,
|
|
87
|
+
};
|
|
88
|
+
const npxBin = process.platform === "win32" ? "npx.cmd" : "npx";
|
|
89
|
+
const spec = process.env.CICY_CODE_VERSION
|
|
90
|
+
? `cicy-code@${process.env.CICY_CODE_VERSION}`
|
|
91
|
+
: "cicy-code";
|
|
92
|
+
child = spawn(npxBin, ["-y", spec], { stdio, detached: false, env });
|
|
93
|
+
console.log(`[cicy-code-sidecar] spawned npx ${spec} pid=${child.pid} port=${port} registry=${registry} log=${logPath || "(none)"}`);
|
|
117
94
|
|
|
118
95
|
child.on("exit", (code, signal) => {
|
|
119
96
|
console.log(`[cicy-code-sidecar] exited code=${code} signal=${signal}`);
|
|
@@ -126,7 +103,12 @@ async function stop({ timeoutMs = 5000 } = {}) {
|
|
|
126
103
|
if (!child) return;
|
|
127
104
|
const p = child;
|
|
128
105
|
child = null;
|
|
129
|
-
//
|
|
106
|
+
// Docker-launched (win32): not a real ChildProcess — remove the container.
|
|
107
|
+
if (p && p.docker) {
|
|
108
|
+
try { await require("./docker").stop(); } catch {}
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
// WSL-launched (legacy): not a real ChildProcess, kill via wsl pkill instead.
|
|
130
112
|
if (p && p.wsl) {
|
|
131
113
|
try { await require("./wsl").stop(); } catch {}
|
|
132
114
|
return;
|
|
@@ -141,4 +123,4 @@ async function stop({ timeoutMs = 5000 } = {}) {
|
|
|
141
123
|
}
|
|
142
124
|
}
|
|
143
125
|
|
|
144
|
-
module.exports = { start, stop, probeExisting
|
|
126
|
+
module.exports = { start, stop, probeExisting };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Windows sidecar backend: run cicy-code inside a Docker container.
|
|
2
|
+
//
|
|
3
|
+
// Platform split (2026-06): mac/linux start cicy-code locally via `npx
|
|
4
|
+
// cicy-code` (see cicy-code.js); Windows runs it in Docker Desktop instead.
|
|
5
|
+
// The base-env image's entrypoint installs cicy-code from npm at container
|
|
6
|
+
// startup, so the image is version-independent. If the image isn't present
|
|
7
|
+
// locally it's loaded from R2 (CN-friendly, no Docker Hub pull):
|
|
8
|
+
// https://r2.deepfetch.de5.net/docker/cicy-code-latest.tar.gz
|
|
9
|
+
//
|
|
10
|
+
// The container maps :8008 and persists ~/cicy-ai in a named volume.
|
|
11
|
+
const { execFile } = require("child_process");
|
|
12
|
+
const https = require("https");
|
|
13
|
+
const http = require("http");
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const os = require("os");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
|
|
18
|
+
const IMAGE = process.env.CICY_DOCKER_IMAGE || "cicybot/cicy-code:latest";
|
|
19
|
+
const R2_TARBALL = process.env.CICY_DOCKER_URL || "https://r2.deepfetch.de5.net/docker/cicy-code-latest.tar.gz";
|
|
20
|
+
const CONTAINER = process.env.CICY_DOCKER_CONTAINER || "cicy-code";
|
|
21
|
+
const VOLUME = process.env.CICY_DOCKER_VOLUME || "cicy-ai-data";
|
|
22
|
+
// CICY_* env vars forwarded into the container (team onboarding, version pin…).
|
|
23
|
+
const PASS_ENV = ["CICY_TEAM_TOKEN", "CICY_CODE_VERSION", "NPM_REGISTRY", "CICY_NPM_REGISTRY", "CICY_AGENTS", "ENABLE_CDN", "CICY_CLOUDFLARED_TOKEN"];
|
|
24
|
+
|
|
25
|
+
function run(args, { timeout = 30000 } = {}) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
execFile("docker", args, { timeout, windowsHide: true }, (err, stdout, stderr) => {
|
|
28
|
+
if (err) { err.stdout = String(stdout || ""); err.stderr = String(stderr || ""); return reject(err); }
|
|
29
|
+
resolve({ stdout: String(stdout), stderr: String(stderr) });
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function dockerOk() {
|
|
35
|
+
try { await run(["version", "--format", "{{.Server.Version}}"], { timeout: 8000 }); return true; }
|
|
36
|
+
catch { return false; }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function imagePresent() {
|
|
40
|
+
try { await run(["image", "inspect", IMAGE], { timeout: 8000 }); return true; }
|
|
41
|
+
catch { return false; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function download(url, dest, hops = 5) {
|
|
45
|
+
return new Promise((resolve, reject) => {
|
|
46
|
+
if (hops <= 0) return reject(new Error("too many redirects"));
|
|
47
|
+
const lib = url.startsWith("https:") ? https : http;
|
|
48
|
+
const req = lib.get(url, { timeout: 60000 }, (res) => {
|
|
49
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
50
|
+
res.resume();
|
|
51
|
+
return download(res.headers.location, dest, hops - 1).then(resolve, reject);
|
|
52
|
+
}
|
|
53
|
+
if (res.statusCode !== 200) { res.resume(); return reject(new Error(`HTTP ${res.statusCode}`)); }
|
|
54
|
+
const out = fs.createWriteStream(dest);
|
|
55
|
+
res.pipe(out);
|
|
56
|
+
out.on("finish", () => out.close(() => resolve(dest)));
|
|
57
|
+
out.on("error", reject);
|
|
58
|
+
});
|
|
59
|
+
req.on("error", reject);
|
|
60
|
+
req.on("timeout", () => { req.destroy(); reject(new Error("timeout")); });
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function loadImage() {
|
|
65
|
+
const tmp = path.join(os.tmpdir(), `cicy-code-image-${process.pid}.tar.gz`);
|
|
66
|
+
console.log(`[docker-sidecar] downloading image from ${R2_TARBALL}`);
|
|
67
|
+
await download(R2_TARBALL, tmp);
|
|
68
|
+
console.log(`[docker-sidecar] docker load…`);
|
|
69
|
+
await run(["load", "-i", tmp], { timeout: 300000 });
|
|
70
|
+
try { fs.unlinkSync(tmp); } catch {}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function checkStatus() {
|
|
74
|
+
const installed = await dockerOk();
|
|
75
|
+
return { installed, imagePresent: installed ? await imagePresent() : false };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Start the container. Returns a sidecar child token { docker:true, container,
|
|
79
|
+
// id } or null when Docker isn't ready (homepage guides the user to install
|
|
80
|
+
// Docker Desktop).
|
|
81
|
+
async function start({ port = 8008 } = {}) {
|
|
82
|
+
if (!(await dockerOk())) {
|
|
83
|
+
console.warn("[docker-sidecar] Docker not available — homepage will guide install");
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (!(await imagePresent())) {
|
|
87
|
+
try { await loadImage(); }
|
|
88
|
+
catch (e) { console.warn(`[docker-sidecar] image load failed: ${e.message}`); return null; }
|
|
89
|
+
}
|
|
90
|
+
// Replace any stale container of the same name.
|
|
91
|
+
try { await run(["rm", "-f", CONTAINER]); } catch {}
|
|
92
|
+
|
|
93
|
+
const args = [
|
|
94
|
+
"run", "-d", "--name", CONTAINER, "--restart", "unless-stopped",
|
|
95
|
+
"-p", `${port}:8008`,
|
|
96
|
+
"-v", `${VOLUME}:/home/cicy/cicy-ai`,
|
|
97
|
+
];
|
|
98
|
+
for (const k of PASS_ENV) {
|
|
99
|
+
if (process.env[k]) args.push("-e", `${k}=${process.env[k]}`);
|
|
100
|
+
}
|
|
101
|
+
args.push(IMAGE);
|
|
102
|
+
|
|
103
|
+
const { stdout } = await run(args, { timeout: 60000 });
|
|
104
|
+
const id = stdout.trim().slice(0, 12);
|
|
105
|
+
console.log(`[docker-sidecar] started container ${CONTAINER} (${id}) on :${port}`);
|
|
106
|
+
return { docker: true, container: CONTAINER, id };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function stop() {
|
|
110
|
+
try { await run(["rm", "-f", CONTAINER]); } catch {}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { start, stop, checkStatus, loadImage, imagePresent, dockerOk };
|