cicy-desktop 1.0.8 → 2.1.30
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/.cicy-code-ref +1 -0
- package/.env.dev +7 -0
- package/.github/workflows/linux-app-release.yml +78 -0
- package/.github/workflows/mac-app-release.yml +161 -0
- package/.github/workflows/windows-exe-release.yml +105 -0
- package/.kiro/steering/dev-workflow.md +6 -6
- package/AGENTS.md +30 -2
- package/CLAUDE.md +609 -162
- package/CLAUDE_HANDOFF.md +168 -0
- package/DESIGN.md +66 -0
- package/DOCKER.md +12 -12
- package/Dockerfile +2 -2
- package/README.md +331 -720
- package/bin/cicy-desktop +862 -0
- package/bin/cicy-rpc +13 -0
- package/build/icon.icns +0 -0
- package/build/icon.ico +0 -0
- package/build/icon.png +0 -0
- package/build/icon.svg +22 -0
- package/build/icons/icon-1024.png +0 -0
- package/build/icons/icon-128.png +0 -0
- package/build/icons/icon-16.png +0 -0
- package/build/icons/icon-24.png +0 -0
- package/build/icons/icon-256.png +0 -0
- package/build/icons/icon-32.png +0 -0
- package/build/icons/icon-48.png +0 -0
- package/build/icons/icon-512.png +0 -0
- package/build/icons/icon-64.png +0 -0
- package/build/icons/icon-96.png +0 -0
- package/build/icons/trayTemplate-16.png +0 -0
- package/build/icons/trayTemplate-16@2x.png +0 -0
- package/build/icons/trayTemplate-22.png +0 -0
- package/build/icons/trayTemplate-22@2x.png +0 -0
- package/build/icons/trayTemplate-32.png +0 -0
- package/build/icons/trayTemplate-32@2x.png +0 -0
- package/build/trayTemplate.png +0 -0
- package/build/trayTemplate.svg +13 -0
- package/build/trayTemplate@2x.png +0 -0
- package/cicy-dektop.command +51 -0
- package/copy-to-desktop.sh +4 -4
- package/dev-app-update.yml +3 -0
- package/docs/AUTOMATION-API.md +2 -2
- package/docs/REQUEST_MONITORING.md +1 -1
- package/docs/REST-API-FEATURE.md +2 -2
- package/docs/REST-API.md +276 -319
- package/docs/backend-selector-design.md +204 -0
- package/docs/chrome-proxy.md +142 -0
- package/docs/cicy-desktop-current-execution-bridge.md +509 -0
- package/docs/feature-distributed-multi-agent.md +11 -11
- package/docs/worklog-2026-03-27.md +108 -0
- package/docs/yaml.md +1 -1
- package/generate-openapi.js +159 -158
- package/package.json +80 -13
- package/scripts/prepare-cicy-code-sidecar.js +116 -0
- package/service.sh +5 -5
- package/src/app-updater.js +131 -0
- package/src/backends/auth-loopback.js +141 -0
- package/src/backends/homepage-preload.js +283 -0
- package/src/backends/homepage-react/assets/index-B8FrtpTX.js +49 -0
- package/src/backends/homepage-react/assets/index-CNVsvsZX.css +1 -0
- package/src/backends/homepage-react/index.html +13 -0
- package/src/backends/homepage-window.js +104 -0
- package/src/backends/homepage.html +1127 -0
- package/src/backends/ipc.js +220 -0
- package/src/backends/local-teams.js +621 -0
- package/src/backends/poller.js +63 -0
- package/src/backends/registry.js +141 -0
- package/src/backends/sidecar-ipc.js +186 -0
- package/src/backends/updater.js +71 -0
- package/src/backends/webview-preload.js +60 -0
- package/src/backends/window-manager.js +172 -0
- package/src/backends/window-tracker.js +50 -0
- package/src/chrome/chrome-cdp-client.js +80 -0
- package/src/chrome/chrome-launcher.js +229 -0
- package/src/chrome/debugger-port-resolver.js +44 -0
- package/src/chrome/runtime-registry.js +83 -0
- package/src/cli/rpc.js +356 -0
- package/src/cluster/artifact-registry.js +61 -0
- package/src/cluster/local-agent-registry.js +40 -0
- package/src/cluster/remote-executor.js +20 -0
- package/src/cluster/types.js +31 -0
- package/src/cluster/worker-client.js +74 -0
- package/src/cluster/worker-identity.js +25 -0
- package/src/i18n/index.js +56 -0
- package/src/i18n/locales/en.json +81 -0
- package/src/i18n/locales/fr.json +81 -0
- package/src/i18n/locales/ja.json +81 -0
- package/src/i18n/locales/zh-CN.json +81 -0
- package/src/main-old.js +7 -7
- package/src/main.js +840 -272
- package/src/master/agent-index.js +24 -0
- package/src/master/chrome-config.js +97 -0
- package/src/master/master-admin-routes.js +68 -0
- package/src/master/master-admin.html +359 -0
- package/src/master/master-main.js +103 -0
- package/src/master/master-metrics.js +145 -0
- package/src/master/master-routes.js +241 -0
- package/src/master/master-token-manager.js +46 -0
- package/src/master/session-affinity-store.js +36 -0
- package/src/master/task-scheduler.js +150 -0
- package/src/master/task-store.js +53 -0
- package/src/master/worker-inventory.js +267 -0
- package/src/master/worker-registry.js +74 -0
- package/src/server/args-parser.js +57 -2
- package/src/server/chrome-management-routes.js +145 -0
- package/src/server/chrome-proxy-routes.js +121 -0
- package/src/server/express-app.js +24 -5
- package/src/server/logging.js +1 -1
- package/src/server/mcp-server.js +1 -1
- package/src/server/tool-catalog.js +46 -0
- package/src/server/tool-executor.js +22 -0
- package/src/server/tool-registry.js +81 -77
- package/src/server/worker-observability-routes.js +54 -0
- package/src/sidecar/cicy-code.js +144 -0
- package/src/sidecar/installer.js +672 -0
- package/src/sidecar/mirrors.js +67 -0
- package/src/sidecar/net-detect.js +50 -0
- package/src/sidecar/wsl.js +585 -0
- package/src/swagger-ui.html +1 -1
- package/src/tools/account-tools.js +86 -59
- package/src/tools/chrome-tools.js +770 -0
- package/src/tools/exec-tools.js +27 -6
- package/src/tools/file-tools.js +3 -3
- package/src/tools/index.js +2 -1
- package/src/tools/ping.js +63 -60
- package/src/tools/system-tools.js +6 -6
- package/src/tools/window-tools.js +29 -5
- package/src/tray.js +93 -0
- package/src/ui-react-dist/assets/index-IWkApOSk.css +1 -0
- package/src/ui-react-dist/assets/index-jGZoL6K1.js +154 -0
- package/src/ui-react-dist/index.html +13 -0
- package/src/utils/auth.js +48 -12
- package/src/utils/global-json.js +85 -0
- package/src/utils/snapshot-utils.js +1 -1
- package/src/utils/window-monitor.js +17 -7
- package/src/utils/window-utils.js +94 -30
- package/update-desktop.sh +3 -3
- package/workers/render/index.html +12 -0
- package/workers/render/package-lock.json +2438 -0
- package/workers/render/package.json +20 -0
- package/workers/render/src/App.css +631 -0
- package/workers/render/src/App.jsx +864 -0
- package/workers/render/src/main.jsx +20 -0
- package/workers/render/vite.config.js +10 -0
- package/workers/render/wrangler.toml +15 -0
- package/workers/render.bak.20260528-2338/DESIGN_v2.md +254 -0
- package/workers/render.bak.20260528-2338/index.html +12 -0
- package/workers/render.bak.20260528-2338/package-lock.json +827 -0
- package/workers/render.bak.20260528-2338/package.json +19 -0
- package/workers/render.bak.20260528-2338/public/_headers +5 -0
- package/workers/render.bak.20260528-2338/public/manifest.json +6 -0
- package/workers/render.bak.20260528-2338/src/App.css +224 -0
- package/workers/render.bak.20260528-2338/src/App.jsx +1028 -0
- package/workers/render.bak.20260528-2338/src/api.js +285 -0
- package/workers/render.bak.20260528-2338/src/cicycode-ops.js +222 -0
- package/workers/render.bak.20260528-2338/src/components/BackendCard.css +299 -0
- package/workers/render.bak.20260528-2338/src/components/BackendCard.jsx +133 -0
- package/workers/render.bak.20260528-2338/src/components/BackendModal.css +161 -0
- package/workers/render.bak.20260528-2338/src/components/BackendModal.jsx +199 -0
- package/workers/render.bak.20260528-2338/src/components/Button.css +72 -0
- package/workers/render.bak.20260528-2338/src/components/Button.jsx +37 -0
- package/workers/render.bak.20260528-2338/src/components/Card.css +42 -0
- package/workers/render.bak.20260528-2338/src/components/Card.jsx +21 -0
- package/workers/render.bak.20260528-2338/src/components/Icon.jsx +30 -0
- package/workers/render.bak.20260528-2338/src/components/Menu.css +55 -0
- package/workers/render.bak.20260528-2338/src/components/Menu.jsx +91 -0
- package/workers/render.bak.20260528-2338/src/components/SidecarBanner.css +79 -0
- package/workers/render.bak.20260528-2338/src/components/SidecarBanner.jsx +84 -0
- package/workers/render.bak.20260528-2338/src/components/StatusChip.css +19 -0
- package/workers/render.bak.20260528-2338/src/components/StatusChip.jsx +31 -0
- package/workers/render.bak.20260528-2338/src/components/Toast.css +31 -0
- package/workers/render.bak.20260528-2338/src/components/Toast.jsx +23 -0
- package/workers/render.bak.20260528-2338/src/components/WslSetupBanner.css +464 -0
- package/workers/render.bak.20260528-2338/src/components/WslSetupBanner.jsx +716 -0
- package/workers/render.bak.20260528-2338/src/dockerInstaller.js +0 -0
- package/workers/render.bak.20260528-2338/src/i18n/en.json +116 -0
- package/workers/render.bak.20260528-2338/src/i18n/fr.json +116 -0
- package/workers/render.bak.20260528-2338/src/i18n/index.js +69 -0
- package/workers/render.bak.20260528-2338/src/i18n/ja.json +116 -0
- package/workers/render.bak.20260528-2338/src/i18n/zh-CN.json +121 -0
- package/workers/render.bak.20260528-2338/src/main.js +475 -0
- package/workers/render.bak.20260528-2338/src/main.jsx +18 -0
- package/workers/render.bak.20260528-2338/src/style.css +275 -0
- package/workers/render.bak.20260528-2338/src/styles/base.css +98 -0
- package/workers/render.bak.20260528-2338/src/styles/tokens.css +90 -0
- package/workers/render.bak.20260528-2338/src/tos.js +72 -0
- package/workers/render.bak.20260528-2338/src/worker.js +40 -0
- package/workers/render.bak.20260528-2338/src/wslInstaller.js +1563 -0
- package/workers/render.bak.20260528-2338/vite.config.js +36 -0
- package/workers/render.bak.20260528-2338/wrangler.toml +17 -0
- package/.github/workflows/build.yml +0 -85
- package/bin/cicy +0 -176
- package/electron-mcp-fixed.command +0 -134
- package/electron-mcp-simple.command +0 -135
- package/electron-mcp.command +0 -92
package/CLAUDE.md
CHANGED
|
@@ -1,162 +1,609 @@
|
|
|
1
|
-
# CLAUDE.md
|
|
2
|
-
|
|
3
|
-
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
###
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
npm run
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
-
|
|
60
|
-
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
-
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
###
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
- `
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
`cicy-desktop` is an Electron app that exposes ~50 system tools (Chrome control, clipboard, screenshot, shell exec, system info, ...) over MCP and REST/RPC. The app runs in two roles — **worker** (the Electron process exposing tools) and **master** (a thin control plane that routes tool calls across workers) — and ships a bundled `cicy-code` sidecar daemon so the desktop is a fully offline-capable backend.
|
|
6
|
+
|
|
7
|
+
## Development workflow rules (read first)
|
|
8
|
+
|
|
9
|
+
**This repo is edited in exactly one place** — the Linux dev machine. Mac is a runtime mirror for macOS-side validation; Windows is a separate runtime mirror that always rides the production CF Worker. There are **two iteration loops**: a fast one for React UI work (Mac, HMR) and a slow one for packaged-build validation. Pick whichever matches what you're touching.
|
|
10
|
+
|
|
11
|
+
### Platform routing
|
|
12
|
+
|
|
13
|
+
| Role | Where the SPA comes from | When to use |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| **Mac dev** | Vite dev server on Mac (`localhost:8173`) loaded by source-mode Electron | Loop A — any edit to `workers/render/src/**` (React UI), HMR live |
|
|
16
|
+
| **Mac packaged** | bundled `file://src/backends/homepage-react/` inside the .app | Loop B — release-shaped validation (main-process / preload / IPC changes) |
|
|
17
|
+
| **Windows** | remote `https://desktop.cicy-ai.com/` (CF Worker `desktop-render`) | Always. Win NSIS package + electron-updater auto-pulls newer releases. SPA changes ship via `wrangler deploy`, no rebuild needed |
|
|
18
|
+
|
|
19
|
+
Linux never runs Electron. Linux never serves the SPA to Mac. Edits + commits + the CF Worker deploy all happen here; the Mac and Win machines are pure runtime mirrors.
|
|
20
|
+
|
|
21
|
+
### Loop A — fast (Mac-native Vite + source-mode Electron)
|
|
22
|
+
|
|
23
|
+
Use this for anything in `workers/render/src/` (React UI, CSS, App.jsx). React + Vite **HMRs** without restarting Electron. No SSH tunnel involved — Vite and Electron both run on Mac, so the URL is genuinely local.
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# 1. Linux dev: sync source to Mac
|
|
27
|
+
rsync -avz --delete \
|
|
28
|
+
--exclude=node_modules --exclude=dist --exclude=.git \
|
|
29
|
+
~/projects/cicy-desktop/ mac:~/projects/cicy-desktop/
|
|
30
|
+
|
|
31
|
+
# 2. Mac: start the Vite dev server (first time also: `npm install` in workers/render)
|
|
32
|
+
ssh mac
|
|
33
|
+
cd ~/projects/cicy-desktop/workers/render && nohup npm run dev > /tmp/vite-dev.log 2>&1 &
|
|
34
|
+
# Vite listens on localhost:8173
|
|
35
|
+
|
|
36
|
+
# 3. Mac: kill any installed .app then run Electron from source
|
|
37
|
+
# .env.dev already sets CICY_HOMEPAGE_URL=http://localhost:8173, so
|
|
38
|
+
# source-mode Electron loads the live Vite bundle (HMR enabled) instead
|
|
39
|
+
# of the bundled file:// one.
|
|
40
|
+
pkill -f "MacOS/CiCy Desktop" 2>/dev/null
|
|
41
|
+
cd ~/projects/cicy-desktop && npm install # first time only
|
|
42
|
+
nohup bash -c 'set -a; . ./.env.dev; set +a; npm start' > /tmp/cicy-desktop-dev.log 2>&1 &
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Now: edit `workers/render/src/App.jsx` on Linux → `rsync` to Mac → HMR picks it up instantly. **No Electron restart needed** for React/CSS changes.
|
|
46
|
+
|
|
47
|
+
For continuous syncing during a session, run a one-shot rsync after each save, or set up `fswatch` / IDE-side sync. Whatever you do, the source of truth stays on Linux.
|
|
48
|
+
|
|
49
|
+
### Loop B — packaged build (only for release validation)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Linux: full sync (same as Loop A step 1)
|
|
53
|
+
rsync -avz --delete \
|
|
54
|
+
--exclude=node_modules --exclude=dist --exclude=.git \
|
|
55
|
+
~/projects/cicy-desktop/ mac:~/projects/cicy-desktop/
|
|
56
|
+
|
|
57
|
+
# Mac: build the .app (electron-builder won't overwrite a running one)
|
|
58
|
+
ssh mac
|
|
59
|
+
cd ~/projects/cicy-desktop
|
|
60
|
+
pkill -f "MacOS/CiCy Desktop" 2>/dev/null
|
|
61
|
+
CICY_CODE_BIN_PATH=<path-to-cicy-code-binary> npm run build:mac
|
|
62
|
+
open "dist/mac/CiCy Desktop.app"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Windows side — CF Worker is the SPA delivery path
|
|
66
|
+
|
|
67
|
+
Win never runs Vite or source-mode Electron. The Win NSIS package's main process loads `https://desktop.cicy-ai.com/`, which is the `desktop-render` Worker on Cloudflare serving `workers/render/dist/`. To ship a SPA change to Win users without releasing a new desktop package:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Linux dev: build + deploy SPA
|
|
71
|
+
cd ~/projects/cicy-desktop/workers/render
|
|
72
|
+
npm run build
|
|
73
|
+
CLOUDFLARE_ACCOUNT_ID=$(jq -r .cf.prod.account_id ~/cicy-ai/global.json) \
|
|
74
|
+
CLOUDFLARE_API_TOKEN=$(jq -r .cf.prod.api_token ~/cicy-ai/global.json) \
|
|
75
|
+
npx wrangler deploy
|
|
76
|
+
# Also mirror into the file:// folder for Mac packaged builds
|
|
77
|
+
rsync -av --delete dist/ ../../src/backends/homepage-react/
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Win users see the new SPA on next desktop relaunch (no auto-reload — they have to restart cicy-desktop). For main-process / preload changes Win needs an actual NSIS rebuild + electron-updater push (see `## Build & distribute`).
|
|
81
|
+
|
|
82
|
+
### Loop A failure modes
|
|
83
|
+
|
|
84
|
+
The Mac-native loop has no SSH tunnel to drop, but it has three other failure shapes:
|
|
85
|
+
|
|
86
|
+
1. **Vite died** (terminal closed, OOM, port conflict)
|
|
87
|
+
```bash
|
|
88
|
+
ssh mac "curl -sI http://localhost:8173/ -m 4 | head -1"
|
|
89
|
+
# Connection refused → Vite down. Restart with step 2 of Loop A.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
2. **The bundled .app is running instead of source-mode Electron** (it'll happily load Vite at 8173 if `.env.dev` is sourced, but more often Mac auto-launches the .app and you forget to kill it)
|
|
93
|
+
```bash
|
|
94
|
+
ssh mac 'ps -ef | grep "MacOS/CiCy Desktop" | grep -v grep'
|
|
95
|
+
# If you see /Applications/CiCy Desktop.app/... — that's the packaged one.
|
|
96
|
+
# pkill -f "MacOS/CiCy Desktop" then re-run Loop A step 3.
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
3. **Electron loaded a stale URL** (`.env.dev` not sourced, fallback to `file://` or `desktop.cicy-ai.com`). Inspect the live URL over CDP:
|
|
100
|
+
```bash
|
|
101
|
+
ssh mac 'curl -s http://127.0.0.1:9221/json | python3 -c "
|
|
102
|
+
import sys, json
|
|
103
|
+
for t in json.load(sys.stdin):
|
|
104
|
+
print(t.get(\"type\"), t.get(\"url\",\"\")[:80])"'
|
|
105
|
+
# If url is anything other than http://localhost:8173/, source-mode wasn't
|
|
106
|
+
# picked up cleanly. Re-export CICY_HOMEPAGE_URL and restart Electron.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### The three reload classes (the trap that wastes hours)
|
|
110
|
+
|
|
111
|
+
Electron has three independent execution contexts. **Each has its own reload rule**, and they don't share. Knowing which class your file belongs to is the difference between a 2-second iteration and a 30-second one.
|
|
112
|
+
|
|
113
|
+
| Class | Files | Reload trigger |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| **Vite render** | `workers/render/src/**` — App.jsx, App.css, any imported JS | ✅ **HMR**. Save → instant. No Electron restart. |
|
|
116
|
+
| **Preload** | `src/backends/homepage-preload.js`, `src/backends/webview-preload.js` | ❌ **Full Electron restart**. Preloads load once at BrowserWindow creation. `⌘+R`/devtools reload does NOT re-read them. |
|
|
117
|
+
| **Main process** | `src/main.js`, `src/backends/*.js` required by main (e.g. `local-teams.js`), IPC handler registration, any tool module | ❌ **Full Electron restart**. Main runs in Node and is never reloaded by the renderer. |
|
|
118
|
+
|
|
119
|
+
**The silent failure pattern**: React (Vite) HMRs to a new App.jsx that calls `window.cicy.someNewField`. If the **preload** still exposes the old surface, `someNewField` is `undefined` and your code paths silently misbehave. Always check that preload changes have actually landed by inspecting `window.cicy` over CDP (see [Debugging](#debugging-via-remote-debugging-port-9221)).
|
|
120
|
+
|
|
121
|
+
### Where edits go
|
|
122
|
+
|
|
123
|
+
- **Edit only on Linux** at `~/projects/cicy-desktop`. Never `ssh mac` to edit `src/...` — that creates two-master divergence (the kind of mess earlier rebases had to clean up).
|
|
124
|
+
- **Commits / pushes from Linux.** Mac is a working-tree mirror; nothing committed there should be the source of truth.
|
|
125
|
+
- **Windows builds: GitHub Actions only** (see `.github/workflows/build-windows*.yml`). Don't try local Windows builds.
|
|
126
|
+
|
|
127
|
+
## Common commands
|
|
128
|
+
|
|
129
|
+
### Install and run
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
npm install
|
|
133
|
+
npm start # local Electron worker via bin/cicy-desktop
|
|
134
|
+
npm run start:master # control-plane master on port 8100
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Formatting
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm run format
|
|
141
|
+
npm run format:check
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Tests
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
npm test # full suite
|
|
148
|
+
npx jest --runInBand tests/rpc/master-routes.test.js # single file
|
|
149
|
+
npx jest --runInBand --testNamePattern="Master routes" tests/rpc/master-routes.test.js
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`jest.config.js` runs single-process (`maxWorkers: 1`) with `forceExit: true`. Tests under `tests/rpc/` spin up the real Electron worker or supertest HTTP routes, so they take real time and are not pure unit tests.
|
|
153
|
+
|
|
154
|
+
### Build & distribute
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
npm install
|
|
158
|
+
npm run build # multi-platform via electron-builder
|
|
159
|
+
npm run build:mac # dist/CiCy Desktop-<ver>.dmg + .zip + dist/mac/CiCy Desktop.app
|
|
160
|
+
npm run build:win # NSIS installer
|
|
161
|
+
npm run build:linux # deb + AppImage
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Dev iteration loop on macOS:
|
|
165
|
+
|
|
166
|
+
1. edit `src/...`
|
|
167
|
+
2. `pkill -f "MacOS/CiCy Desktop"` (electron-builder won't overwrite a running app)
|
|
168
|
+
3. `CICY_CODE_BIN_PATH=<path-to-cicy-code-binary> npm run build:mac`
|
|
169
|
+
4. `open "dist/mac/CiCy Desktop.app"`
|
|
170
|
+
5. inspect the packaged renderer via `npx --yes asar extract-file "dist/mac/CiCy Desktop.app/Contents/Resources/app.asar" <path>`
|
|
171
|
+
|
|
172
|
+
### RPC CLI workflows
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
./bin/cicy-rpc init
|
|
176
|
+
./bin/cicy-rpc tools
|
|
177
|
+
./bin/cicy-rpc ping
|
|
178
|
+
CICY_NODE=windows ./bin/cicy-rpc ping
|
|
179
|
+
CICY_NODE=windows ./bin/cicy-rpc chrome_launch_profile accountIdx=1 url=https://example.com/
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Distinctions to internalize:
|
|
183
|
+
|
|
184
|
+
- `cicy` / `cicy-desktop` — local worker lifecycle (start/stop/status). **Not** for tool calls.
|
|
185
|
+
- `cicy-rpc` — tool invocation. Reads `~/global.json`. Choose remote node via `CICY_NODE=<name>`.
|
|
186
|
+
|
|
187
|
+
## Architecture
|
|
188
|
+
|
|
189
|
+
Two runtime roles:
|
|
190
|
+
|
|
191
|
+
1. **Worker** — an Electron process exposing tools over MCP and REST/RPC
|
|
192
|
+
2. **Master** — a thin control plane that tracks workers/agents/tasks and forwards `/api/rpc/:toolName` calls to a selected worker
|
|
193
|
+
|
|
194
|
+
A single host can run either or both. The bundled `.app` is normally a worker; running `npm run start:master` adds the master role.
|
|
195
|
+
|
|
196
|
+
### Worker runtime
|
|
197
|
+
|
|
198
|
+
Entrypoint: `src/main.js`.
|
|
199
|
+
|
|
200
|
+
Initializes Electron flags, auth, logging, Express, MCP plumbing, loads tool modules through `src/server/tool-catalog.js`, and registers every tool into both the MCP server and the REST/RPC surface. Registers + heartbeats to a master when `CICY_MASTER_URL` and `CICY_MASTER_TOKEN` are present.
|
|
201
|
+
|
|
202
|
+
Supporting modules:
|
|
203
|
+
|
|
204
|
+
- `src/server/express-app.js` — base Express app, CORS, `/ping`, `/docs`, `/openapi.json`, UI shell routes
|
|
205
|
+
- `src/server/mcp-server.js` — MCP transport setup
|
|
206
|
+
- `src/server/tool-registry.js` — tool registration bridge
|
|
207
|
+
- `src/server/tool-executor.js` — central execution path for REST/MCP tool calls
|
|
208
|
+
- `src/cluster/worker-client.js` — worker registration + heartbeat to the master
|
|
209
|
+
- `src/cluster/worker-identity.js` — identity payload advertised to the master
|
|
210
|
+
|
|
211
|
+
Worker RPC surface:
|
|
212
|
+
|
|
213
|
+
- `GET /rpc/tools` — list registered tools
|
|
214
|
+
- `POST /rpc/tools/call` — `{ name, arguments }` style invocation
|
|
215
|
+
- `POST /rpc/:toolName` — direct REST entrypoint, what `cicy-rpc` uses after resolving the node
|
|
216
|
+
|
|
217
|
+
### Tool system
|
|
218
|
+
|
|
219
|
+
Tool implementations live in `src/tools/*.js` and are loaded via `require("../tools")` from `src/server/tool-catalog.js`. Each module exports a function receiving `registerTool(name, description, schema, handler, options)`. The catalog is grouped by `tag` and reused for:
|
|
220
|
+
|
|
221
|
+
- MCP tool registration
|
|
222
|
+
- `GET /rpc/tools`
|
|
223
|
+
- OpenAPI generation in `/openapi.json`
|
|
224
|
+
|
|
225
|
+
A tool definition change affects all three surfaces at once.
|
|
226
|
+
|
|
227
|
+
Tools use **CommonJS** and **Zod schemas**.
|
|
228
|
+
|
|
229
|
+
### Master runtime
|
|
230
|
+
|
|
231
|
+
Entrypoint: `src/master/master-main.js`.
|
|
232
|
+
|
|
233
|
+
In-memory state:
|
|
234
|
+
|
|
235
|
+
- `WorkerRegistry` — live registered workers
|
|
236
|
+
- `WorkerInventory` — merged view of configured nodes from `~/global.json` plus registered workers
|
|
237
|
+
- `AgentIndex` — worker agent metadata
|
|
238
|
+
- `TaskStore` — forwarded task records
|
|
239
|
+
- `SessionAffinityStore` — control-session routing affinity
|
|
240
|
+
|
|
241
|
+
Master routes split into:
|
|
242
|
+
|
|
243
|
+
- `src/master/master-routes.js` — public API under `/api`
|
|
244
|
+
- `src/master/master-admin-routes.js` — admin-only routes under `/admin`
|
|
245
|
+
|
|
246
|
+
The hot path is `POST /api/rpc/:toolName`:
|
|
247
|
+
|
|
248
|
+
1. build request context from `workerId`, `agentId`, runtime session, control session, `accountIdx`
|
|
249
|
+
2. choose an execution target with `src/master/task-scheduler.js`
|
|
250
|
+
3. create a task record in `TaskStore`
|
|
251
|
+
4. inject worker-specific fields (`win_id`, `agentId`, `runtimeSessionId`, `effectiveChromeProfile`)
|
|
252
|
+
5. forward to the selected worker via `src/cluster/remote-executor`
|
|
253
|
+
6. store completion/failure state
|
|
254
|
+
|
|
255
|
+
### Chrome profile dispatch
|
|
256
|
+
|
|
257
|
+
Chrome profile handling is split between master and worker. The source-of-truth `chrome.json` lives on the **master** at `~/cicy-ai/db/chrome.json`; workers don't need a local one.
|
|
258
|
+
|
|
259
|
+
Master-side:
|
|
260
|
+
|
|
261
|
+
- `src/master/chrome-config.js` reads master-local `~/cicy-ai/db/chrome.json`
|
|
262
|
+
- `src/master/master-routes.js` injects `effectiveChromeProfile` for forwarded chrome tool calls when `accountIdx` is present
|
|
263
|
+
- injection covers `chrome_launch_profile`, `chrome_get_profile`, `chrome_get_targets`, `chrome_cdp_call`
|
|
264
|
+
|
|
265
|
+
Worker-side launch (`src/tools/chrome-tools.js::chrome_launch_profile`):
|
|
266
|
+
|
|
267
|
+
- prefers injected `effectiveChromeProfile`
|
|
268
|
+
- falls back to local `~/cicy-ai/db/chrome.json` for backward compat
|
|
269
|
+
- if neither exists → clear error
|
|
270
|
+
- if target user-data-dir doesn't exist → initialize from `~/chrome/_tmp` (or just `mkdir`)
|
|
271
|
+
- `orgPath -> Default` copy is best-effort if the path exists
|
|
272
|
+
|
|
273
|
+
Chrome internals separated:
|
|
274
|
+
|
|
275
|
+
- `src/chrome/chrome-launcher.js` — binary resolution, args, spawn, debugger readiness, per-profile proxy via `--proxy-server=<url>`
|
|
276
|
+
- `src/chrome/chrome-cdp-client.js` — `/json/version`, `/json/list`, activation, generic CDP calls
|
|
277
|
+
- `src/chrome/runtime-registry.js` — local runtime state per account
|
|
278
|
+
- `src/chrome/debugger-port-resolver.js` — port assignment
|
|
279
|
+
|
|
280
|
+
Cross-platform Chrome discovery (`chrome-launcher.js::getBinaryCandidates`):
|
|
281
|
+
|
|
282
|
+
- macOS: `/Applications/Google Chrome.app`, `~/Applications/Google Chrome.app`, `/Applications/Chromium.app`
|
|
283
|
+
- Windows: `%LOCALAPPDATA%` / `%PROGRAMFILES%` / `%PROGRAMFILES(X86)%` under `Google\Chrome\Application\chrome.exe` (+ Chromium variants)
|
|
284
|
+
- Linux: `google-chrome` / `chromium` / `chromium-browser` from PATH
|
|
285
|
+
|
|
286
|
+
When none are present, launch errors with `"Chrome/Chromium binary not found"` — user must install Chrome first.
|
|
287
|
+
|
|
288
|
+
### Homepage UI (Vite + React subproject)
|
|
289
|
+
|
|
290
|
+
The first window's UI is a **Vite + React subproject** at `workers/render/`, **not** the older `src/backends/homepage.html`. Treat them as independent codebases that happen to live in the same repo.
|
|
291
|
+
|
|
292
|
+
- entry: `workers/render/src/App.jsx` + `App.css`
|
|
293
|
+
- dev server: `workers/render/vite.config.js` → `0.0.0.0:8173`
|
|
294
|
+
- prod bundle: `workers/render/dist/index.html` (Electron loads via `file://` fallback when `CICY_HOMEPAGE_URL` is unset — see `src/backends/homepage-window.js:pickHomepageURL`)
|
|
295
|
+
- BrowserWindow config (`src/backends/homepage-window.js`):
|
|
296
|
+
- `preload: src/backends/homepage-preload.js`
|
|
297
|
+
- `webviewTag: true` + `allowRunningInsecureContent: true` (the right-side Team Helper drawer is a `<webview>` loading a remote http:// SPA)
|
|
298
|
+
- `sandbox: false` + `contextIsolation: true`
|
|
299
|
+
|
|
300
|
+
Day-to-day UI work happens entirely here. The Linux dev machine runs `npm run dev` in this subproject; the Mac's Electron loads from it via `CICY_HOMEPAGE_URL=http://localhost:8173` (see [Loop A](#loop-a--fast-vite--ssh--r--electron-from-source)).
|
|
301
|
+
|
|
302
|
+
### Preload bridges — `homepage-preload.js` vs `webview-preload.js`
|
|
303
|
+
|
|
304
|
+
Two distinct preload files because they run in different webContents with different security needs:
|
|
305
|
+
|
|
306
|
+
`src/backends/homepage-preload.js` — loaded into the **main BrowserWindow**'s renderer (the Vite/React UI). Exposes the full host surface the React app needs:
|
|
307
|
+
|
|
308
|
+
- `window.electronRPC(tool, args)` — generic dispatch into the worker tool registry (any tool from `src/tools/*.js`)
|
|
309
|
+
- `window.cicy.localTeams.{list, open, add, remove, update, upgrade, onWebviewRelay, replyWebviewRelay}`
|
|
310
|
+
- `window.cicy.cloud.fetch(url, opts)` — main-process `fetch` proxy (sidesteps CORS for `cicy-ai.com` calls; renderer's `localhost:8173` / `file://` origins aren't on the cloud's CORS allowlist)
|
|
311
|
+
- `window.cicy.auth.{loginStart, loginCancel, onComplete}` — browser-loopback login flow (`src/backends/auth-loopback.js`)
|
|
312
|
+
- `window.cicy.app.*`, `window.cicy.windows.*`, `window.cicy.shell.openExternal`, etc.
|
|
313
|
+
- `window.cicy.preloadPath` (legacy) and `window.cicy.webviewPreloadPath` — absolute paths the React code reads to wire the right-drawer `<webview preload={...}>`
|
|
314
|
+
|
|
315
|
+
`src/backends/webview-preload.js` — loaded into the **right-drawer `<webview>`** that hosts the cloud Team Helper SPA. Deliberately **TINY** because the webview loads a remote (third-party) SPA:
|
|
316
|
+
|
|
317
|
+
- `window.electronRPC(tool, args)` — same generic dispatch (the cloud helper's `agent-desktop` skill needs it to run shell commands on the user's machine)
|
|
318
|
+
- `window.cicy.localTeams.{list, add, remove, update, upgrade}` — all five go through `webview:relay` (next section), not directly to main
|
|
319
|
+
|
|
320
|
+
We don't reuse `homepage-preload.js` here because (1) it `require()`s non-electron modules (`../i18n`) that throw in the webview's sandboxed context, half-killing the preload before any contextBridge runs, and (2) exposing `cicy.backends.*` / `cicy.sidecar.*` / `cicy.auth.*` to a cloud SPA is unnecessary attack surface.
|
|
321
|
+
|
|
322
|
+
### `webview:relay` — webview ↔ host renderer authority pattern
|
|
323
|
+
|
|
324
|
+
The Team Helper webview can't be allowed to mutate `~/cicy-ai/global.json` directly — that's the host renderer's UX decision. So `webview-preload.js`'s `cicy.localTeams.*` methods relay through main to the host renderer (App.jsx), wait for its reply, and return the result to the webview's awaited promise.
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
webview main process host renderer (App.jsx)
|
|
328
|
+
│ │ │
|
|
329
|
+
ipcRenderer.invoke │ │
|
|
330
|
+
("webview:relay", msg) │ │
|
|
331
|
+
──────────────────────────────▶ ipcMain.handle │
|
|
332
|
+
│ │
|
|
333
|
+
host.send │
|
|
334
|
+
("webview:relay", │
|
|
335
|
+
{reqId, msg}) │
|
|
336
|
+
─────────────────────────▶ onWebviewRelay handler
|
|
337
|
+
│
|
|
338
|
+
await window.cicy.localTeams.add(spec)
|
|
339
|
+
fetchLocalTeams() ← UI refresh
|
|
340
|
+
│
|
|
341
|
+
ipcRenderer.send
|
|
342
|
+
◀────────────────────────── ("webview:relay-reply",
|
|
343
|
+
{reqId, result})
|
|
344
|
+
resolve(result) │
|
|
345
|
+
◀────────────────────────────── │ │
|
|
346
|
+
promise resolves │ │
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
15s timeout in main if the host renderer never replies. The host renderer is the only place that actually calls `localTeams:add/remove/update/upgrade` IPCs against `local-teams.js`. This keeps add/remove/upgrade authoritative for the UX (it can confirm/deny + refresh state) while still giving the webview real awaitable promises.
|
|
350
|
+
|
|
351
|
+
### Team Helper drawer (cloud SPA in `<webview>`)
|
|
352
|
+
|
|
353
|
+
The right-side drawer in App.jsx hosts a cloud-trial agent that walks new users through installing a local `cicy-code` backend, then hands them off to their own local helper:
|
|
354
|
+
|
|
355
|
+
- `HELPER_URL_BASE` (App.jsx constant): URL of the cloud helper container — currently `http://43.99.56.150:8011`. The container is built from `cicy-cloud/workers/helper/` (separate repo).
|
|
356
|
+
- `HELPER_SHARED_TOKEN`: the cloud container's `api_token`. Regenerated on every container restart; must be re-pasted into App.jsx whenever the cloud helper is rebuilt.
|
|
357
|
+
- `HELPER_PANE_ID = "w-6002:main.0"` — the `Team Helper` opencode pane the cloud `cicy-code --helper=1` mode pins.
|
|
358
|
+
|
|
359
|
+
The webview `src` is `${HELPER_URL_BASE}/?token=${token}#/agent/w-6002`. Once `agent-webpage helper-init` returns the user's OS / arch / network reachability, the cloud agent downloads `cicy-code` to the user's machine and registers the new team via `await window.cicy.localTeams.add({...install_source: "helper-mac-linux"...})`. App.jsx detects `install_source` starting with `helper-` and **auto-swaps `helperUrl`** 2.5 s later to `<new team base_url>/?token=...#/agent/w-6002`. From that point the drawer is the user's own long-lived local Team Helper — no 30-min cap, same task surface (install / upgrade / token-rotate / remove / open).
|
|
360
|
+
|
|
361
|
+
The "send `start`" centered modal in the drawer is a manual fallback for when the server-side helper-kick goroutine (cicy-code's `watchHelperOpencodeReadyAndKick`) didn't fire — e.g. the user reopened the drawer too quickly. Local-storage key `helper_modal_suppressed` records "Don't show again".
|
|
362
|
+
|
|
363
|
+
#### Helper token rotation workflow
|
|
364
|
+
|
|
365
|
+
Every cloud-helper rebuild generates a fresh `api_token`. `HELPER_SHARED_TOKEN` in App.jsx must be updated to match, otherwise the drawer's `<webview src=…?token=…>` and the renderer's `cloud.fetch` calls 401 against the helper. Standard loop (already proven against both local-Docker and the remote `43.99.56.150` helper, which accepts the same token):
|
|
366
|
+
|
|
367
|
+
```bash
|
|
368
|
+
# 1. Grab the new token straight out of the helper container's global.json
|
|
369
|
+
docker exec cicy-helper grep api_token /home/cicy/cicy-ai/global.json
|
|
370
|
+
# "api_token": "cicy_XXXXXXXX…",
|
|
371
|
+
|
|
372
|
+
# 2. Paste it into workers/render/src/App.jsx HELPER_SHARED_TOKEN
|
|
373
|
+
# Vite HMR's the constant change into the running renderer immediately,
|
|
374
|
+
# no Electron restart needed for THIS step (the webview keys off helperUrl
|
|
375
|
+
# so it remounts with the new token).
|
|
376
|
+
|
|
377
|
+
# 3. rsync to Mac so its source matches Linux (vite-dev tunnel still works,
|
|
378
|
+
# but explicit sync prevents drift if you later switch loops).
|
|
379
|
+
rsync -avz --delete \
|
|
380
|
+
--exclude=node_modules --exclude=dist --exclude=.git \
|
|
381
|
+
--exclude=workers/render/node_modules --exclude=workers/render/dist \
|
|
382
|
+
~/projects/cicy-desktop/ mac:~/projects/cicy-desktop/
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Only the **token** HMRs cleanly. If you also rebuilt the helper image with new AGENTS.md / new preload-relevant code, the cloud SPA in the webview is fine to reload (it's served by the container), but anything on the Electron side (homepage-preload, webview-preload, main, local-teams.js) is a full `⌘+Q` + reopen as usual.
|
|
386
|
+
|
|
387
|
+
`HELPER_URL_BASE` (App.jsx) is the **other** half of the pairing. It currently points at `http://43.99.56.150:8011` (a long-running shared helper). If you want to swap to a locally rebuilt container, change it to `http://localhost:8011` and `ssh -fNR 8011:127.0.0.1:8011 mac` so the Mac can reach your dev box's helper. Same token-grab step still applies.
|
|
388
|
+
|
|
389
|
+
### Backends launcher (legacy `src/backends/`)
|
|
390
|
+
|
|
391
|
+
`src/backends/homepage.html` + `cicy.backends.{list, add, remove, …}` is the **pre-Vite** launcher. It still ships and still works (the bundled sidecar and Add-by-URL flow live here), but it is **not** what new users see — `pickHomepageURL` prefers the Vite/React entry. Touch this only when you're working on the old launcher path. Registry file: `<userData>/backends.json` (`src/backends/registry.js`). IPC handlers: `src/backends/ipc.js`.
|
|
392
|
+
|
|
393
|
+
### Trust gate (`isTrustedUrl`)
|
|
394
|
+
|
|
395
|
+
`src/utils/window-utils.js::isTrustedUrl(url)` decides whether a `BrowserWindow` gets:
|
|
396
|
+
|
|
397
|
+
- `nodeIntegration: true`
|
|
398
|
+
- `contextIsolation: false`
|
|
399
|
+
- the `dom-ready` `electronRPC` auto-injection
|
|
400
|
+
|
|
401
|
+
Trusted hosts:
|
|
402
|
+
|
|
403
|
+
1. `localhost` / `127.0.0.1`
|
|
404
|
+
2. `*.de5.net`
|
|
405
|
+
3. **any hostname in `backends.json`** — anything the user added via the Add form (v2.0.2 widening)
|
|
406
|
+
|
|
407
|
+
Effect for renderers loading a trusted URL: `window.electronRPC(toolName, args)` is a function round-tripping through `ipcRenderer.invoke("rpc", toolName, args)` into the worker's tool registry.
|
|
408
|
+
|
|
409
|
+
### Bridge to cicy-code (`desktop_event` / `rpc_call`)
|
|
410
|
+
|
|
411
|
+
When this app opens a cicy-code backend, the server-side `agent-desktop` and `agent-chrome` skills reach Electron-main tools through cicy-code's chat WebSocket — **not** through this app's REST/RPC surface.
|
|
412
|
+
|
|
413
|
+
Flow:
|
|
414
|
+
|
|
415
|
+
1. cicy-code server posts `POST /api/chat/push` with `{ type: "desktop_event", data: { type: "rpc_call", tool, args, requestId } }`
|
|
416
|
+
2. cicy-code relays to the connected client over WebSocket
|
|
417
|
+
3. cicy-code's React app (`app/src/components/layout/useDesktopEvents.ts`) listens for `desktop_event`, sees `type === "rpc_call"`, awaits `window.electronRPC(tool, args)` — the same function the trust gate exposes
|
|
418
|
+
4. result dispatched as `rpc-result` CustomEvent → relayed back through the WS by `Workspace.tsx`
|
|
419
|
+
5. server-side skill (`hosttools.go::desktopRPC`) matches by `requestId` and returns
|
|
420
|
+
|
|
421
|
+
Why this exists: `agent-webpage exec-js` runs synchronously via `window.eval` and cannot await Promises, so it can't call `electronRPC` directly. `rpc_call` is the async-safe sibling.
|
|
422
|
+
|
|
423
|
+
Implication: anything that calls `window.electronRPC` from outside the cicy-code React tree must run inside a renderer where `isTrustedUrl` granted `nodeIntegration`, or where `homepage-preload.js` is the preload.
|
|
424
|
+
|
|
425
|
+
### On-disk layout — `~/.local/bin/cicy-code` symlink → versioned binary
|
|
426
|
+
|
|
427
|
+
Both the in-app installer (`src/sidecar/installer.js`) and the cloud Team Helper agent write the daemon into the user's `~/.local/bin/` with this shape:
|
|
428
|
+
|
|
429
|
+
```
|
|
430
|
+
~/.local/bin/cicy-code-2.1.8 (actual binary, +x)
|
|
431
|
+
~/.local/bin/cicy-code-2.1.9 (next version after upgrade)
|
|
432
|
+
~/.local/bin/cicy-code (symlink → cicy-code-2.1.9, atomic-swapped on upgrade)
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
Rationale:
|
|
436
|
+
|
|
437
|
+
- **Atomic upgrade**: `ln -sfn cicy-code-<new> ~/.local/bin/cicy-code` is a POSIX-atomic relink. A long-running daemon spawned via the symlink keeps its current inode open; future re-spawns pick up the new target.
|
|
438
|
+
- **Rollback**: old versioned binaries stay on disk. Rolling back is one symlink swap.
|
|
439
|
+
- **Version-from-disk**: `fs.readlinkSync(~/.local/bin/cicy-code)` and parsing the basename gives the current version — no separate `version` file to keep in sync. Legacy fallback to a `<binDir>/version` file is kept in `installer.userVersion()` for older installs.
|
|
440
|
+
|
|
441
|
+
Upgrade flow inside `src/backends/local-teams.js::upgradeNative`:
|
|
442
|
+
|
|
443
|
+
1. `fetchManifestVersion()` learns the upcoming version (so the download filename is `cicy-code-<ver>` from the start).
|
|
444
|
+
2. `downloadFile(directURL → mirrorURL, ~/.local/bin/cicy-code-<ver>)`.
|
|
445
|
+
3. `chmod 0o755`.
|
|
446
|
+
4. `--version` round-trip verifies the bytes; if mirror served stale, rename the file onto the real version.
|
|
447
|
+
5. `pkill -f ~/.local/bin/cicy-code` (and the previously stored `install_path` if it differs) kills the old daemon.
|
|
448
|
+
6. `ln -sfn cicy-code-<ver> ~/.local/bin/cicy-code` (written at a tmp name + renamed = atomic).
|
|
449
|
+
7. Re-spawn via the symlink (`spawn(linkPath, [], { detached: true })`).
|
|
450
|
+
8. `waitForHealth(/api/health)` up to 30 s; on success, `updateTeam(id, {install_path: linkPath})` so older team rows that stored a versioned path migrate to the symlink.
|
|
451
|
+
|
|
452
|
+
The cloud helper agent uses the **same layout** when it does the initial install (`AGENTS.md` step 1A.2/1A.3 in `cicy-cloud/workers/helper/`). It writes `cicy-code-<ver>` + `ln -sfn` + spawns via the symlink, then registers the team with `install_path=~/.local/bin/cicy-code`. This way the agent's install path and the desktop's upgrade path are identical — no special-case wiring needed.
|
|
453
|
+
|
|
454
|
+
### Sidecar cicy-code daemon — **NOT bundled** (2026-05-29 principle)
|
|
455
|
+
|
|
456
|
+
`cicy-desktop` no longer ships a `cicy-code` binary in the `.app`. The daemon is acquired one of three ways:
|
|
457
|
+
|
|
458
|
+
1. **Already running on `:8008`** — left over from a previous session, started by the user, or installed by the cloud Team Helper. `src/sidecar/cicy-code.js::probeExisting` detects it and `start()` reuses without re-spawning.
|
|
459
|
+
2. **In-app installer** — `src/sidecar/installer.js` downloads the platform-matching binary from `cicy-ai/cicy-code` GitHub releases into `<userData>/cicy-code/<platform>-<arch>/cicy-code`. Triggered from the homepage when no daemon is running. `userBinary()` returns this path; `bundledBinaryPath()` only checks this single source — there is intentionally no `<App>/Contents/Resources/cicy-code` fallback.
|
|
460
|
+
3. **Cloud Team Helper** — the trial helper container walks the user through installing cicy-code on their own machine, then registers it via `window.cicy.localTeams.add({...})`. Today the helper writes to `~/Downloads/cicy-code`; the in-app installer location is preferred (`<userData>/cicy-code/...`) so `userBinary()` discovers it on the next desktop launch with no extra wiring.
|
|
461
|
+
|
|
462
|
+
Removed in this principle change:
|
|
463
|
+
- `package.json` no longer has `extraResources` entries for `vendor/cicy-code/*/cicy-code`
|
|
464
|
+
- `package.json` no longer runs `prepare:sidecar` in `prebuild*`
|
|
465
|
+
- `scripts/prepare-cicy-code-sidecar.js` is dormant (kept for reference; can be deleted)
|
|
466
|
+
- `vendor/cicy-code/` directory is no longer touched by the build
|
|
467
|
+
|
|
468
|
+
`sidecar/cicy-code.js::start()` therefore returns `null` whenever no `userBinary()` is present AND no daemon is on `:8008`. The homepage's Team Helper card is the surface that gets the user from "no daemon" to "daemon running" — either by triggering the in-app installer (for the legacy single-click path) or by walking the cloud-helper onboarding flow.
|
|
469
|
+
|
|
470
|
+
Windows path is unchanged — `src/sidecar/wsl.js` runs the daemon inside WSL2 because cicy-code is POSIX-only.
|
|
471
|
+
|
|
472
|
+
#### What broke that motivated the principle (2026-05-29)
|
|
473
|
+
|
|
474
|
+
Bundled `cicy-code` was pinned at `v2.1.2`. The trial helper installs `releases/latest` (now `v2.1.8`). On every cicy-desktop start the bundled `v2.1.2` raced ahead and bound `:8008`; when the helper later tried to launch `~/Downloads/cicy-code` it hit "address in use" and silently exited. Worse: `localTeams.list()` saw `:8008` healthy and added a "running" team card pointing at `w-6002`, which the `v2.1.2` daemon doesn't know how to spawn (the built-in pane only exists in `v2.1.8+`). End result: drawer swap → 404, version churn, hours wasted. The principle removes the bundled copy entirely — there's exactly one acquisition path and one source of truth at any time.
|
|
475
|
+
|
|
476
|
+
### CLI/config split
|
|
477
|
+
|
|
478
|
+
Two CLIs, different jobs:
|
|
479
|
+
|
|
480
|
+
- `bin/cicy-desktop` / `cicy` — local worker lifecycle (start/stop/status)
|
|
481
|
+
- `bin/cicy-rpc` — tool invocation
|
|
482
|
+
|
|
483
|
+
`src/cli/rpc.js` (`cicy-rpc`):
|
|
484
|
+
|
|
485
|
+
- reads `~/global.json`
|
|
486
|
+
- resolves `cicyDesktopNodes[<name>]`
|
|
487
|
+
- uses `CICY_NODE` to choose the target node
|
|
488
|
+
- POSTs directly to `/<rpc-path>` on that node with bearer auth
|
|
489
|
+
|
|
490
|
+
`cicy-rpc init` only initializes `~/global.json` if missing. It is not a general node-management command.
|
|
491
|
+
|
|
492
|
+
## Config and auth
|
|
493
|
+
|
|
494
|
+
### `~/global.json`
|
|
495
|
+
|
|
496
|
+
```json
|
|
497
|
+
{
|
|
498
|
+
"api_token": "cicy_…",
|
|
499
|
+
"cicyDesktopNodes": {
|
|
500
|
+
"mac": { "base_url": "http://127.0.0.1:8101", "api_token": "…" },
|
|
501
|
+
"windows": { "base_url": "http://1.2.3.4:8101", "api_token": "…" }
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
`cicy-rpc` picks the token in this order:
|
|
507
|
+
|
|
508
|
+
1. `cicyDesktopNodes.<name>.api_token`
|
|
509
|
+
2. top-level `api_token`
|
|
510
|
+
|
|
511
|
+
### Worker registration to master
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
MASTER_TOKEN=$(jq -r '.api_token' ~/global.json)
|
|
515
|
+
PORT=8101 CICY_MASTER_URL="http://127.0.0.1:8100" CICY_MASTER_TOKEN="$MASTER_TOKEN" npm start
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
The master uses `CICY_MASTER_TOKEN` directly or falls back to `MasterTokenManager`.
|
|
519
|
+
|
|
520
|
+
## File map
|
|
521
|
+
|
|
522
|
+
| What | Where |
|
|
523
|
+
|---|---|
|
|
524
|
+
| worker startup/runtime | `src/main.js` |
|
|
525
|
+
| master startup/runtime | `src/master/master-main.js` |
|
|
526
|
+
| master forwarding | `src/master/master-routes.js` |
|
|
527
|
+
| `~/global.json` inventory | `src/master/worker-inventory.js` |
|
|
528
|
+
| RPC CLI | `src/cli/rpc.js` |
|
|
529
|
+
| worker tool catalog | `src/server/tool-catalog.js` |
|
|
530
|
+
| tool execution plumbing | `src/server/tool-executor.js` |
|
|
531
|
+
| Chrome tools | `src/tools/chrome-tools.js` |
|
|
532
|
+
| Chrome launcher | `src/chrome/chrome-launcher.js` |
|
|
533
|
+
| Chrome CDP helpers | `src/chrome/chrome-cdp-client.js` |
|
|
534
|
+
| cluster registration | `src/cluster/worker-client.js` |
|
|
535
|
+
| backends registry | `src/backends/registry.js` |
|
|
536
|
+
| backends launcher UI | `src/backends/homepage.html` |
|
|
537
|
+
| backends preload bridge | `src/backends/homepage-preload.js` |
|
|
538
|
+
| BrowserWindow trust + auto-inject | `src/utils/window-utils.js` |
|
|
539
|
+
| sidecar packaging | `scripts/prepare-cicy-code-sidecar.js` |
|
|
540
|
+
| RPC test files | `tests/rpc/master-routes.test.js`, `tests/rpc/cicy-rpc.test.js` |
|
|
541
|
+
|
|
542
|
+
## Debugging via remote-debugging-port 9221
|
|
543
|
+
|
|
544
|
+
Electron renderers in dev are launched with `--remote-debugging-port=9221`. Use it instead of guessing.
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
# Open the tunnel once per session
|
|
548
|
+
ssh -fNR 9221:127.0.0.1:9221 mac # if your dev → Mac
|
|
549
|
+
# or
|
|
550
|
+
ssh -fNL 9221:127.0.0.1:9221 mac # if you're on Linux looking at Mac
|
|
551
|
+
|
|
552
|
+
# Enumerate targets (homepage + every <webview>)
|
|
553
|
+
curl -s http://127.0.0.1:9221/json/list | jq '.[] | {type, url, webSocketDebuggerUrl}'
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
Each target has a `webSocketDebuggerUrl`. Connect and send any `Runtime.evaluate` to inspect window state from outside Electron:
|
|
557
|
+
|
|
558
|
+
```js
|
|
559
|
+
// /tmp/cdp-probe.mjs
|
|
560
|
+
import http from 'node:http';
|
|
561
|
+
const targets = await new Promise(r =>
|
|
562
|
+
http.get('http://127.0.0.1:9221/json/list', res => {
|
|
563
|
+
let b=''; res.on('data',c=>b+=c); res.on('end',()=>r(JSON.parse(b)));
|
|
564
|
+
}));
|
|
565
|
+
const target = targets.find(t => t.type === 'webview'); // or 'page' for homepage
|
|
566
|
+
const ws = new WebSocket(target.webSocketDebuggerUrl);
|
|
567
|
+
await new Promise((ok, fail) => { ws.onopen = ok; ws.onerror = fail; });
|
|
568
|
+
|
|
569
|
+
let id = 0;
|
|
570
|
+
function call(method, params={}) {
|
|
571
|
+
const reqId = ++id;
|
|
572
|
+
return new Promise(res => {
|
|
573
|
+
ws.addEventListener('message', function h(e) {
|
|
574
|
+
const m = JSON.parse(e.data);
|
|
575
|
+
if (m.id === reqId) { ws.removeEventListener('message', h); res(m); }
|
|
576
|
+
});
|
|
577
|
+
ws.send(JSON.stringify({ id: reqId, method, params }));
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
for (const expr of [
|
|
582
|
+
'typeof window.electronRPC',
|
|
583
|
+
'typeof window.cicy',
|
|
584
|
+
'window.cicy && Object.keys(window.cicy.localTeams || {})',
|
|
585
|
+
'(window.cicy && window.cicy.webviewPreloadPath) || null',
|
|
586
|
+
]) {
|
|
587
|
+
const r = await call('Runtime.evaluate', { expression: expr, returnByValue: true });
|
|
588
|
+
console.log(expr, '→', JSON.stringify(r.result?.result?.value));
|
|
589
|
+
}
|
|
590
|
+
ws.close();
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
Typical pattern after editing a preload: this probe **must** show your new field on the homepage target before you assume the change landed. If it shows the old surface, you forgot to `⌘+Q` + reopen Electron.
|
|
594
|
+
|
|
595
|
+
For the Team Helper webview specifically: its `webSocketDebuggerUrl` lives in the same `/json/list` output, typed `webview`. Probing it confirms whether the `<webview preload={file://...}>` attribute actually loaded `webview-preload.js`.
|
|
596
|
+
|
|
597
|
+
## Mental model checklist
|
|
598
|
+
|
|
599
|
+
When touching any of these, expect ripple effects:
|
|
600
|
+
|
|
601
|
+
- adding a tool → touches MCP server, REST/RPC, OpenAPI all at once via the catalog
|
|
602
|
+
- changing trust criteria → changes `nodeIntegration` for whole classes of windows; verify with the cicy-code bridge still works
|
|
603
|
+
- changing the homepage entry flow → check that cold-launch and "back to launcher" paths both behave (history.length / sessionStorage gates)
|
|
604
|
+
- changing the sidecar packaging → verify `dist/mac/CiCy Desktop.app/Contents/Resources/cicy-code/cicy-code` is present and executable after build
|
|
605
|
+
- changing `chrome_*` tools → both master injection (`effectiveChromeProfile`) and worker fallback (`~/cicy-ai/db/chrome.json`) paths still need to work
|
|
606
|
+
- changing **any preload file** → `⌘+Q` + reopen Electron is mandatory; HMR / `⌘+R` won't reload it. Confirm via CDP `Runtime.evaluate` on the target window
|
|
607
|
+
- changing **`src/backends/local-teams.js`** → it's `require`d by main; full Electron restart needed. Don't forget to also expose any new methods through both `homepage-preload.js` (full surface) and `webview-preload.js` (relay)
|
|
608
|
+
- changing the `<webview>` preload surface → also update `webview:relay` handlers in main + App.jsx so the new methods route correctly. Webview can't call IPCs directly; everything funnels through `webview:relay`
|
|
609
|
+
- changing the cloud Team Helper container → rebuild + restart copies a NEW `api_token`. Re-paste `HELPER_SHARED_TOKEN` in `workers/render/src/App.jsx` (HMRs) but if you also changed `HELPER_URL_BASE` you've effectively repointed the drawer — verify the new URL is reachable from the user's machine, not just yours
|