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.
@@ -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
- function reply(obj) {
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.0.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",
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/)读同一个文件,