cicy-desktop 2.1.78 → 2.1.79
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/bin/cicy-desktop +7 -7
- package/package.json +6 -6
- package/src/backends/homepage-preload.js +22 -0
- package/src/backends/homepage-react/assets/index-CKpaMBKz.css +1 -0
- package/src/backends/homepage-react/assets/index-CSsNZgC5.js +365 -0
- package/src/backends/homepage-react/index.html +2 -2
- package/src/backends/homepage-window.js +52 -7
- package/src/backends/ipc.js +57 -0
- package/src/backends/local-teams.js +73 -26
- package/src/backends/sidecar-ipc.js +11 -0
- package/src/backends/webview-preload.js +5 -3
- package/src/backends/window-manager.js +13 -3
- package/src/chrome/chrome-launcher.js +5 -4
- package/src/chrome/debugger-port-resolver.js +1 -1
- package/src/cloud/cloud-client.js +237 -41
- package/src/cluster/types.js +0 -5
- package/src/extension/inject.js +1 -1
- package/src/main.js +282 -88
- package/src/master/chrome-config.js +2 -2
- package/src/preload-rpc.js +1 -1
- package/src/profiles/profile-store.js +321 -0
- package/src/profiles/trusted-origins-store.js +95 -0
- package/src/server/worker-observability-routes.js +0 -2
- package/src/sidecar/cicy-code.js +84 -23
- package/src/sidecar/localbin.js +20 -3
- package/src/sidecar/native.js +3 -3
- package/src/sidecar/version.js +45 -0
- package/src/tabbrowser/newtab-protocol.js +54 -0
- package/src/tabbrowser/tab-browser.html +151 -0
- package/src/tabbrowser/tab-shell-preload.js +28 -0
- package/src/tabbrowser/tab-shell.html +227 -0
- package/src/tools/account-tools.js +191 -25
- package/src/tools/chrome-tools.js +173 -37
- package/src/tools/device-tools.js +25 -0
- package/src/tools/index.js +2 -0
- package/src/tools/tab-browser-tools.js +453 -0
- package/src/tools/window-tools.js +64 -7
- package/src/utils/brand-host-electron.js +25 -0
- package/src/utils/context-menu-options.js +80 -0
- package/src/utils/cookie-logins.js +58 -0
- package/src/utils/ip-probe.js +50 -0
- package/src/utils/rpc-audit.js +53 -0
- package/src/utils/rpc-guard.js +189 -0
- package/src/utils/window-monitor.js +5 -15
- package/src/utils/window-registry.js +210 -0
- package/src/utils/window-thumbnails.js +126 -0
- package/src/utils/window-utils.js +146 -109
- package/workers/render/package-lock.json +6 -6
- package/workers/render/src/App.css +36 -2
- package/workers/render/src/App.jsx +587 -103
- package/src/backends/artifact-ipc.js +0 -142
- package/src/backends/homepage-react/assets/index-DE9m6JTn.css +0 -1
- package/src/backends/homepage-react/assets/index-DLYMzgf5.js +0 -365
- package/src/cluster/artifact-registry.js +0 -61
|
@@ -3,6 +3,10 @@ const path = require("path");
|
|
|
3
3
|
const os = require("os");
|
|
4
4
|
const { z } = require("zod");
|
|
5
5
|
|
|
6
|
+
const profileStore = require("../profiles/profile-store");
|
|
7
|
+
const { summarizeCookieLogins } = require("../utils/cookie-logins");
|
|
8
|
+
const { probeIpViaSession } = require("../utils/ip-probe");
|
|
9
|
+
|
|
6
10
|
const ACCOUNT_DIR = path.join(os.homedir(), "data", "electron");
|
|
7
11
|
|
|
8
12
|
function ensureAccountDir() {
|
|
@@ -175,40 +179,25 @@ module.exports = (registerTool) => {
|
|
|
175
179
|
{ tag: "Account" }
|
|
176
180
|
);
|
|
177
181
|
|
|
178
|
-
//
|
|
182
|
+
// 设置账户代理(持久化到 account-N.json + 立即应用到 session)
|
|
179
183
|
registerTool(
|
|
180
184
|
"set_account_proxy",
|
|
181
|
-
"
|
|
185
|
+
"为指定账户设置代理:持久化写入 account-N.json,并立即应用到该 sandbox session(之后新开窗口也会自动套用)。留空则清除。",
|
|
182
186
|
z.object({
|
|
183
187
|
accountIdx: z.number().describe("账户索引"),
|
|
184
|
-
proxy: z.string().optional().describe("代理地址,如
|
|
188
|
+
proxy: z.string().optional().describe("代理地址,如 socks5://127.0.0.1:20001,留空则清除代理"),
|
|
185
189
|
}),
|
|
186
190
|
async ({ accountIdx, proxy }) => {
|
|
187
191
|
try {
|
|
192
|
+
// 1) persist the desired proxy (canonical {url, enabled}) to the store
|
|
193
|
+
const view = profileStore.setProxy("electron", accountIdx, proxy || "");
|
|
194
|
+
// 2) apply to the live session now
|
|
188
195
|
const { session } = require("electron");
|
|
189
196
|
const ses = session.fromPartition(`persist:sandbox-${accountIdx}`);
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
content: [
|
|
195
|
-
{
|
|
196
|
-
type: "text",
|
|
197
|
-
text: JSON.stringify({ success: true, accountIdx, proxy }, null, 2),
|
|
198
|
-
},
|
|
199
|
-
],
|
|
200
|
-
};
|
|
201
|
-
} else {
|
|
202
|
-
await ses.setProxy({ proxyRules: "" });
|
|
203
|
-
return {
|
|
204
|
-
content: [
|
|
205
|
-
{
|
|
206
|
-
type: "text",
|
|
207
|
-
text: JSON.stringify({ success: true, accountIdx, proxy: "cleared" }, null, 2),
|
|
208
|
-
},
|
|
209
|
-
],
|
|
210
|
-
};
|
|
211
|
-
}
|
|
197
|
+
await ses.setProxy({ proxyRules: profileStore.proxyRules(view.proxy) });
|
|
198
|
+
return {
|
|
199
|
+
content: [{ type: "text", text: JSON.stringify({ success: true, accountIdx, proxy: view.proxy }, null, 2) }],
|
|
200
|
+
};
|
|
212
201
|
} catch (error) {
|
|
213
202
|
return {
|
|
214
203
|
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
@@ -218,4 +207,181 @@ module.exports = (registerTool) => {
|
|
|
218
207
|
},
|
|
219
208
|
{ tag: "Account" }
|
|
220
209
|
);
|
|
210
|
+
|
|
211
|
+
// ── Unified profile surface (mirrors agent-chrome verbs) ──────────────────
|
|
212
|
+
const toResult = (obj, isError = false) => ({
|
|
213
|
+
content: [{ type: "text", text: JSON.stringify(obj, null, 2) }],
|
|
214
|
+
...(isError ? { isError: true } : {}),
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
registerTool(
|
|
218
|
+
"electron_list_profiles",
|
|
219
|
+
"列出所有 Electron profile(从 account-N.json 配置读取,含 name/proxy/logins)",
|
|
220
|
+
z.object({}),
|
|
221
|
+
async () => {
|
|
222
|
+
try {
|
|
223
|
+
return toResult(profileStore.listProfiles("electron"));
|
|
224
|
+
} catch (e) {
|
|
225
|
+
return toResult({ error: e.message }, true);
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
{ tag: "Account" }
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
registerTool(
|
|
232
|
+
"electron_add_profile",
|
|
233
|
+
"新增一个 Electron profile:在 ~/data/electron/ 写入下一个 account-N.json(N=现有最大+1,至少为 1),返回统一视图",
|
|
234
|
+
z.object({ name: z.string().optional().describe("可选:profile 名称(metadata.name)") }),
|
|
235
|
+
async ({ name } = {}) => {
|
|
236
|
+
try {
|
|
237
|
+
ensureAccountDir();
|
|
238
|
+
const re = /^account-(\d+)\.json$/;
|
|
239
|
+
const nums = fs.readdirSync(ACCOUNT_DIR).map((f) => (re.exec(f) ? Number(re.exec(f)[1]) : null)).filter((n) => typeof n === "number");
|
|
240
|
+
const next = nums.length ? Math.max(...nums) + 1 : 1;
|
|
241
|
+
const file = path.join(ACCOUNT_DIR, `account-${next}.json`);
|
|
242
|
+
if (fs.existsSync(file)) return toResult({ error: `account-${next}.json already exists` }, true);
|
|
243
|
+
const now = new Date().toISOString();
|
|
244
|
+
const data = {
|
|
245
|
+
accountIdx: next, createdAt: now, updatedAt: now, windows: [],
|
|
246
|
+
metadata: { description: `Account ${next}`, ...(name ? { name } : {}), tags: [] },
|
|
247
|
+
proxy: { url: "", enabled: false }, logins: [],
|
|
248
|
+
};
|
|
249
|
+
fs.writeFileSync(file, JSON.stringify(data, null, 2));
|
|
250
|
+
return toResult({ success: true, accountIdx: next, profile: profileStore.getProfile("electron", next) });
|
|
251
|
+
} catch (e) {
|
|
252
|
+
return toResult({ error: e.message }, true);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
{ tag: "Account" }
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
registerTool(
|
|
259
|
+
"electron_get_profile",
|
|
260
|
+
"获取单个 Electron profile 的统一视图(name/proxy/logins/partition)",
|
|
261
|
+
z.object({ accountIdx: z.number().describe("账户索引") }),
|
|
262
|
+
async ({ accountIdx }) => {
|
|
263
|
+
try {
|
|
264
|
+
const p = profileStore.getProfile("electron", accountIdx);
|
|
265
|
+
return p ? toResult(p) : toResult({ error: `electron-${accountIdx} not found` }, true);
|
|
266
|
+
} catch (e) {
|
|
267
|
+
return toResult({ error: e.message }, true);
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
{ tag: "Account" }
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
registerTool(
|
|
274
|
+
"electron_probe_ip",
|
|
275
|
+
"探测某 Electron profile 当前出口 IP + 地区(经该 sandbox session 的代理),写入 config 并盖探测时间;可重复探测",
|
|
276
|
+
z.object({ accountIdx: z.number().describe("账户索引") }),
|
|
277
|
+
async ({ accountIdx }) => {
|
|
278
|
+
try {
|
|
279
|
+
const { session } = require("electron");
|
|
280
|
+
const ses = session.fromPartition(`persist:sandbox-${accountIdx}`);
|
|
281
|
+
// Ensure the profile's configured proxy is applied to the session before
|
|
282
|
+
// probing — otherwise (e.g. no window opened yet) it goes DIRECT and we'd
|
|
283
|
+
// report the machine's own IP instead of the profile's egress.
|
|
284
|
+
const prof = profileStore.getProfile("electron", accountIdx);
|
|
285
|
+
const rules = profileStore.proxyRules(prof && prof.proxy);
|
|
286
|
+
await ses.setProxy({ proxyRules: rules || "direct://" });
|
|
287
|
+
const info = await probeIpViaSession(ses);
|
|
288
|
+
const view = profileStore.setIpInfo("electron", accountIdx, info);
|
|
289
|
+
return toResult({ accountIdx, backend: "electron", proxy: rules || null, ipInfo: view.ipInfo });
|
|
290
|
+
} catch (e) {
|
|
291
|
+
return toResult({ error: e.message }, true);
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
{ tag: "Account" }
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
registerTool(
|
|
298
|
+
"electron_detect_logins",
|
|
299
|
+
"探测指定 Electron profile(persist:sandbox-N)当前 session 登录了哪些站点:读取该 partition 全部 cookie 按域名归并,标记带会话 cookie 的域名(cookie 在≠一定登录,仅强信号)",
|
|
300
|
+
z.object({ accountIdx: z.number().describe("账户索引") }),
|
|
301
|
+
async ({ accountIdx }) => {
|
|
302
|
+
try {
|
|
303
|
+
const { session } = require("electron");
|
|
304
|
+
const ses = session.fromPartition(`persist:sandbox-${accountIdx}`);
|
|
305
|
+
const cookies = await ses.cookies.get({});
|
|
306
|
+
return toResult({ accountIdx, backend: "electron", ...summarizeCookieLogins(cookies) });
|
|
307
|
+
} catch (e) {
|
|
308
|
+
return toResult({ error: e.message }, true);
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
{ tag: "Account" }
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
registerTool(
|
|
315
|
+
"electron_profile_login_add",
|
|
316
|
+
"记录某 Electron profile 登录了哪个平台账号(每个平台一条,重复平台覆盖)",
|
|
317
|
+
z.object({
|
|
318
|
+
accountIdx: z.number().describe("账户索引"),
|
|
319
|
+
platform: z.string().describe("平台,如 github / google / x"),
|
|
320
|
+
account: z.string().describe("账号标识,如 alice@x.com"),
|
|
321
|
+
}),
|
|
322
|
+
async ({ accountIdx, platform, account }) => {
|
|
323
|
+
try {
|
|
324
|
+
return toResult(profileStore.addLogin("electron", accountIdx, platform, account));
|
|
325
|
+
} catch (e) {
|
|
326
|
+
return toResult({ error: e.message }, true);
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
{ tag: "Account" }
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
registerTool(
|
|
333
|
+
"electron_profile_login_set",
|
|
334
|
+
"新增/更新某 Electron profile 的一条登录记录(富字段:地址/名称/用户名/邮箱/手机/2FA/备用邮箱/备注;按名称 name 归并,只覆盖传入的非空字段)",
|
|
335
|
+
z.object({
|
|
336
|
+
accountIdx: z.number().describe("账户索引"),
|
|
337
|
+
name: z.string().describe("名称=平台/站点名,如 抖音 / Google(归并键)"),
|
|
338
|
+
url: z.string().optional().describe("地址,如 https://www.douyin.com"),
|
|
339
|
+
username: z.string().optional().describe("用户名"),
|
|
340
|
+
email: z.string().optional().describe("邮箱"),
|
|
341
|
+
mobile: z.string().optional().describe("手机号"),
|
|
342
|
+
twofa: z.string().optional().describe("2FA(TOTP 秘钥或说明)"),
|
|
343
|
+
secondEmail: z.string().optional().describe("备用邮箱"),
|
|
344
|
+
note: z.string().optional().describe("备注"),
|
|
345
|
+
loginAt: z.string().optional().describe("登录时间 ISO(不传则首次记录时自动 now)"),
|
|
346
|
+
}),
|
|
347
|
+
async ({ accountIdx, ...login }) => {
|
|
348
|
+
try {
|
|
349
|
+
return toResult(profileStore.setLogin("electron", accountIdx, login));
|
|
350
|
+
} catch (e) {
|
|
351
|
+
return toResult({ error: e.message }, true);
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
{ tag: "Account" }
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
registerTool(
|
|
358
|
+
"electron_profile_login_rm",
|
|
359
|
+
"移除某 Electron profile 的某平台登录记录",
|
|
360
|
+
z.object({
|
|
361
|
+
accountIdx: z.number().describe("账户索引"),
|
|
362
|
+
platform: z.string().describe("平台"),
|
|
363
|
+
}),
|
|
364
|
+
async ({ accountIdx, platform }) => {
|
|
365
|
+
try {
|
|
366
|
+
return toResult(profileStore.removeLogin("electron", accountIdx, platform));
|
|
367
|
+
} catch (e) {
|
|
368
|
+
return toResult({ error: e.message }, true);
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
{ tag: "Account" }
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
registerTool(
|
|
375
|
+
"electron_profile_logins",
|
|
376
|
+
"列出某 Electron profile 已记录的平台登录",
|
|
377
|
+
z.object({ accountIdx: z.number().describe("账户索引") }),
|
|
378
|
+
async ({ accountIdx }) => {
|
|
379
|
+
try {
|
|
380
|
+
return toResult(profileStore.listLogins("electron", accountIdx));
|
|
381
|
+
} catch (e) {
|
|
382
|
+
return toResult({ error: e.message }, true);
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
{ tag: "Account" }
|
|
386
|
+
);
|
|
221
387
|
};
|
|
@@ -17,6 +17,9 @@ const { isPortOpen } = require("../utils/process-utils");
|
|
|
17
17
|
const { getVersion, getTargets, createTarget, activateTarget, callCdp } = require("../chrome/chrome-cdp-client");
|
|
18
18
|
const { resolveChromeDebuggerPort } = require("../chrome/debugger-port-resolver");
|
|
19
19
|
const { config } = require("../config");
|
|
20
|
+
const profileStore = require("../profiles/profile-store");
|
|
21
|
+
const { summarizeCookieLogins } = require("../utils/cookie-logins");
|
|
22
|
+
const { probeIpViaSession } = require("../utils/ip-probe");
|
|
20
23
|
|
|
21
24
|
const PRIVATE_CHROME_JSON = path.join(os.homedir(), "cicy-ai", "db", "chrome.json");
|
|
22
25
|
const PRIVATE_CHROME_TMP_DIR = path.join(os.homedir(), "chrome", "_tmp");
|
|
@@ -39,14 +42,24 @@ function expandHome(input) {
|
|
|
39
42
|
return input;
|
|
40
43
|
}
|
|
41
44
|
|
|
45
|
+
// tildify — collapse the home-dir prefix back to "~" for DISPLAY only.
|
|
46
|
+
// The machine has its own user (e.g. /Users/ton); surfacing that absolute path
|
|
47
|
+
// is non-portable for other people/agents, so anything shown/returned uses "~".
|
|
48
|
+
// NEVER feed a tildified path to Chrome's --user-data-dir or fs (use expandHome).
|
|
49
|
+
function tildify(input) {
|
|
50
|
+
if (typeof input !== "string" || input.length === 0) return input;
|
|
51
|
+
const home = os.homedir();
|
|
52
|
+
if (input === home) return "~";
|
|
53
|
+
if (input.startsWith(home + path.sep)) return "~/" + input.slice(home.length + 1);
|
|
54
|
+
return input;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Delegate to the shared normalizer (profile-store) so chrome + electron agree
|
|
58
|
+
// on proxy encoding. Accepts string | {enable,url} | {url,enabled}; returns the
|
|
59
|
+
// URL string when enabled, else null (the contract chrome-launcher expects).
|
|
42
60
|
function normalizePrivateProxy(proxyValue) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
if (proxyValue && typeof proxyValue === "object" && proxyValue.enable && proxyValue.url) {
|
|
47
|
-
return proxyValue.url;
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
61
|
+
const p = profileStore.normalizeProxy(proxyValue);
|
|
62
|
+
return p.enabled && p.url ? p.url : null;
|
|
50
63
|
}
|
|
51
64
|
|
|
52
65
|
function readPrivateChromeConfig() {
|
|
@@ -71,7 +84,7 @@ function listPrivateChromeEntries({ includeHidden = false } = {}) {
|
|
|
71
84
|
const entries = Object.entries(data)
|
|
72
85
|
.filter(([k]) => (includeHidden ? true : !k.startsWith("__")))
|
|
73
86
|
.map(([profileKey, entry]) => {
|
|
74
|
-
const m = /^
|
|
87
|
+
const m = /^profile_(\d+)$/.exec(profileKey);
|
|
75
88
|
const accountIdx = m ? Number(m[1]) : null;
|
|
76
89
|
return { profileKey, accountIdx, entry };
|
|
77
90
|
})
|
|
@@ -87,7 +100,7 @@ function listPrivateChromeEntries({ includeHidden = false } = {}) {
|
|
|
87
100
|
|
|
88
101
|
function getPrivateChromeEntryByAccountIdx(accountIdx) {
|
|
89
102
|
const data = readPrivateChromeConfig();
|
|
90
|
-
const profileKey = `
|
|
103
|
+
const profileKey = `profile_${accountIdx}`;
|
|
91
104
|
const entry = data[profileKey] || null;
|
|
92
105
|
if (!entry) return null;
|
|
93
106
|
return { profileKey, accountIdx, entry };
|
|
@@ -144,7 +157,7 @@ function normalizeEffectiveChromeProfile(accountIdx, effectiveChromeProfile) {
|
|
|
144
157
|
const platform = parsed.platform && typeof parsed.platform === "object" ? parsed.platform : {};
|
|
145
158
|
|
|
146
159
|
return {
|
|
147
|
-
profileKey: `
|
|
160
|
+
profileKey: `profile_${accountIdx}`,
|
|
148
161
|
accountIdx,
|
|
149
162
|
gmail,
|
|
150
163
|
orgPath,
|
|
@@ -279,7 +292,7 @@ async function launchOrActivateProfile({
|
|
|
279
292
|
effectiveChromeProfile,
|
|
280
293
|
}) {
|
|
281
294
|
const registry = getChromeRuntimeRegistry();
|
|
282
|
-
const profileKey = `
|
|
295
|
+
const profileKey = `profile_${accountIdx}`;
|
|
283
296
|
|
|
284
297
|
let normalized;
|
|
285
298
|
let profileSource;
|
|
@@ -290,7 +303,7 @@ async function launchOrActivateProfile({
|
|
|
290
303
|
const cfg = getPrivateChromeEntryByAccountIdx(accountIdx);
|
|
291
304
|
if (!cfg) {
|
|
292
305
|
throw new Error(
|
|
293
|
-
`Missing chrome profile for
|
|
306
|
+
`Missing chrome profile for profile_${accountIdx}. Use master dispatch or pass effectiveChromeProfile.`
|
|
294
307
|
);
|
|
295
308
|
}
|
|
296
309
|
normalized = normalizePrivateChromeEntry(cfg.profileKey, cfg.accountIdx, cfg.entry);
|
|
@@ -308,6 +321,8 @@ async function launchOrActivateProfile({
|
|
|
308
321
|
const effectiveUserDataDirRoot =
|
|
309
322
|
normalized.expanded.rpaDir ||
|
|
310
323
|
getDefaultUserDataDirRoot(accountIdx, config.chromeUserDataRoot || DEFAULT_USER_DATA_BASE_ROOT);
|
|
324
|
+
// ~-collapsed form for anything returned/stored for display (portable across users)
|
|
325
|
+
const displayUserDataDir = tildify(effectiveUserDataDirRoot);
|
|
311
326
|
|
|
312
327
|
// Script parity: if /json/version reachable => activate first page target and return reused
|
|
313
328
|
const liveStatus = await probeChromeDebugger(effectivePort);
|
|
@@ -323,7 +338,7 @@ async function launchOrActivateProfile({
|
|
|
323
338
|
status: "running",
|
|
324
339
|
debuggerPort: effectivePort,
|
|
325
340
|
proxy: effectiveProxy || null,
|
|
326
|
-
userDataDirRoot:
|
|
341
|
+
userDataDirRoot: displayUserDataDir,
|
|
327
342
|
profileDirectory: getProfileDirectory(accountIdx),
|
|
328
343
|
url: url || null,
|
|
329
344
|
webSocketDebuggerUrl: liveStatus.webSocketDebuggerUrl || null,
|
|
@@ -339,7 +354,7 @@ async function launchOrActivateProfile({
|
|
|
339
354
|
gmail: normalized.gmail,
|
|
340
355
|
port: effectivePort,
|
|
341
356
|
proxy: effectiveProxy || null,
|
|
342
|
-
userDataDirRoot:
|
|
357
|
+
userDataDirRoot: displayUserDataDir,
|
|
343
358
|
runtime: nextRuntime,
|
|
344
359
|
liveStatus,
|
|
345
360
|
targetsPreview: buildTargetsPreview(targets),
|
|
@@ -363,7 +378,7 @@ async function launchOrActivateProfile({
|
|
|
363
378
|
debuggerPort: effectivePort,
|
|
364
379
|
proxy: effectiveProxy || null,
|
|
365
380
|
chromeBinary: config.chromeBinary || null,
|
|
366
|
-
userDataDirRoot:
|
|
381
|
+
userDataDirRoot: displayUserDataDir,
|
|
367
382
|
profileDirectory: getProfileDirectory(accountIdx),
|
|
368
383
|
url: url || null,
|
|
369
384
|
error: null,
|
|
@@ -385,7 +400,7 @@ async function launchOrActivateProfile({
|
|
|
385
400
|
debuggerPort: launched.debuggerPort,
|
|
386
401
|
proxy: launched.proxy,
|
|
387
402
|
chromeBinary: launched.chromeBinary,
|
|
388
|
-
userDataDirRoot: launched.userDataDirRoot,
|
|
403
|
+
userDataDirRoot: tildify(launched.userDataDirRoot),
|
|
389
404
|
profileDirectory: launched.profileDirectory,
|
|
390
405
|
url: launched.url,
|
|
391
406
|
webSocketDebuggerUrl: launched.webSocketDebuggerUrl,
|
|
@@ -406,7 +421,7 @@ async function launchOrActivateProfile({
|
|
|
406
421
|
gmail: normalized.gmail,
|
|
407
422
|
port: effectivePort,
|
|
408
423
|
proxy: effectiveProxy || null,
|
|
409
|
-
userDataDirRoot:
|
|
424
|
+
userDataDirRoot: displayUserDataDir,
|
|
410
425
|
runtime: nextRuntime,
|
|
411
426
|
activatedTargetId,
|
|
412
427
|
targetsPreview: buildTargetsPreview(targets),
|
|
@@ -440,12 +455,16 @@ function registerChromeTools(registerTool) {
|
|
|
440
455
|
profileKey,
|
|
441
456
|
accountIdx,
|
|
442
457
|
gmail: normalized.gmail,
|
|
458
|
+
note: normalized.note,
|
|
443
459
|
orgPath: normalized.orgPath,
|
|
444
460
|
rpaDir: normalized.rpaDir,
|
|
445
461
|
port,
|
|
446
462
|
proxy: normalized.proxyUrl,
|
|
447
463
|
proxyRaw: normalized.proxy,
|
|
448
464
|
platform: normalized.platform,
|
|
465
|
+
// parity with electron_list_profiles so the panel renders the same row
|
|
466
|
+
ipInfo: profileStore.normalizeIpInfo(entry.ipInfo),
|
|
467
|
+
logins: (Array.isArray(entry.logins) ? entry.logins : []).map(profileStore.normalizeLogin),
|
|
449
468
|
runtime,
|
|
450
469
|
liveStatus,
|
|
451
470
|
});
|
|
@@ -528,14 +547,14 @@ function registerChromeTools(registerTool) {
|
|
|
528
547
|
"chrome_get_profile",
|
|
529
548
|
"获取指定 accountIdx 的 profile:privateConfig + runtime + liveStatus(脚本心智)",
|
|
530
549
|
z.object({
|
|
531
|
-
accountIdx: z.number().describe("账户索引,映射到 ~/cicy-ai/db/chrome.json 的
|
|
550
|
+
accountIdx: z.number().describe("账户索引,映射到 ~/cicy-ai/db/chrome.json 的 profile_<idx>"),
|
|
532
551
|
}),
|
|
533
552
|
async ({ accountIdx }) => {
|
|
534
553
|
const registry = getChromeRuntimeRegistry();
|
|
535
554
|
const cfg = getPrivateChromeEntryByAccountIdx(accountIdx);
|
|
536
555
|
if (!cfg) {
|
|
537
556
|
return toToolResult(
|
|
538
|
-
{ error: `Missing chrome.json entry:
|
|
557
|
+
{ error: `Missing chrome.json entry: profile_${accountIdx}` },
|
|
539
558
|
{ isError: true }
|
|
540
559
|
);
|
|
541
560
|
}
|
|
@@ -587,7 +606,7 @@ function registerChromeTools(registerTool) {
|
|
|
587
606
|
|
|
588
607
|
registerTool(
|
|
589
608
|
"chrome_add_profile",
|
|
590
|
-
"新增账号(等价于 chrome-rpa.sh add):从 ~/chrome/__tmp 创建 ~/chrome/
|
|
609
|
+
"新增账号(等价于 chrome-rpa.sh add):从 ~/chrome/__tmp 创建 ~/chrome/profile_N 并写回 ~/cicy-ai/db/chrome.json",
|
|
591
610
|
z.object({
|
|
592
611
|
gmail: z.string().optional().describe("可选:新账号 gmail"),
|
|
593
612
|
orgPath: z.string().optional().describe("可选:orgPath(默认 Profile 9)"),
|
|
@@ -596,13 +615,13 @@ function registerChromeTools(registerTool) {
|
|
|
596
615
|
async ({ gmail, orgPath, launchAfterCreate } = {}) => {
|
|
597
616
|
const data = readPrivateChromeConfig();
|
|
598
617
|
const nums = Object.keys(data)
|
|
599
|
-
.map((k) => (/^
|
|
618
|
+
.map((k) => (/^profile_(\d+)$/.exec(k) ? Number(/^profile_(\d+)$/.exec(k)[1]) : null))
|
|
600
619
|
.filter((n) => typeof n === "number");
|
|
601
620
|
const nextNum = nums.length ? Math.max(...nums) + 1 : 1;
|
|
602
621
|
|
|
603
|
-
const profileKey = `
|
|
622
|
+
const profileKey = `profile_${nextNum}`;
|
|
604
623
|
const port = 11000 + nextNum;
|
|
605
|
-
const rpaDirTilde = `~/chrome
|
|
624
|
+
const rpaDirTilde = `~/chrome/profile_${nextNum}`;
|
|
606
625
|
const rpaDir = expandHome(rpaDirTilde);
|
|
607
626
|
|
|
608
627
|
if (fs.existsSync(rpaDir)) {
|
|
@@ -652,23 +671,92 @@ function registerChromeTools(registerTool) {
|
|
|
652
671
|
|
|
653
672
|
registerTool(
|
|
654
673
|
"chrome_set_profile_proxy",
|
|
655
|
-
"设置 ~/cicy-ai/db/chrome.json 中指定 accountIdx 的 proxy
|
|
674
|
+
"设置 ~/cicy-ai/db/chrome.json 中指定 accountIdx 的 proxy(持久化为 {url,enabled};下次启动生效)",
|
|
656
675
|
z.object({
|
|
657
676
|
accountIdx: z.number().describe("账户索引"),
|
|
658
677
|
proxy: z.string().optional().describe("代理 URL;留空则清空"),
|
|
659
678
|
}),
|
|
660
679
|
async ({ accountIdx, proxy } = {}) => {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
680
|
+
try {
|
|
681
|
+
const view = profileStore.setProxy("chrome", accountIdx, proxy || "");
|
|
682
|
+
return toToolResult({ success: true, profileKey: `profile_${accountIdx}`, profile: view });
|
|
683
|
+
} catch (e) {
|
|
684
|
+
return toToolResult({ error: e.message }, { isError: true });
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
{ tag: "Chrome" }
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
registerTool(
|
|
691
|
+
"chrome_profile_login_add",
|
|
692
|
+
"记录某 Chrome profile 登录了哪个平台账号(每个平台一条,重复平台覆盖)",
|
|
693
|
+
z.object({
|
|
694
|
+
accountIdx: z.number().describe("账户索引"),
|
|
695
|
+
platform: z.string().describe("平台,如 github / google / x"),
|
|
696
|
+
account: z.string().describe("账号标识,如 alice@x.com"),
|
|
697
|
+
}),
|
|
698
|
+
async ({ accountIdx, platform, account } = {}) => {
|
|
699
|
+
try {
|
|
700
|
+
return toToolResult(profileStore.addLogin("chrome", accountIdx, platform, account));
|
|
701
|
+
} catch (e) {
|
|
702
|
+
return toToolResult({ error: e.message }, { isError: true });
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
{ tag: "Chrome" }
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
registerTool(
|
|
709
|
+
"chrome_profile_login_set",
|
|
710
|
+
"新增/更新某 Chrome profile 的一条登录记录(富字段:地址/名称/用户名/邮箱/手机/2FA/备用邮箱/备注;按名称 name 归并,只覆盖传入的非空字段)",
|
|
711
|
+
z.object({
|
|
712
|
+
accountIdx: z.number().describe("账户索引"),
|
|
713
|
+
name: z.string().describe("名称=平台/站点名,如 抖音 / Google(归并键)"),
|
|
714
|
+
url: z.string().optional().describe("地址,如 https://www.douyin.com"),
|
|
715
|
+
username: z.string().optional().describe("用户名"),
|
|
716
|
+
email: z.string().optional().describe("邮箱"),
|
|
717
|
+
mobile: z.string().optional().describe("手机号"),
|
|
718
|
+
twofa: z.string().optional().describe("2FA(TOTP 秘钥或说明)"),
|
|
719
|
+
secondEmail: z.string().optional().describe("备用邮箱"),
|
|
720
|
+
note: z.string().optional().describe("备注"),
|
|
721
|
+
loginAt: z.string().optional().describe("登录时间 ISO(不传则首次记录时自动 now)"),
|
|
722
|
+
}),
|
|
723
|
+
async ({ accountIdx, ...login } = {}) => {
|
|
724
|
+
try {
|
|
725
|
+
return toToolResult(profileStore.setLogin("chrome", accountIdx, login));
|
|
726
|
+
} catch (e) {
|
|
727
|
+
return toToolResult({ error: e.message }, { isError: true });
|
|
728
|
+
}
|
|
729
|
+
},
|
|
730
|
+
{ tag: "Chrome" }
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
registerTool(
|
|
734
|
+
"chrome_profile_login_rm",
|
|
735
|
+
"移除某 Chrome profile 的某平台登录记录",
|
|
736
|
+
z.object({
|
|
737
|
+
accountIdx: z.number().describe("账户索引"),
|
|
738
|
+
platform: z.string().describe("平台"),
|
|
739
|
+
}),
|
|
740
|
+
async ({ accountIdx, platform } = {}) => {
|
|
741
|
+
try {
|
|
742
|
+
return toToolResult(profileStore.removeLogin("chrome", accountIdx, platform));
|
|
743
|
+
} catch (e) {
|
|
744
|
+
return toToolResult({ error: e.message }, { isError: true });
|
|
745
|
+
}
|
|
746
|
+
},
|
|
747
|
+
{ tag: "Chrome" }
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
registerTool(
|
|
751
|
+
"chrome_profile_logins",
|
|
752
|
+
"列出某 Chrome profile 已记录的平台登录",
|
|
753
|
+
z.object({ accountIdx: z.number().describe("账户索引") }),
|
|
754
|
+
async ({ accountIdx } = {}) => {
|
|
755
|
+
try {
|
|
756
|
+
return toToolResult(profileStore.listLogins("chrome", accountIdx));
|
|
757
|
+
} catch (e) {
|
|
758
|
+
return toToolResult({ error: e.message }, { isError: true });
|
|
665
759
|
}
|
|
666
|
-
data[key] = {
|
|
667
|
-
...data[key],
|
|
668
|
-
proxy: typeof proxy === "string" ? proxy : "",
|
|
669
|
-
};
|
|
670
|
-
writePrivateChromeConfig(data);
|
|
671
|
-
return toToolResult({ success: true, profileKey: key, privateConfig: data[key] });
|
|
672
760
|
},
|
|
673
761
|
{ tag: "Chrome" }
|
|
674
762
|
);
|
|
@@ -696,7 +784,7 @@ function registerChromeTools(registerTool) {
|
|
|
696
784
|
}),
|
|
697
785
|
async ({ accountIdx, note, accounts } = {}) => {
|
|
698
786
|
const data = readPrivateChromeConfig();
|
|
699
|
-
const key = `
|
|
787
|
+
const key = `profile_${accountIdx}`;
|
|
700
788
|
if (!data[key]) {
|
|
701
789
|
return toToolResult({ error: `Missing chrome.json entry: ${key}` }, { isError: true });
|
|
702
790
|
}
|
|
@@ -775,7 +863,7 @@ function registerChromeTools(registerTool) {
|
|
|
775
863
|
const cfg = getPrivateChromeEntryByAccountIdx(accountIdx);
|
|
776
864
|
const { debuggerPort: port } = resolveChromeDebuggerPort(accountIdx, {
|
|
777
865
|
registry,
|
|
778
|
-
chromeConfig: cfg ? { [`
|
|
866
|
+
chromeConfig: cfg ? { [`profile_${accountIdx}`]: cfg.entry } : null,
|
|
779
867
|
});
|
|
780
868
|
|
|
781
869
|
if (!port) {
|
|
@@ -813,7 +901,7 @@ function registerChromeTools(registerTool) {
|
|
|
813
901
|
const cfg = getPrivateChromeEntryByAccountIdx(accountIdx);
|
|
814
902
|
const { debuggerPort: port } = resolveChromeDebuggerPort(accountIdx, {
|
|
815
903
|
registry,
|
|
816
|
-
chromeConfig: cfg ? { [`
|
|
904
|
+
chromeConfig: cfg ? { [`profile_${accountIdx}`]: cfg.entry } : null,
|
|
817
905
|
});
|
|
818
906
|
|
|
819
907
|
if (!port) {
|
|
@@ -841,6 +929,54 @@ function registerChromeTools(registerTool) {
|
|
|
841
929
|
},
|
|
842
930
|
{ tag: "Chrome" }
|
|
843
931
|
);
|
|
932
|
+
|
|
933
|
+
registerTool(
|
|
934
|
+
"chrome_probe_ip",
|
|
935
|
+
"探测某 Chrome profile 当前出口 IP + 地区(经其配置代理,用临时 Electron session,无需启动 Chrome),写入 config 并盖探测时间;可重复探测",
|
|
936
|
+
z.object({ accountIdx: z.number().describe("账户索引") }),
|
|
937
|
+
async ({ accountIdx }) => {
|
|
938
|
+
try {
|
|
939
|
+
const { session } = require("electron");
|
|
940
|
+
const view = profileStore.getProfile("chrome", accountIdx);
|
|
941
|
+
const rules = profileStore.proxyRules(view && view.proxy);
|
|
942
|
+
const ses = session.fromPartition(`chrome-ipprobe-${accountIdx}`, { cache: false });
|
|
943
|
+
await ses.setProxy({ proxyRules: rules || "direct://" });
|
|
944
|
+
const info = await probeIpViaSession(ses);
|
|
945
|
+
const saved = profileStore.setIpInfo("chrome", accountIdx, info);
|
|
946
|
+
return toToolResult({ accountIdx, backend: "chrome", proxy: rules || null, ipInfo: saved.ipInfo });
|
|
947
|
+
} catch (e) {
|
|
948
|
+
return toToolResult({ error: e.message }, { isError: true });
|
|
949
|
+
}
|
|
950
|
+
},
|
|
951
|
+
{ tag: "Chrome" }
|
|
952
|
+
);
|
|
953
|
+
|
|
954
|
+
registerTool(
|
|
955
|
+
"chrome_detect_logins",
|
|
956
|
+
"探测指定 Chrome profile 当前 session 登录了哪些站点:读取全部 cookie 按域名归并,标记带会话 cookie 的域名(cookie 在≠一定登录,仅强信号;profile 需在运行)",
|
|
957
|
+
z.object({ accountIdx: z.number().describe("账户索引") }),
|
|
958
|
+
async ({ accountIdx }) => {
|
|
959
|
+
const registry = getChromeRuntimeRegistry();
|
|
960
|
+
const cfg = getPrivateChromeEntryByAccountIdx(accountIdx);
|
|
961
|
+
const { debuggerPort: port } = resolveChromeDebuggerPort(accountIdx, {
|
|
962
|
+
registry,
|
|
963
|
+
chromeConfig: cfg ? { [`profile_${accountIdx}`]: cfg.entry } : null,
|
|
964
|
+
});
|
|
965
|
+
if (!port) {
|
|
966
|
+
return toToolResult(
|
|
967
|
+
{ error: `Missing debuggerPort for accountIdx=${accountIdx}(profile 未启动?先 launch)` },
|
|
968
|
+
{ isError: true }
|
|
969
|
+
);
|
|
970
|
+
}
|
|
971
|
+
try {
|
|
972
|
+
const { cookies } = await callCdp({ debuggerPort: port, method: "Storage.getCookies", params: {} });
|
|
973
|
+
return toToolResult({ accountIdx, backend: "chrome", ...summarizeCookieLogins(cookies) });
|
|
974
|
+
} catch (error) {
|
|
975
|
+
return toToolResult({ error: error.message }, { isError: true });
|
|
976
|
+
}
|
|
977
|
+
},
|
|
978
|
+
{ tag: "Chrome" }
|
|
979
|
+
);
|
|
844
980
|
}
|
|
845
981
|
|
|
846
982
|
module.exports = registerChromeTools;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const { z } = require("zod");
|
|
2
|
+
|
|
3
|
+
// get_device_info — return this machine's identity + detected network/locale
|
|
4
|
+
// info, read from global.json (deviceInfo block + deviceId). Instant, no
|
|
5
|
+
// network: the egress-IP/region/system-language detection runs at startup
|
|
6
|
+
// (src/main.js → cloud-client.detectAndPersistDeviceInfo) and persists here.
|
|
7
|
+
// The cicy-code app SPA calls this over electronRPC and forwards it on the
|
|
8
|
+
// chat-WS register so the cicy-code backend (and agent-webpage `clients`) can
|
|
9
|
+
// see each desktop client's ip / region / system language / deviceId.
|
|
10
|
+
module.exports = (registerTool) => {
|
|
11
|
+
registerTool(
|
|
12
|
+
"get_device_info",
|
|
13
|
+
"返回本机 deviceId + 出口公网 IP + IP 所在地区 + 系统语言(读 global.json 的 deviceInfo,秒回,不发网络请求)",
|
|
14
|
+
z.object({}),
|
|
15
|
+
async () => {
|
|
16
|
+
try {
|
|
17
|
+
const info = require("../cloud/cloud-client").getDeviceInfo();
|
|
18
|
+
return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
|
|
19
|
+
} catch (error) {
|
|
20
|
+
return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{ tag: "Device" }
|
|
24
|
+
);
|
|
25
|
+
};
|
package/src/tools/index.js
CHANGED
|
@@ -13,9 +13,11 @@ module.exports = [
|
|
|
13
13
|
require("./system-tools"),
|
|
14
14
|
require("./automation-tools"),
|
|
15
15
|
require("./account-tools"),
|
|
16
|
+
require("./device-tools"),
|
|
16
17
|
require("./download-tools"),
|
|
17
18
|
require("./ipc-bridge"),
|
|
18
19
|
require("./hook-gemini"),
|
|
19
20
|
require("./chrome-tools"),
|
|
21
|
+
require("./tab-browser-tools"),
|
|
20
22
|
require("./list-tools"),
|
|
21
23
|
];
|