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
@@ -1,285 +0,0 @@
1
- // Backend bridge. In Electron the contextBridge from
2
- // src/backends/homepage-preload.js exposes window.cicy with real IPC.
3
- // In a plain browser tab (Vite dev), no such bridge exists — fall back to
4
- // a mock that pulls Local health from the same origin's /api/health
5
- // (assumes cicy-code is at the same host:port that Vite is served from
6
- // OR at a fixed dev URL configured below) and keeps Cloud backends in
7
- // localStorage so the UI is fully exercisable for layout iteration.
8
-
9
- const MODE = (typeof window !== "undefined" && window.cicy) ? "electron" : "browser";
10
-
11
- // In browser mode, default to the cicy-code on the same host:8008.
12
- // Override with ?cicy=http://other:8008 in the URL bar if needed.
13
- const params = new URLSearchParams(typeof location !== "undefined" ? location.search : "");
14
- const DEV_LOCAL_BASE = params.get("cicy")
15
- || (typeof location !== "undefined" ? `${location.protocol}//${location.hostname}:8008` : "http://127.0.0.1:8008");
16
- const LS_KEY = "cicy-desktop-mock-backends";
17
- const LS_LOCAL = {
18
- id: "local",
19
- name: "Local (dev)",
20
- kind: "local",
21
- url: "",
22
- addedAt: new Date(0).toISOString(),
23
- };
24
-
25
- function lsLoad() {
26
- try { return JSON.parse(localStorage.getItem(LS_KEY) || "[]"); } catch { return []; }
27
- }
28
- function lsSave(list) { localStorage.setItem(LS_KEY, JSON.stringify(list)); }
29
-
30
- async function probeUrl(url, token) {
31
- if (!url) return { ok: false, error: "no url" };
32
- try {
33
- const u = new URL(url);
34
- u.pathname = "/api/health";
35
- u.search = "";
36
- const started = Date.now();
37
- const r = await fetch(u.toString(), {
38
- headers: token ? { Authorization: `Bearer ${token}` } : {},
39
- });
40
- const latencyMs = Date.now() - started;
41
- if (!r.ok) return { ok: false, statusCode: r.status, latencyMs };
42
- const data = await r.json();
43
- return { ok: true, statusCode: r.status, latencyMs, ...data };
44
- } catch (e) { return { ok: false, error: e.message }; }
45
- }
46
-
47
- const browserApi = {
48
- mode: "browser",
49
- backends: {
50
- async list() {
51
- const cloud = lsLoad();
52
- return [LS_LOCAL, ...cloud].map(b => ({
53
- ...b,
54
- resolvedUrl: b.kind === "local"
55
- ? `${DEV_LOCAL_BASE}/`
56
- : (b.url + (b.token ? `${b.url.includes("?") ? "&" : "?"}token=${encodeURIComponent(b.token)}` : "")),
57
- }));
58
- },
59
- async add(input) {
60
- const list = lsLoad();
61
- const b = {
62
- id: crypto.randomUUID(),
63
- name: input.name || "Unnamed",
64
- kind: "manual",
65
- url: input.url,
66
- token: input.token || undefined,
67
- addedAt: new Date().toISOString(),
68
- };
69
- list.push(b);
70
- lsSave(list);
71
- return b;
72
- },
73
- async remove(id) {
74
- if (id === "local") return false;
75
- const list = lsLoad().filter(b => b.id !== id);
76
- lsSave(list);
77
- return true;
78
- },
79
- async update(input) {
80
- if (!input || !input.id || input.id === "local") return null;
81
- const list = lsLoad();
82
- const i = list.findIndex(b => b.id === input.id);
83
- if (i < 0) return null;
84
- const prev = list[i];
85
- list[i] = {
86
- ...prev,
87
- name: input.name !== undefined ? (String(input.name).trim() || prev.name) : prev.name,
88
- url: input.url !== undefined ? String(input.url).trim() : prev.url,
89
- token: input.token === "" || input.token === null ? undefined : (input.token !== undefined ? input.token : prev.token),
90
- updatedAt: new Date().toISOString(),
91
- };
92
- lsSave(list);
93
- return list[i];
94
- },
95
- async probe(input) {
96
- return probeUrl(input.url, input.token);
97
- },
98
- async open(id) {
99
- const list = await browserApi.backends.list();
100
- const b = list.find(x => x.id === id);
101
- if (!b) return { ok: false, error: "not found" };
102
- window.open(b.resolvedUrl, "_blank");
103
- return { ok: true, windowId: -1 };
104
- },
105
- async health(id) {
106
- if (id === "local") return probeUrl(DEV_LOCAL_BASE, null);
107
- const list = lsLoad();
108
- const b = list.find(x => x.id === id);
109
- if (!b) return { ok: false, error: "not found" };
110
- return probeUrl(b.url, b.token);
111
- },
112
- async healthAll() {
113
- const list = await browserApi.backends.list();
114
- const out = await Promise.all(list.map(async b => ({
115
- id: b.id,
116
- health: b.kind === "local"
117
- ? await probeUrl(DEV_LOCAL_BASE, null)
118
- : await probeUrl(b.url, b.token),
119
- })));
120
- return out;
121
- },
122
- async restartSidecar() {
123
- return { ok: false, error: "restartSidecar only works inside Electron" };
124
- },
125
- },
126
- windows: {
127
- async list() {
128
- // In browser mode we just remember windows we opened this session.
129
- return [];
130
- },
131
- async focus() { return false; },
132
- },
133
- updates: {
134
- async check() {
135
- const health = await probeUrl(DEV_LOCAL_BASE, null);
136
- const currentVersion = health.ok ? health.version : null;
137
- try {
138
- const r = await fetch("https://api.github.com/repos/cicy-ai/cicy-code/releases/latest", {
139
- headers: { "Accept": "application/vnd.github+json" },
140
- });
141
- if (!r.ok) throw new Error(`GitHub ${r.status}`);
142
- const release = await r.json();
143
- const latestVersion = String(release.tag_name || "").replace(/^v/, "");
144
- const hasUpdate = !!currentVersion && cmpSemver(latestVersion, currentVersion) > 0;
145
- return { hasUpdate, currentVersion, latestVersion, releaseUrl: release.html_url };
146
- } catch (e) {
147
- throw new Error(`update check failed: ${e.message}`);
148
- }
149
- },
150
- async openReleasePage(url) {
151
- if (url) window.open(url, "_blank");
152
- return true;
153
- },
154
- },
155
- clipboard: {
156
- async write(text) {
157
- try { await navigator.clipboard.writeText(String(text || "")); return true; }
158
- catch { return false; }
159
- },
160
- },
161
- system: {
162
- // Browser-dev stub for the Local-card prereq probe. Honors
163
- // ?platform=win|mac|linux + ?prereq=ok|missing + ?installed=true|false
164
- // + ?running=true|false in the URL so we can preview every state
165
- // matrix without an actual host check.
166
- async checkPrereq() {
167
- const ua = (navigator.userAgent || "").toLowerCase();
168
- const auto = ua.includes("windows") ? "windows"
169
- : ua.includes("mac") ? "darwin"
170
- : "linux";
171
- const platform = params.get("platform") === "win" ? "windows"
172
- : params.get("platform") === "mac" ? "darwin"
173
- : params.get("platform") === "linux" ? "linux"
174
- : auto;
175
- const force = params.get("prereq");
176
- if (platform === "windows") {
177
- const ok = force === "missing" ? false : true;
178
- return {
179
- platform, kind: "docker",
180
- ok,
181
- required: "Docker Desktop",
182
- version: ok ? "27.3.1" : null,
183
- installUrl: "https://www.docker.com/products/docker-desktop/",
184
- };
185
- }
186
- const ok = force === "missing" ? false : true;
187
- return {
188
- platform, kind: "node",
189
- ok,
190
- required: "Node.js 22+",
191
- version: ok ? "v22.11.0" : "v18.19.0",
192
- installUrl: "https://nodejs.org/en/download",
193
- };
194
- },
195
- async checkCicyCodeInstalled() {
196
- // Same URL-param override for browser-dev preview.
197
- const v = params.get("installed");
198
- const installed = v == null ? true : v === "true";
199
- return { installed, version: installed ? "2.0.1" : null };
200
- },
201
- async localIPs() {
202
- // In a plain browser tab we can't read the host's IPs without WebRTC
203
- // tricks (which most browsers now block). Return empty — Electron
204
- // path provides the real impl via systemOps.localIPs.
205
- return [];
206
- },
207
- },
208
- };
209
-
210
- function cmpSemver(a, b) {
211
- const parse = s => String(s || "").replace(/^v/, "").split("-")[0].split(".").map(n => parseInt(n, 10) || 0);
212
- const A = parse(a), B = parse(b);
213
- for (let i = 0; i < Math.max(A.length, B.length); i++) {
214
- const x = A[i] || 0, y = B[i] || 0;
215
- if (x !== y) return x > y ? 1 : -1;
216
- }
217
- return 0;
218
- }
219
-
220
- // Hybrid bridge: each namespace prefers the Electron preload (when present)
221
- // and falls back to the browser stub otherwise. Lets us add new namespaces
222
- // like `system` to browserApi independently of the homepage-preload upgrade.
223
- const eApi = (typeof window !== "undefined" && window.cicy) ? window.cicy : null;
224
- // Browser stub for cicycode.* — Electron preload owns the real impl via
225
- // exec_shell. In browser dev we just toast "not available" so the UI
226
- // still renders without crashing.
227
- const browserCicycode = {
228
- install: async () => ({ ok: false, error: "cicycode.install only works inside Electron" }),
229
- upgrade: async () => ({ ok: false, error: "cicycode.upgrade only works inside Electron" }),
230
- start: async () => ({ ok: false, error: "cicycode.start only works inside Electron" }),
231
- stop: async () => ({ ok: false, error: "cicycode.stop only works inside Electron" }),
232
- uninstall: async () => ({ ok: false, error: "cicycode.uninstall only works inside Electron" }),
233
- };
234
-
235
- // system + cicycode commands live entirely in Vite (cicycode-ops.js) so
236
- // the install / start / stop / WSL / npm strings are HMR-able. The Electron
237
- // preload's namespaces of the same name are now bypassed for cicycode +
238
- // system (preload's stale Docker variants are no longer used).
239
- import { systemOps, cicycodeOps } from "./cicycode-ops.js";
240
-
241
- // On Windows, cicy-code runs in WSL. The main-process window-manager reads the
242
- // token from the WSL filesystem. As a belt-and-suspenders fix we also patch the
243
- // local backend's resolvedUrl in the render process so the homepage can connect
244
- // even when window-manager hasn't been updated yet.
245
- function wrapBackendsForWsl(backends) {
246
- if (!systemOps.isWin()) return backends;
247
- return {
248
- ...backends,
249
- async list() {
250
- const [items, wslToken] = await Promise.all([
251
- backends.list(),
252
- systemOps.readWslToken(),
253
- ]);
254
- if (!wslToken) return items;
255
- return items.map(b => {
256
- if (b.kind !== "local") return b;
257
- const tokenQs = `?token=${encodeURIComponent(wslToken)}`;
258
- return { ...b, resolvedUrl: `http://127.0.0.1:8008/${tokenQs}` };
259
- });
260
- },
261
- };
262
- }
263
-
264
- export const api = {
265
- backends: wrapBackendsForWsl((eApi && eApi.backends) || browserApi.backends),
266
- windows: (eApi && eApi.windows) || browserApi.windows,
267
- updates: (eApi && eApi.updates) || browserApi.updates,
268
- clipboard: (eApi && eApi.clipboard) || browserApi.clipboard,
269
- shell: (eApi && eApi.shell) || { openExternal: (u) => { if (u) window.open(u, "_blank"); return true; } },
270
- app: (eApi && eApi.app) || { quit: () => { window.close(); return true; } },
271
- tos: (eApi && eApi.tos) || browserTos(),
272
- // Always Vite-side — drops the preload's stale Docker commands.
273
- system: eApi ? systemOps : browserApi.system,
274
- cicycode: eApi ? cicycodeOps : browserCicycode,
275
- };
276
-
277
- function browserTos() {
278
- const KEY = "cicy-tos-accepted";
279
- return {
280
- get: async () => ({ version: localStorage.getItem(KEY), acceptedAt: null }),
281
- accept: async (version) => { localStorage.setItem(KEY, version); return { ok: true }; },
282
- };
283
- }
284
- export const apiMode = eApi ? "electron" : "browser";
285
- export const devLocalBase = DEV_LOCAL_BASE;
@@ -1,222 +0,0 @@
1
- // Platform-specific cicy-code lifecycle commands. Built entirely on top of
2
- // window.electronRPC("exec_shell", ...) so changing any command string is
3
- // a Vite HMR refresh — no .app rebuild.
4
- //
5
- // Windows path: WSL only. The Go binary doesn't compile for Windows
6
- // natively (POSIX pty deps), and Docker Desktop is too heavy / has
7
- // licensing friction. WSL is built into Win10+, is the path cicy-code's
8
- // own npm postinstall recommends, and reuses 90% of the Unix code path.
9
- //
10
- // Mac / Linux path: npx cicy-code, npm i -g cicy-code, etc.
11
-
12
- function rpc(tool, args) {
13
- if (typeof window === "undefined" || !window.electronRPC) {
14
- return Promise.resolve({ isError: true, content: [{ type: "text", text: "no electronRPC (browser dev mode)" }] });
15
- }
16
- return window.electronRPC(tool, args || {});
17
- }
18
-
19
- function flatten(res) {
20
- if (!res) return { ok: false, error: "no response" };
21
- if (res.isError) {
22
- const msg = (res.content || []).map(c => c.text).filter(Boolean).join("\n");
23
- return { ok: false, error: msg || "tool error" };
24
- }
25
- // Tools return `{content:[{type:"text", text:"<json string with stdout/stderr/exitCode>"}]}`
26
- // Parse the inner JSON; fall back to raw text.
27
- const raw = (res.content || []).map(c => c.text).filter(Boolean).join("\n");
28
- try {
29
- const inner = JSON.parse(raw);
30
- return { ok: inner.exitCode === 0, stdout: inner.stdout || "", stderr: inner.stderr || "", exitCode: inner.exitCode };
31
- } catch {
32
- return { ok: true, stdout: raw, stderr: "", exitCode: 0 };
33
- }
34
- }
35
-
36
- async function exec(cmd) { return flatten(await rpc("exec_shell", { command: cmd })); }
37
-
38
- // ── platform detection ───────────────────────────────────────────────
39
- // URL ?platform=win|mac|linux overrides the auto-detected platform so the
40
- // UI can be previewed in a browser without actually being on that OS.
41
- // (Real exec_shell calls still go to the host OS; this only changes how
42
- // the Local card renders and which command strings are picked.)
43
- function platform() {
44
- if (typeof window !== "undefined") {
45
- try {
46
- const force = new URLSearchParams(window.location.search).get("platform");
47
- if (force === "win") return "win32";
48
- if (force === "mac") return "darwin";
49
- if (force === "linux") return "linux";
50
- } catch {}
51
- if (window.cicy && window.cicy.platform) return window.cicy.platform;
52
- }
53
- const ua = (typeof navigator !== "undefined" ? navigator.userAgent : "").toLowerCase();
54
- if (ua.includes("windows")) return "win32";
55
- if (ua.includes("mac")) return "darwin";
56
- return "linux";
57
- }
58
- const isWin = () => platform() === "win32";
59
-
60
- // ── WSL token reader (Windows only) ──────────────────────────────────
61
- // Reads api_token from the cicy-code daemon's ~/cicy-ai/global.json inside
62
- // WSL. Called by api.js to build a token-bearing local URL without relying
63
- // on the main-process window-manager (which may not have the fix deployed yet).
64
- async function readWslToken() {
65
- if (!isWin()) return "";
66
- try {
67
- // Use chr() to avoid quote nesting issues across WSL/shell boundaries.
68
- const home = String.raw`os.path.expanduser(chr(126)+chr(47)+chr(99)+chr(105)+chr(99)+chr(121)+chr(45)+chr(97)+chr(105)+chr(47)+chr(103)+chr(108)+chr(111)+chr(98)+chr(97)+chr(108)+chr(46)+chr(106)+chr(115)+chr(111)+chr(110))`;
69
- const key = String.raw`chr(97)+chr(112)+chr(105)+chr(95)+chr(116)+chr(111)+chr(107)+chr(101)+chr(110)`;
70
- // Cap at 4 s — a `wsl --unregister` mid-session leaves the wsl
71
- // service in a state where `wsl -d Ubuntu …` hangs forever, and
72
- // since api.backends.list() awaits this in Promise.all, a hang here
73
- // freezes the entire App init (no checkWsl, no banner, blank UI).
74
- // Token enrichment is purely a convenience — losing it just means
75
- // the local team URL doesn't carry ?token=…, which the cicy-code
76
- // window-manager backfills anyway.
77
- const execPromise = exec(`wsl -d Ubuntu -- python3 -c "import json,os; d=json.load(open(${home})); print(d.get(${key},chr(110)))"`);
78
- const timeoutPromise = new Promise((resolve) =>
79
- setTimeout(() => resolve({ ok: false, stdout: "", stderr: "timeout" }), 4000)
80
- );
81
- const r = await Promise.race([execPromise, timeoutPromise]);
82
- const t = (r.stdout || "").trim();
83
- return t === "n" ? "" : t;
84
- } catch { return ""; }
85
- }
86
- async function checkNode() {
87
- const r = await exec("node --version");
88
- if (!r.ok) return { ok: false, kind: "node", required: "Node.js 22+", version: null, installUrl: "https://nodejs.org/en/download" };
89
- const m = r.stdout.trim().match(/v?(\d+)\.(\d+)\.(\d+)/);
90
- const major = m ? Number(m[1]) : 0;
91
- return { ok: major >= 22, kind: "node", required: "Node.js 22+", version: m ? `v${m[1]}.${m[2]}.${m[3]}` : null, installUrl: "https://nodejs.org/en/download" };
92
- }
93
-
94
- // Windows: WSL status check. `wsl --status` lists default distro + kernel version.
95
- // Exit 0 + non-empty stdout = installed. Exit non-0 = WSL not enabled.
96
- async function checkWsl() {
97
- // First: verify wsl.exe exists at all (missing on old Win10 builds).
98
- const which = await exec("where wsl.exe");
99
- if (!which.ok || !which.stdout.trim()) {
100
- return {
101
- ok: false,
102
- kind: "wsl",
103
- required: "WSL2 + Ubuntu",
104
- version: null,
105
- noExe: true, // wsl.exe itself is missing
106
- installUrl: "https://aka.ms/wsl-install",
107
- installCommand: null,
108
- };
109
- }
110
-
111
- const r = await exec("wsl --status");
112
- const installed = r.ok && r.stdout && r.stdout.trim().length > 0;
113
- const distroMatch = r.stdout && r.stdout.match(/Default Distribution:\s*(\S+)/i);
114
- // `wsl --list --quiet` tells us if any distro is registered.
115
- const listR = await exec("wsl --list --quiet");
116
- const hasDistro = listR.ok && listR.stdout.trim().length > 0;
117
- return {
118
- ok: installed && hasDistro,
119
- installed,
120
- hasDistro,
121
- kind: "wsl",
122
- required: "WSL2 + Ubuntu",
123
- version: distroMatch ? distroMatch[1] : (installed ? "WSL2" : null),
124
- installUrl: "https://learn.microsoft.com/en-us/windows/wsl/install",
125
- };
126
- }
127
-
128
- async function checkPrereq() {
129
- const plat = platform();
130
- if (isWin()) {
131
- // Windows: WSL only — Docker path was removed by request. The wsl
132
- // installer drives the entire install flow from the renderer (see
133
- // wslInstaller.js), so we surface the WSL prereq state here.
134
- const wsl = await checkWsl();
135
- return { platform: "windows", ...wsl };
136
- }
137
- const base = await checkNode();
138
- return { ...base, platform: plat };
139
- }
140
-
141
- // ── cicy-code installation state ─────────────────────────────────────
142
- async function checkCicyCodeInstalled() {
143
- if (isWin()) {
144
- // WSL only: docker inspect path was removed by request.
145
- const wr = await exec("wsl bash -lc 'command -v cicy-code >/dev/null && cicy-code --version 2>&1 || true'");
146
- const m = wr.stdout && wr.stdout.match(/(\d+\.\d+\.\d+)/);
147
- return { installed: !!m, version: m ? m[1] : null, runtime: "wsl" };
148
- }
149
- const cmd = "command -v cicy-code >/dev/null && cicy-code --version 2>&1 || npx --no-install cicy-code --version 2>&1 || true";
150
- const r = await exec(cmd);
151
- const m = r.stdout && r.stdout.match(/(\d+\.\d+\.\d+)/);
152
- return { installed: !!m, version: m ? m[1] : null };
153
- }
154
-
155
- // ── lifecycle commands ───────────────────────────────────────────────
156
- const LOG = "~/.cicy-code.log";
157
-
158
- function wrap(cmd) {
159
- // Wrap a Unix command for WSL execution on Windows.
160
- return isWin() ? `wsl bash -lc ${JSON.stringify(cmd)}` : cmd;
161
- }
162
-
163
- export const cicycodeOps = {
164
- async install() {
165
- // Windows install runs through wslInstaller.js which is invoked from
166
- // App.jsx handleInstall directly — this path is only hit on macOS/Linux.
167
- return exec(wrap("npm i -g cicy-code@latest"));
168
- },
169
- async upgrade() {
170
- if (isWin()) {
171
- // WSL path: re-run the npm install inside the user's WSL distro.
172
- return exec(wrap("npm i -g cicy-code@latest"));
173
- }
174
- return exec(wrap("npm i -g cicy-code@latest"));
175
- },
176
- async start(port = 8008) {
177
- return exec(wrap(`nohup cicy-code > ${LOG} 2>&1 & disown`));
178
- },
179
- async stop() {
180
- return exec(wrap("pkill -f 'cicy-code' || true"));
181
- },
182
- async uninstall() {
183
- return exec(wrap("npm uninstall -g cicy-code"));
184
- },
185
- async installPrereq() {
186
- // Windows: trigger WSL install (requires elevated shell — open the
187
- // Microsoft Store / docs page so the user runs it themselves).
188
- if (isWin()) {
189
- // Best-effort: try `wsl --install` in the same exec_shell. If it
190
- // requires elevation, fail and we open the docs URL instead.
191
- const r = await exec("wsl --install -d Ubuntu");
192
- if (r.ok) return r;
193
- return { ok: false, error: "Need to run `wsl --install` as Administrator. Opening docs.", installUrl: "https://learn.microsoft.com/en-us/windows/wsl/install" };
194
- }
195
- return { ok: false, error: "Open https://nodejs.org/en/download to install Node.js 22+" };
196
- },
197
- };
198
-
199
- // Enumerate this machine's non-loopback IPv4 addresses via the host shell.
200
- // Cross-platform fallback chain — no Node dependency, since this runs
201
- // before the cicy-code prereq check has been satisfied.
202
- async function localIPs() {
203
- let cmd;
204
- if (isWin()) {
205
- // Get-NetIPAddress is on Windows 10+ by default. Strips loopback +
206
- // APIPA (169.254.*) and joins on newline so we can split below.
207
- cmd = `powershell -NoProfile -Command "Get-NetIPAddress -AddressFamily IPv4 | Where-Object {$_.IPAddress -notmatch '^(127|169\\.254)'} | Select-Object -ExpandProperty IPAddress"`;
208
- } else if (platform() === "darwin") {
209
- cmd = `ifconfig | awk '/inet / && $2 !~ /^127/ {print $2}'`;
210
- } else {
211
- // hostname -I is portable across most Linux distros.
212
- cmd = `hostname -I 2>/dev/null | tr ' ' '\\n' | grep -v '^$' | grep -v '^127'`;
213
- }
214
- const r = await exec(cmd);
215
- if (!r.ok || !r.stdout) return [];
216
- return r.stdout
217
- .split(/\r?\n/)
218
- .map(s => s.trim())
219
- .filter(s => /^\d+\.\d+\.\d+\.\d+$/.test(s) && !s.startsWith("127.") && !s.startsWith("169.254."));
220
- }
221
-
222
- export const systemOps = { checkNode, checkWsl, checkPrereq, checkCicyCodeInstalled, localIPs, platform, isWin, readWslToken };