vcorp-chrome 2.0.0 → 2.2.0
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/docs//346/241/214/351/235/242/345/256/214/345/205/250/346/250/241/345/274/217-/344/272/244/346/216/245.md +128 -0
- package/extension/cloud-policy.js +9 -0
- package/extension/cloud.js +90 -4
- package/extension/desktop-policy.js +19 -13
- package/extension/manifest.json +41 -41
- package/extension/panel.html +105 -46
- package/extension/panel.js +55 -62
- package/native-host/full.js +217 -0
- package/native-host/host.js +36 -3
- package/package.json +53 -53
- package/src/cli.js +16 -1
- package/src/lib/paths.js +15 -0
package/native-host/host.js
CHANGED
|
@@ -6,14 +6,18 @@
|
|
|
6
6
|
// run:把扩展审过的参数再审一遍,交给 agent-desktop,回它的信封
|
|
7
7
|
// 扩展那一侧已经过了 desktop-policy.js;这里再审是因为扩展本身也可能出错,
|
|
8
8
|
// 而真实输入(--headed)、截图、剪贴板读取这些不该因为一处疏漏就对云端打开。
|
|
9
|
+
//
|
|
10
|
+
// 完全模式(用户在本机 `vcorp-chrome desktop full on`):run 不再查白名单,
|
|
11
|
+
// 另有 shell / file 两种操作(见 full.js);大回包分页,扩展用 page 取回。
|
|
9
12
|
import path from 'node:path';
|
|
10
13
|
import { fileURLToPath } from 'node:url';
|
|
11
14
|
import { runAgentDesktop, findBinary } from '../src/lib/desktop.js';
|
|
12
|
-
import { machineId, desktopDeviceId } from '../src/lib/paths.js';
|
|
15
|
+
import { machineId, desktopDeviceId, fullMode } from '../src/lib/paths.js';
|
|
13
16
|
import {
|
|
14
17
|
DESKTOP_CMDS, REF_RE, filterWindows, checkObserved, recordOf, isBrowserMainTitle,
|
|
15
18
|
} from '../extension/desktop-policy.js';
|
|
16
19
|
import { VERSION } from '../src/lib/version.js';
|
|
20
|
+
import { runShell, fileOp, pageOut, pageRead } from './full.js';
|
|
17
21
|
|
|
18
22
|
// Chrome 收宿主回包的上限是 1MB,留出 JSON 外壳的余量
|
|
19
23
|
const REPLY_MAX = 1000 * 1000;
|
|
@@ -69,8 +73,17 @@ function readMessage() {
|
|
|
69
73
|
});
|
|
70
74
|
}
|
|
71
75
|
|
|
72
|
-
|
|
76
|
+
// 完全模式下 argv 只查形状:是字符串数组、没有 NUL
|
|
77
|
+
export function fullArgvOk(argv) {
|
|
78
|
+
return Array.isArray(argv) && argv.length > 0 && argv.length <= 200
|
|
79
|
+
&& argv.every((a) => typeof a === 'string' && a.length <= 100000 && !a.includes('\0'));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function reply(obj, { paging = false } = {}) {
|
|
73
83
|
let body = Buffer.from(JSON.stringify(obj), 'utf8');
|
|
84
|
+
if (body.length > REPLY_MAX && paging) {
|
|
85
|
+
try { body = Buffer.from(JSON.stringify(pageOut(body)), 'utf8'); } catch { /* 落不了盘就照旧报太大 */ }
|
|
86
|
+
}
|
|
74
87
|
if (body.length > REPLY_MAX) {
|
|
75
88
|
body = Buffer.from(JSON.stringify({
|
|
76
89
|
ok: false,
|
|
@@ -89,17 +102,37 @@ function reply(obj) {
|
|
|
89
102
|
|
|
90
103
|
async function main() {
|
|
91
104
|
const msg = await readMessage();
|
|
105
|
+
const full = fullMode();
|
|
92
106
|
if (msg?.op === 'hello') {
|
|
93
107
|
const machine = machineId();
|
|
94
108
|
const probe = await runAgentDesktop(['list-apps'], { timeoutMs: 15000 });
|
|
95
109
|
return reply({
|
|
96
110
|
ok: true,
|
|
97
|
-
device: { id: desktopDeviceId(machine), kind: 'desktop', machine, caps: { platform: process.platform, version: VERSION } },
|
|
111
|
+
device: { id: desktopDeviceId(machine), kind: 'desktop', machine, caps: { platform: process.platform, version: VERSION, full } },
|
|
98
112
|
ready: !!probe.ok,
|
|
113
|
+
full,
|
|
99
114
|
...(probe.ok ? {} : { error: probe.error }),
|
|
100
115
|
binary: findBinary(),
|
|
101
116
|
});
|
|
102
117
|
}
|
|
118
|
+
// 分页回包的后续段:不管完全模式,取的只是本宿主刚写下的那份
|
|
119
|
+
if (msg?.op === 'page') return reply(pageRead(msg.id, msg.offset));
|
|
120
|
+
// 扩展每条桌面命令前问一次:开关只在这台电脑上,扩展不缓存它
|
|
121
|
+
if (msg?.op === 'mode') return reply({ ok: true, full });
|
|
122
|
+
const offFull = () => reply(denied('这台电脑没有打开桌面完全模式。请用户在这台电脑的终端运行 npx vcorp-chrome desktop full on'));
|
|
123
|
+
if (msg?.op === 'shell') {
|
|
124
|
+
if (!full) return offFull();
|
|
125
|
+
return reply(await runShell(msg.params || {}), { paging: true });
|
|
126
|
+
}
|
|
127
|
+
if (msg?.op === 'file') {
|
|
128
|
+
if (!full) return offFull();
|
|
129
|
+
return reply(await fileOp(msg.params || {}), { paging: true });
|
|
130
|
+
}
|
|
131
|
+
if (msg?.op === 'run' && full) {
|
|
132
|
+
if (!fullArgvOk(msg.argv)) return reply(denied('argv 不合法'));
|
|
133
|
+
const timeoutMs = Math.min(Math.max(Number(msg.timeoutMs) || 30000, 1000), 7 * 60 * 1000);
|
|
134
|
+
return reply(await runAgentDesktop(msg.argv, { timeoutMs }), { paging: true });
|
|
135
|
+
}
|
|
103
136
|
if (msg?.op === 'run') {
|
|
104
137
|
const why = recheck(msg.argv);
|
|
105
138
|
if (why) return reply(denied(why));
|
package/package.json
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "vcorp-chrome",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "让任何 AI agent 操控你自己的 Chrome——带着全部登录态。MCP + Chrome 扩展,Claude Code / Codex / Cursor / Gemini CLI 通用。",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"vcorp-chrome": "src/cli.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"src",
|
|
11
|
-
"extension",
|
|
12
|
-
"native-host",
|
|
13
|
-
"docs",
|
|
14
|
-
"README.md"
|
|
15
|
-
],
|
|
16
|
-
"engines": {
|
|
17
|
-
"node": ">=20"
|
|
18
|
-
},
|
|
19
|
-
"scripts": {
|
|
20
|
-
"mcp": "node src/cli.js mcp",
|
|
21
|
-
"bridge": "node src/cli.js bridge --foreground",
|
|
22
|
-
"doctor": "node src/cli.js doctor",
|
|
23
|
-
"build:loads": "node scripts/build-loads.mjs",
|
|
24
|
-
"test": "node --test test/protocol.test.js test/mcp.test.js test/fetch-pages.test.js test/liveness.test.js test/redact.test.js test/audit-redact.test.js test/learnings.test.js test/risk.test.js test/identity.test.js test/host.test.js test/presence.test.js test/act-script.test.js test/multi-instance.test.js test/compat.test.js test/cloud-policy.test.js test/loads.test.js test/desktop-policy.test.js",
|
|
25
|
-
"test:live": "node --test test/scenarios.test.js"
|
|
26
|
-
},
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
29
|
-
"ws": "^8.18.3"
|
|
30
|
-
},
|
|
31
|
-
"keywords": [
|
|
32
|
-
"mcp",
|
|
33
|
-
"chrome",
|
|
34
|
-
"browser-automation",
|
|
35
|
-
"claude-code",
|
|
36
|
-
"codex",
|
|
37
|
-
"cursor",
|
|
38
|
-
"ai-agent"
|
|
39
|
-
],
|
|
40
|
-
"license": "MIT",
|
|
41
|
-
"author": "Wizard",
|
|
42
|
-
"repository": {
|
|
43
|
-
"type": "git",
|
|
44
|
-
"url": "git+https://github.com/wizd/huashu-chrome.git"
|
|
45
|
-
},
|
|
46
|
-
"devDependencies": {
|
|
47
|
-
"esbuild": "^0.24.2",
|
|
48
|
-
"jszip": "^3.10.1"
|
|
49
|
-
},
|
|
50
|
-
"optionalDependencies": {
|
|
51
|
-
"agent-desktop": "1.0.2"
|
|
52
|
-
}
|
|
53
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "vcorp-chrome",
|
|
3
|
+
"version": "2.2.0",
|
|
4
|
+
"description": "让任何 AI agent 操控你自己的 Chrome——带着全部登录态。MCP + Chrome 扩展,Claude Code / Codex / Cursor / Gemini CLI 通用。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vcorp-chrome": "src/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"src",
|
|
11
|
+
"extension",
|
|
12
|
+
"native-host",
|
|
13
|
+
"docs",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=20"
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"mcp": "node src/cli.js mcp",
|
|
21
|
+
"bridge": "node src/cli.js bridge --foreground",
|
|
22
|
+
"doctor": "node src/cli.js doctor",
|
|
23
|
+
"build:loads": "node scripts/build-loads.mjs",
|
|
24
|
+
"test": "node --test test/protocol.test.js test/mcp.test.js test/fetch-pages.test.js test/liveness.test.js test/redact.test.js test/audit-redact.test.js test/learnings.test.js test/risk.test.js test/identity.test.js test/host.test.js test/presence.test.js test/act-script.test.js test/multi-instance.test.js test/compat.test.js test/cloud-policy.test.js test/loads.test.js test/desktop-policy.test.js test/desktop-full.test.js",
|
|
25
|
+
"test:live": "node --test test/scenarios.test.js"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.22.0",
|
|
29
|
+
"ws": "^8.18.3"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"mcp",
|
|
33
|
+
"chrome",
|
|
34
|
+
"browser-automation",
|
|
35
|
+
"claude-code",
|
|
36
|
+
"codex",
|
|
37
|
+
"cursor",
|
|
38
|
+
"ai-agent"
|
|
39
|
+
],
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"author": "Wizard",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/wizd/huashu-chrome.git"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"esbuild": "^0.24.2",
|
|
48
|
+
"jszip": "^3.10.1"
|
|
49
|
+
},
|
|
50
|
+
"optionalDependencies": {
|
|
51
|
+
"agent-desktop": "1.0.2"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/cli.js
CHANGED
|
@@ -117,9 +117,23 @@ switch (cmd) {
|
|
|
117
117
|
const r = nat.uninstall();
|
|
118
118
|
console.log(r.removed.length ? `已移除:\n ${r.removed.join('\n ')}` : '本来就没有登记。');
|
|
119
119
|
} else if (sub === 'status') {
|
|
120
|
+
const { fullMode } = await import('./lib/paths.js');
|
|
120
121
|
for (const l of (await nat.status()).lines) console.log(` ${l}`);
|
|
122
|
+
console.log(` 完全模式:${fullMode() ? '开(云端任务可以跑命令、读写文件、用全部桌面命令)' : '关'}`);
|
|
123
|
+
} else if (sub === 'full') {
|
|
124
|
+
const { setFullMode, FULL_FILE } = await import('./lib/paths.js');
|
|
125
|
+
const v = process.argv[4];
|
|
126
|
+
if (v !== 'on' && v !== 'off') {
|
|
127
|
+
console.log('用法:vcorp-chrome desktop full on | off');
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
if (setFullMode(v === 'on')) {
|
|
131
|
+
console.log(`已打开桌面完全模式(${FULL_FILE})。`);
|
|
132
|
+
console.log('配对服务器上的任务现在可以在这台电脑上跑任意命令、读写任意文件、操作任何应用。');
|
|
133
|
+
console.log('关掉:npx vcorp-chrome desktop full off');
|
|
134
|
+
} else console.log('已关闭桌面完全模式。');
|
|
121
135
|
} else {
|
|
122
|
-
console.log('用法:vcorp-chrome desktop install | uninstall | status');
|
|
136
|
+
console.log('用法:vcorp-chrome desktop install | uninstall | status | full on|off');
|
|
123
137
|
}
|
|
124
138
|
break;
|
|
125
139
|
}
|
|
@@ -156,6 +170,7 @@ switch (cmd) {
|
|
|
156
170
|
vcorp-chrome bridge --foreground 前台跑桥(调试用)
|
|
157
171
|
vcorp-chrome install --dry-run 只看会改哪些配置,不写
|
|
158
172
|
vcorp-chrome desktop install|uninstall|status 让云端任务也能操作桌面应用(登记原生消息宿主)
|
|
173
|
+
vcorp-chrome desktop full on|off 完全模式:云端任务能跑命令、读写文件、用全部桌面命令
|
|
159
174
|
|
|
160
175
|
配置目录 ${HOME}`);
|
|
161
176
|
}
|
package/src/lib/paths.js
CHANGED
|
@@ -13,6 +13,21 @@ export const LOG_FILE = path.join(HOME, 'bridge.log');
|
|
|
13
13
|
|
|
14
14
|
export const MACHINE_FILE = path.join(HOME, 'machine.json');
|
|
15
15
|
|
|
16
|
+
// 桌面完全模式的开关。只在这台电脑上用 `vcorp-chrome desktop full on|off` 改,
|
|
17
|
+
// 原生宿主每条命令现读一次——服务器、网页、扩展都写不到这个文件。
|
|
18
|
+
export const FULL_FILE = path.join(HOME, 'desktop-full.json');
|
|
19
|
+
|
|
20
|
+
export function fullMode() {
|
|
21
|
+
try { return JSON.parse(fs.readFileSync(FULL_FILE, 'utf8')).on === true; } catch { return false; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function setFullMode(on) {
|
|
25
|
+
ensureHome();
|
|
26
|
+
if (!on) { fs.rmSync(FULL_FILE, { force: true }); return false; }
|
|
27
|
+
fs.writeFileSync(FULL_FILE, JSON.stringify({ on: true, at: new Date().toISOString() }, null, 2), { mode: 0o600 });
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
export const DEFAULT_PORT = 8899;
|
|
17
32
|
|
|
18
33
|
// 这台电脑(这个系统用户)的身份。本机 MCP 和原生宿主(native-host/)读同一个文件,
|