@web-auto/camo 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +18 -19
  2. package/bin/browser-service.mjs +11 -0
  3. package/package.json +7 -2
  4. package/scripts/install.mjs +3 -3
  5. package/src/cli.mjs +8 -5
  6. package/src/commands/attach.mjs +141 -0
  7. package/src/commands/browser.mjs +5 -16
  8. package/src/commands/mouse.mjs +2 -12
  9. package/src/container/runtime-core/operations/index.mjs +6 -15
  10. package/src/container/subscription-registry.mjs +6 -6
  11. package/src/core/actions.mjs +0 -12
  12. package/src/core/index.mjs +0 -1
  13. package/src/lifecycle/lock.mjs +7 -3
  14. package/src/services/browser-service/index.js +651 -0
  15. package/src/services/browser-service/index.js.map +1 -0
  16. package/src/services/browser-service/internal/BrowserSession.input.test.js +322 -0
  17. package/src/services/browser-service/internal/BrowserSession.input.test.js.map +1 -0
  18. package/src/services/browser-service/internal/BrowserSession.js +304 -0
  19. package/src/services/browser-service/internal/BrowserSession.js.map +1 -0
  20. package/src/services/browser-service/internal/ElementRegistry.js +61 -0
  21. package/src/services/browser-service/internal/ElementRegistry.js.map +1 -0
  22. package/src/services/browser-service/internal/ProfileLock.js +85 -0
  23. package/src/services/browser-service/internal/ProfileLock.js.map +1 -0
  24. package/src/services/browser-service/internal/SessionManager.js +184 -0
  25. package/src/services/browser-service/internal/SessionManager.js.map +1 -0
  26. package/src/services/browser-service/internal/SessionManager.test.js +40 -0
  27. package/src/services/browser-service/internal/SessionManager.test.js.map +1 -0
  28. package/src/services/browser-service/internal/browser-session/cookies.js +145 -0
  29. package/src/services/browser-service/internal/browser-session/cookies.js.map +1 -0
  30. package/src/services/browser-service/internal/browser-session/input-ops.js +127 -0
  31. package/src/services/browser-service/internal/browser-session/input-ops.js.map +1 -0
  32. package/src/services/browser-service/internal/browser-session/input-pipeline.js +133 -0
  33. package/src/services/browser-service/internal/browser-session/input-pipeline.js.map +1 -0
  34. package/src/services/browser-service/internal/browser-session/logging.js +46 -0
  35. package/src/services/browser-service/internal/browser-session/navigation.js +39 -0
  36. package/src/services/browser-service/internal/browser-session/navigation.js.map +1 -0
  37. package/src/services/browser-service/internal/browser-session/page-hooks.js +443 -0
  38. package/src/services/browser-service/internal/browser-session/page-hooks.js.map +1 -0
  39. package/src/services/browser-service/internal/browser-session/page-management.js +212 -0
  40. package/src/services/browser-service/internal/browser-session/page-management.js.map +1 -0
  41. package/src/services/browser-service/internal/browser-session/recording.js +199 -0
  42. package/src/services/browser-service/internal/browser-session/recording.js.map +1 -0
  43. package/src/services/browser-service/internal/browser-session/runtime-events.js +62 -0
  44. package/src/services/browser-service/internal/browser-session/runtime-events.js.map +1 -0
  45. package/src/services/browser-service/internal/browser-session/session-core.js +85 -0
  46. package/src/services/browser-service/internal/browser-session/session-core.js.map +1 -0
  47. package/src/services/browser-service/internal/browser-session/session-state.js +39 -0
  48. package/src/services/browser-service/internal/browser-session/session-state.js.map +1 -0
  49. package/src/services/browser-service/internal/browser-session/types.js +15 -0
  50. package/src/services/browser-service/internal/browser-session/types.js.map +1 -0
  51. package/src/services/browser-service/internal/browser-session/utils.js +69 -0
  52. package/src/services/browser-service/internal/browser-session/utils.js.map +1 -0
  53. package/src/services/browser-service/internal/browser-session/viewport-manager.js +47 -0
  54. package/src/services/browser-service/internal/browser-session/viewport-manager.js.map +1 -0
  55. package/src/services/browser-service/internal/browser-session/viewport.js +216 -0
  56. package/src/services/browser-service/internal/browser-session/viewport.js.map +1 -0
  57. package/src/services/browser-service/internal/container-matcher.js +852 -0
  58. package/src/services/browser-service/internal/container-matcher.js.map +1 -0
  59. package/src/services/browser-service/internal/container-registry.js +182 -0
  60. package/src/services/browser-service/internal/engine-manager.js +259 -0
  61. package/src/services/browser-service/internal/engine-manager.js.map +1 -0
  62. package/src/services/browser-service/internal/fingerprint.js +203 -0
  63. package/src/services/browser-service/internal/fingerprint.js.map +1 -0
  64. package/src/services/browser-service/internal/heartbeat.js +137 -0
  65. package/src/services/browser-service/internal/logging.js +46 -0
  66. package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -0
  67. package/src/services/browser-service/internal/pageRuntime.js +29 -0
  68. package/src/services/browser-service/internal/pageRuntime.js.map +1 -0
  69. package/src/services/browser-service/internal/runtimeInjector.js +31 -0
  70. package/src/services/browser-service/internal/runtimeInjector.js.map +1 -0
  71. package/src/services/browser-service/internal/service-process-logger.js +140 -0
  72. package/src/services/browser-service/internal/state-bus.js +46 -0
  73. package/src/services/browser-service/internal/state-bus.js.map +1 -0
  74. package/src/services/browser-service/internal/storage-paths.js +42 -0
  75. package/src/services/browser-service/internal/storage-paths.js.map +1 -0
  76. package/src/services/browser-service/internal/ws-server.js +1194 -0
  77. package/src/services/browser-service/internal/ws-server.js.map +1 -0
  78. package/src/services/browser-service/internal/ws-server.test.js +59 -0
  79. package/src/services/browser-service/internal/ws-server.test.js.map +1 -0
  80. package/src/services/browser-service/server.mjs +6 -0
  81. package/src/services/controller/cli-bridge.js +93 -0
  82. package/src/services/controller/container-index.js +50 -0
  83. package/src/services/controller/container-storage.js +36 -0
  84. package/src/services/controller/controller-actions.js +207 -0
  85. package/src/services/controller/controller.js +1138 -0
  86. package/src/services/controller/selectors.js +54 -0
  87. package/src/services/controller/transport.js +118 -0
  88. package/src/utils/browser-service.mjs +100 -125
  89. package/src/utils/config.mjs +22 -21
  90. package/src/utils/help.mjs +11 -9
  91. package/src/utils/ws-client.mjs +30 -0
package/README.md CHANGED
@@ -163,7 +163,7 @@ camo create fingerprint --os <os> --region <region>
163
163
  ### Config
164
164
 
165
165
  ```bash
166
- camo config repo-root [path] # Get/set persisted webauto repo root
166
+ camo config repo-root [path] # Get/set persisted camo repo root
167
167
  camo highlight-mode [status|on|off] # Global highlight mode for click/type/scroll
168
168
  ```
169
169
 
@@ -276,7 +276,6 @@ camo window resize [profileId] --width <w> --height <h>
276
276
  ### Mouse Control
277
277
 
278
278
  ```bash
279
- camo mouse move [profileId] --x <x> --y <y> [--steps <n>]
280
279
  camo mouse click [profileId] --x <x> --y <y> [--button left|right|middle] [--clicks <n>] [--delay <ms>]
281
280
  camo mouse wheel [profileId] [--deltax <px>] [--deltay <px>]
282
281
  ```
@@ -349,20 +348,20 @@ By default, non-`events` commands auto-start the progress daemon (`/events`) in
349
348
 
350
349
  ## Configuration
351
350
 
352
- - Config file: `~/.webauto/camo-cli.json`
353
- - Profiles directory: `~/.webauto/profiles/`
354
- - Fingerprints directory: `~/.webauto/fingerprints/`
355
- - Session registry: `~/.webauto/sessions/`
356
- - Lock files: `~/.webauto/locks/`
357
- - GeoIP database: `~/.webauto/geoip/GeoLite2-City.mmdb`
358
- - User container root: `~/.webauto/container-lib/`
359
- - Subscription root: `~/.webauto/container-subscriptions/`
351
+ - Config file: `~/.camo/camo-cli.json`
352
+ - Profiles directory: `~/.camo/profiles/`
353
+ - Fingerprints directory: `~/.camo/fingerprints/`
354
+ - Session registry: `~/.camo/sessions/`
355
+ - Lock files: `~/.camo/locks/`
356
+ - GeoIP database: `~/.camo/geoip/GeoLite2-City.mmdb`
357
+ - User container root: `~/.camo/container-lib/`
358
+ - Subscription root: `~/.camo/container-subscriptions/`
360
359
 
361
360
  ### Subscription-driven Watch
362
361
 
363
362
  ```bash
364
363
  # 1) Migrate container-library into subscription sets
365
- camo container init --source /Users/fanzhang/Documents/github/webauto/container-library
364
+ camo container init --source /Users/fanzhang/Documents/github/camo/container-library
366
365
 
367
366
  # 2) Register sets to a profile
368
367
  camo container register xiaohongshu-batch-1 xiaohongshu_home xiaohongshu_home.search_input
@@ -440,13 +439,13 @@ Condition types:
440
439
 
441
440
  ### Environment Variables
442
441
 
443
- - `WEBAUTO_BROWSER_URL` - Browser service URL (default: `http://127.0.0.1:7704`)
444
- - `WEBAUTO_INSTALL_DIR` - `@web-auto/webauto` 安装目录(可选,首次安装兜底)
445
- - `WEBAUTO_REPO_ROOT` - WebAuto repository root (optional, dev mode)
446
- - `WEBAUTO_DATA_ROOT` / `WEBAUTO_HOME` - 用户数据目录(Windows 默认 `D:/webauto`,无 D 盘回退 `~/.webauto`)
447
- - `WEBAUTO_PROFILE_ROOT` - Profile 目录覆盖(默认 `<data-root>/profiles`)
448
- - `WEBAUTO_ROOT` - 兼容旧变量(当值不是 `webauto/.webauto` 目录时会自动补 `.webauto`)
449
- - `WEBAUTO_CONTAINER_ROOT` - User container root override (default: `~/.webauto/container-lib`)
442
+ - `CAMO_BROWSER_URL` - Browser service URL (default: `http://127.0.0.1:7704`)
443
+ - `CAMO_INSTALL_DIR` - `@web-auto/camo` 安装目录(可选,首次安装兜底)
444
+ - `CAMO_REPO_ROOT` - Camo repository root (optional, dev mode)
445
+ - `CAMO_DATA_ROOT` / `CAMO_HOME` - 用户数据目录(Windows 默认 `D:/camo`,无 D 盘回退 `~/.camo`)
446
+ - `CAMO_PROFILE_ROOT` - Profile 目录覆盖(默认 `<data-root>/profiles`)
447
+ - `CAMO_ROOT` - 兼容旧变量(当值不是 `camo/.camo` 目录时会自动补 `.camo`)
448
+ - `CAMO_CONTAINER_ROOT` - User container root override (default: `~/.camo/container-lib`)
450
449
  - `CAMO_PROGRESS_EVENTS_FILE` - Optional progress event JSONL path override
451
450
  - `CAMO_PROGRESS_WS_HOST` / `CAMO_PROGRESS_WS_PORT` - Progress websocket daemon bind address (default: `127.0.0.1:7788`)
452
451
  - `CAMO_DEFAULT_WINDOW_VERTICAL_RESERVE` - Reserved vertical pixels for default headful auto-size
@@ -455,7 +454,7 @@ Condition types:
455
454
 
456
455
  Camo CLI persists session information locally:
457
456
 
458
- - Sessions are registered in `~/.webauto/sessions/`
457
+ - Sessions are registered in `~/.camo/sessions/`
459
458
  - On restart, `camo sessions` / `camo instances` shows live + orphaned sessions
460
459
  - Stale sessions (>7 days) are automatically cleaned up
461
460
 
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, resolve } from 'node:path';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const entry = resolve(__dirname, '../src/services/browser-service/index.js');
7
+
8
+ const mod = await import(entry);
9
+ if (typeof mod.runBrowserServiceCli === 'function') {
10
+ await mod.runBrowserServiceCli(process.argv);
11
+ }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@web-auto/camo",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Camoufox Browser CLI - Cross-platform browser automation",
5
5
  "type": "module",
6
6
  "bin": {
7
- "camo": "./bin/camo.mjs"
7
+ "camo": "./bin/camo.mjs",
8
+ "browser-service": "./bin/browser-service.mjs"
8
9
  },
9
10
  "files": [
10
11
  "bin/",
@@ -40,5 +41,9 @@
40
41
  },
41
42
  "devDependencies": {
42
43
  "c8": "^10.1.3"
44
+ },
45
+ "dependencies": {
46
+ "playwright": "^1.58.2",
47
+ "ws": "^8.19.0"
43
48
  }
44
49
  }
@@ -49,15 +49,15 @@ function install() {
49
49
 
50
50
  const thisDir = path.dirname(new URL(import.meta.url).pathname);
51
51
  const moduleDir = path.resolve(thisDir, '..');
52
- const srcFile = path.join(moduleDir, 'bin', 'camoufox.mjs');
52
+ const srcFile = path.join(moduleDir, 'bin', 'camo.mjs');
53
53
 
54
54
  if (!fs.existsSync(srcFile)) {
55
55
  console.error(`Source not found: ${srcFile}`);
56
- console.error('Run: cp src/cli.mjs bin/camoufox.mjs');
56
+ console.error('Run: cp src/cli.mjs bin/camo.mjs');
57
57
  process.exit(1);
58
58
  }
59
59
 
60
- const targetFile = path.join(targetDir, 'camoufox.mjs');
60
+ const targetFile = path.join(targetDir, 'camo.mjs');
61
61
  fs.copyFileSync(srcFile, targetFile);
62
62
  fs.chmodSync(targetFile, 0o755);
63
63
 
package/src/cli.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import path from 'node:path';
4
4
  import { readFileSync } from 'node:fs';
5
- import { listProfiles, getDefaultProfile, loadConfig, hasStartScript, setRepoRoot } from './utils/config.mjs';
5
+ import { listProfiles, getDefaultProfile, loadConfig, setRepoRoot } from './utils/config.mjs';
6
6
  import { printHelp, printProfilesAndHint } from './utils/help.mjs';
7
7
  import { handleProfileCommand } from './commands/profile.mjs';
8
8
  import { handleInitCommand } from './commands/init.mjs';
@@ -17,6 +17,7 @@ import { handleEventsCommand } from './commands/events.mjs';
17
17
  import { handleDevtoolsCommand } from './commands/devtools.mjs';
18
18
  import { handleRecordCommand } from './commands/record.mjs';
19
19
  import { handleHighlightModeCommand } from './commands/highlight-mode.mjs';
20
+ import { handleAttachCommand } from './commands/attach.mjs';
20
21
  import {
21
22
  handleStartCommand, handleStopCommand, handleStatusCommand,
22
23
  handleGotoCommand, handleBackCommand, handleScreenshotCommand,
@@ -34,7 +35,6 @@ import { safeAppendProgressEvent } from './events/progress-log.mjs';
34
35
  import { ensureProgressEventDaemon } from './events/daemon.mjs';
35
36
 
36
37
  const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url));
37
- const START_SCRIPT_REL = path.join('runtime', 'infra', 'utils', 'scripts', 'service', 'start-browser-service.mjs');
38
38
  const PACKAGE_JSON_PATH = path.resolve(CURRENT_DIR, '..', 'package.json');
39
39
 
40
40
  function readCliVersion() {
@@ -165,10 +165,8 @@ async function handleConfigCommand(args) {
165
165
  console.log(JSON.stringify({ ok: true, repoRoot: loadConfig().repoRoot }, null, 2));
166
166
  return;
167
167
  }
168
+ // Accept any path; camo does not require camo-specific structure
168
169
  const resolved = path.resolve(repoRoot);
169
- if (!hasStartScript(resolved)) {
170
- throw new Error(`Invalid repo root: ${resolved} (missing ${START_SCRIPT_REL})`);
171
- }
172
170
  setRepoRoot(resolved);
173
171
  console.log(JSON.stringify({ ok: true, repoRoot: resolved }, null, 2));
174
172
  }
@@ -242,6 +240,11 @@ async function main() {
242
240
  return;
243
241
  }
244
242
 
243
+ if (cmd === 'attach') {
244
+ await runTrackedCommand(cmd, args, () => handleAttachCommand(args));
245
+ return;
246
+ }
247
+
245
248
  if (cmd === 'devtools') {
246
249
  await runTrackedCommand(cmd, args, () => handleDevtoolsCommand(args));
247
250
  return;
@@ -0,0 +1,141 @@
1
+ import { resolveWsUrl, ensureWsClient } from '../utils/ws-client.mjs';
2
+
3
+ function readFlagValue(args, names) {
4
+ for (let i = 0; i < args.length; i += 1) {
5
+ if (!names.includes(args[i])) continue;
6
+ const value = args[i + 1];
7
+ if (!value || String(value).startsWith('-')) return null;
8
+ return value;
9
+ }
10
+ return null;
11
+ }
12
+
13
+ function isJsonLine(line) {
14
+ const trimmed = String(line || '').trim();
15
+ return trimmed.startsWith('{') && trimmed.endsWith('}');
16
+ }
17
+
18
+ function nextRequestId() {
19
+ return `attach_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
20
+ }
21
+
22
+ function normalizeCommand(lineObj, sessionId) {
23
+ if (lineObj && typeof lineObj === 'object') {
24
+ if (lineObj.type) {
25
+ return {
26
+ request_id: lineObj.request_id || nextRequestId(),
27
+ session_id: lineObj.session_id || sessionId,
28
+ ...lineObj,
29
+ };
30
+ }
31
+ if (lineObj.command_type || lineObj.commandType) {
32
+ return {
33
+ type: 'command',
34
+ request_id: lineObj.request_id || nextRequestId(),
35
+ session_id: lineObj.session_id || sessionId,
36
+ data: {
37
+ command_type: lineObj.command_type || lineObj.commandType,
38
+ action: lineObj.action,
39
+ parameters: lineObj.parameters || lineObj.args || {},
40
+ },
41
+ };
42
+ }
43
+ if (lineObj.action) {
44
+ return {
45
+ type: 'command',
46
+ request_id: lineObj.request_id || nextRequestId(),
47
+ session_id: lineObj.session_id || sessionId,
48
+ data: {
49
+ command_type: 'dev_command',
50
+ action: lineObj.action,
51
+ parameters: lineObj.args || lineObj.parameters || {},
52
+ },
53
+ };
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+
59
+ export async function handleAttachCommand(args) {
60
+ const target = args[1];
61
+ if (!target) {
62
+ throw new Error('Usage: camo attach <profileId|sessionId> [--format json|jsonl]');
63
+ }
64
+
65
+ const sessionId = target;
66
+ const format = readFlagValue(args, ['--format']) || 'jsonl';
67
+ const wsUrl = resolveWsUrl();
68
+ const socket = await ensureWsClient(wsUrl);
69
+
70
+ const send = (msg) => socket.send(JSON.stringify(msg));
71
+ const output = (obj) => {
72
+ if (format === 'json') {
73
+ process.stdout.write(JSON.stringify(obj, null, 2) + '\n');
74
+ } else {
75
+ process.stdout.write(JSON.stringify(obj) + '\n');
76
+ }
77
+ };
78
+
79
+ send({
80
+ type: 'subscribe',
81
+ request_id: nextRequestId(),
82
+ session_id: sessionId,
83
+ data: { topics: ['browser.runtime.event'] },
84
+ });
85
+
86
+ output({ ok: true, attached: sessionId, wsUrl, format });
87
+
88
+ let closing = false;
89
+
90
+ socket.addEventListener('message', (event) => {
91
+ const text = typeof event.data === 'string' ? event.data : String(event.data);
92
+ output({ type: 'ws', data: text });
93
+ });
94
+
95
+ socket.addEventListener('close', () => {
96
+ if (!closing) output({ ok: false, event: 'ws_closed' });
97
+ process.exit(0);
98
+ });
99
+
100
+ socket.addEventListener('error', (err) => {
101
+ output({ ok: false, event: 'ws_error', error: err?.message || String(err) });
102
+ process.exit(1);
103
+ });
104
+
105
+ let buffer = '';
106
+ process.stdin.setEncoding('utf8');
107
+ process.stdin.on('data', (chunk) => {
108
+ buffer += chunk;
109
+ let idx;
110
+ while ((idx = buffer.indexOf('\n')) !== -1) {
111
+ const line = buffer.slice(0, idx).trim();
112
+ buffer = buffer.slice(idx + 1);
113
+ if (!line) continue;
114
+ if (!isJsonLine(line)) {
115
+ output({ ok: false, error: 'Invalid JSON line', line });
116
+ continue;
117
+ }
118
+ try {
119
+ const payload = JSON.parse(line);
120
+ const msg = normalizeCommand(payload, sessionId);
121
+ if (!msg) {
122
+ output({ ok: false, error: 'Invalid command payload', payload });
123
+ continue;
124
+ }
125
+ send(msg);
126
+ output({ ok: true, sent: msg.request_id });
127
+ } catch (err) {
128
+ output({ ok: false, error: err?.message || String(err) });
129
+ }
130
+ }
131
+ });
132
+
133
+ process.stdin.on('end', () => {
134
+ closing = true;
135
+ const timer = setTimeout(() => {
136
+ try { socket.close(); } catch {}
137
+ process.exit(0);
138
+ }, 1500);
139
+ timer.unref();
140
+ });
141
+ }
@@ -139,7 +139,7 @@ async function resolveVisibleTargetPoint(profileId, selector, options = {}) {
139
139
  };
140
140
  if (highlight) {
141
141
  try {
142
- const id = 'webauto-action-highlight-overlay';
142
+ const id = 'camo-action-highlight-overlay';
143
143
  const old = document.getElementById(id);
144
144
  if (old) old.remove();
145
145
  const overlay = document.createElement('div');
@@ -222,10 +222,6 @@ async function ensureClickTargetInViewport(profileId, selector, initialTarget, o
222
222
  if (Math.abs(deltaY) < 100) {
223
223
  deltaY = deltaY >= 0 ? 120 : -120;
224
224
  }
225
- const anchorX = clamp(Math.round(vw / 2), 1, Math.max(1, vw - 1));
226
- const anchorY = clamp(Math.round(vh / 2), 1, Math.max(1, vh - 1));
227
-
228
- await callAPI('mouse:move', { profileId, x: anchorX, y: anchorY, steps: 1 }, { timeoutMs: INPUT_ACTION_TIMEOUT_MS });
229
225
  await callAPI('mouse:wheel', { profileId, deltaX: 0, deltaY }, { timeoutMs: INPUT_ACTION_TIMEOUT_MS });
230
226
  autoScrolled += 1;
231
227
  if (settleMs > 0) {
@@ -980,11 +976,6 @@ export async function handleScrollCommand(args) {
980
976
  profileId,
981
977
  script: buildScrollTargetScript({ selector, highlight }),
982
978
  }, { timeoutMs: INPUT_ACTION_TIMEOUT_MS });
983
- const centerX = Number(target?.result?.center?.x);
984
- const centerY = Number(target?.result?.center?.y);
985
- if (Number.isFinite(centerX) && Number.isFinite(centerY)) {
986
- await callAPI('mouse:move', { profileId, x: centerX, y: centerY, steps: 2 }, { timeoutMs: INPUT_ACTION_TIMEOUT_MS });
987
- }
988
979
  const deltaX = direction === 'left' ? -amount : direction === 'right' ? amount : 0;
989
980
  const deltaY = direction === 'up' ? -amount : direction === 'down' ? amount : 0;
990
981
  const result = await callAPI('mouse:wheel', { profileId, deltaX, deltaY }, { timeoutMs: INPUT_ACTION_TIMEOUT_MS });
@@ -1024,7 +1015,6 @@ export async function handleClickCommand(args) {
1024
1015
  if (highlight) {
1025
1016
  target = await resolveVisibleTargetPoint(profileId, selector, { highlight: true });
1026
1017
  }
1027
- await callAPI('mouse:move', { profileId, x: target.center.x, y: target.center.y, steps: 2 }, { timeoutMs: INPUT_ACTION_TIMEOUT_MS });
1028
1018
  const result = await callAPI('mouse:click', {
1029
1019
  profileId,
1030
1020
  x: target.center.x,
@@ -1064,7 +1054,6 @@ export async function handleTypeCommand(args) {
1064
1054
  if (!selector || text === undefined) throw new Error('Usage: camo type [profileId] <selector> <text> [--highlight|--no-highlight]');
1065
1055
 
1066
1056
  const target = await resolveVisibleTargetPoint(profileId, selector, { highlight });
1067
- await callAPI('mouse:move', { profileId, x: target.center.x, y: target.center.y, steps: 2 }, { timeoutMs: INPUT_ACTION_TIMEOUT_MS });
1068
1057
  await callAPI('mouse:click', {
1069
1058
  profileId,
1070
1059
  x: target.center.x,
@@ -1132,11 +1121,11 @@ export async function handleClearHighlightCommand(args) {
1132
1121
  const result = await callAPI('evaluate', {
1133
1122
  profileId,
1134
1123
  script: `(() => {
1135
- const overlay = document.getElementById('webauto-highlight-overlay');
1124
+ const overlay = document.getElementById('camo-highlight-overlay');
1136
1125
  if (overlay) overlay.remove();
1137
- document.querySelectorAll('[data-webauto-highlight]').forEach(el => {
1138
- el.style.outline = el.dataset.webautoHighlight || '';
1139
- delete el.dataset.webautoHighlight;
1126
+ document.querySelectorAll('[data-camo-highlight]').forEach(el => {
1127
+ el.style.outline = el.dataset.camoHighlight || '';
1128
+ delete el.dataset.camoHighlight;
1140
1129
  });
1141
1130
  return { cleared: true };
1142
1131
  })()`
@@ -15,17 +15,7 @@ export async function handleMouseCommand(args) {
15
15
  const profileId = getPositionals(args, 2)[0] || getDefaultProfile();
16
16
  if (!profileId) throw new Error('No profile specified and no default profile set');
17
17
 
18
- if (sub === 'move') {
19
- const xIdx = args.indexOf('--x');
20
- const yIdx = args.indexOf('--y');
21
- const stepsIdx = args.indexOf('--steps');
22
- if (xIdx === -1 || yIdx === -1) throw new Error('Usage: camo mouse move [profileId] --x <x> --y <y> [--steps <n>]');
23
- const x = parseInt(args[xIdx + 1]);
24
- const y = parseInt(args[yIdx + 1]);
25
- const steps = stepsIdx >= 0 ? parseInt(args[stepsIdx + 1]) : undefined;
26
- const result = await callAPI('mouse:move', { profileId, x, y, steps }, { timeoutMs: INPUT_ACTION_TIMEOUT_MS });
27
- console.log(JSON.stringify(result, null, 2));
28
- } else if (sub === 'click') {
18
+ if (sub === 'click') {
29
19
  // Use existing click command? We already have click command for element clicking.
30
20
  // This is for raw mouse click at coordinates.
31
21
  const xIdx = args.indexOf('--x');
@@ -50,6 +40,6 @@ export async function handleMouseCommand(args) {
50
40
  const result = await callAPI('mouse:wheel', { profileId, deltaX, deltaY }, { timeoutMs: INPUT_ACTION_TIMEOUT_MS });
51
41
  console.log(JSON.stringify(result, null, 2));
52
42
  } else {
53
- throw new Error('Usage: camo mouse <move|click|wheel> [profileId] [options]');
43
+ throw new Error('Usage: camo mouse <click|wheel> [profileId] [options]');
54
44
  }
55
45
  }
@@ -32,7 +32,6 @@ const DEFAULT_MODAL_SELECTORS = [
32
32
  '.note-detail-page',
33
33
  '.note-detail-dialog',
34
34
  ];
35
-
36
35
  function resolveFilterMode(input) {
37
36
  const text = String(input || process.env.CAMO_FILTER_MODE || 'strict').trim().toLowerCase();
38
37
  if (!text) return 'strict';
@@ -282,9 +281,6 @@ async function scrollTargetIntoViewport(profileId, selector, initialTarget, para
282
281
  if (isTargetFullyInViewport(target, visibilityMargin)) break;
283
282
  const delta = resolveViewportScrollDelta(target, visibilityMargin);
284
283
  if (Math.abs(delta.deltaX) < 1 && Math.abs(delta.deltaY) < 1) break;
285
- const anchorX = clamp(Math.round(Number(target?.center?.x || 0) || 1), 1, Math.max(1, Number(target?.viewport?.width || 1) - 1));
286
- const anchorY = clamp(Math.round(Number(target?.center?.y || 0) || 1), 1, Math.max(1, Number(target?.viewport?.height || 1) - 1));
287
- await callAPI('mouse:move', { profileId, x: anchorX, y: anchorY, steps: 1 });
288
284
  await callAPI('mouse:wheel', { profileId, deltaX: delta.deltaX, deltaY: delta.deltaY });
289
285
  if (settleMs > 0) await sleep(settleMs);
290
286
  target = await resolveSelectorTarget(profileId, selector, options);
@@ -376,8 +372,6 @@ async function executeSelectorOperation({ profileId, action, operation, params,
376
372
  });
377
373
  }
378
374
 
379
- await callAPI('mouse:move', { profileId, x: target.center.x, y: target.center.y, steps: 2 });
380
-
381
375
  if (action === 'scroll_into_view') {
382
376
  return {
383
377
  ok: true,
@@ -399,7 +393,12 @@ async function executeSelectorOperation({ profileId, action, operation, params,
399
393
  clicks,
400
394
  ...(Number.isFinite(delay) && delay >= 0 ? { delay } : {}),
401
395
  });
402
- return { ok: true, code: 'OPERATION_DONE', message: 'click done', data: { selector, target, result, targetFullyVisible, visibilityMargin } };
396
+ return {
397
+ ok: true,
398
+ code: 'OPERATION_DONE',
399
+ message: 'click done',
400
+ data: { selector, target, result, targetFullyVisible, visibilityMargin },
401
+ };
403
402
  }
404
403
 
405
404
  const text = String(params.text ?? params.value ?? '');
@@ -627,14 +626,6 @@ export async function executeOperation({ profileId, operation, context = {} }) {
627
626
  selector: anchorSelector,
628
627
  filterMode,
629
628
  });
630
- if (anchor?.center?.x && anchor?.center?.y) {
631
- await callAPI('mouse:move', {
632
- profileId: resolvedProfile,
633
- x: Math.max(1, Math.round(Number(anchor.center.x) || 1)),
634
- y: Math.max(1, Math.round(Number(anchor.center.y) || 1)),
635
- steps: 2,
636
- });
637
- }
638
629
  const result = await callAPI('mouse:wheel', { profileId: resolvedProfile, deltaX, deltaY });
639
630
  return {
640
631
  ok: true,
@@ -1,9 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { findRepoRootCandidate } from '../utils/browser-service.mjs';
4
3
  import { CONFIG_DIR } from '../utils/config.mjs';
5
4
 
6
- const CONTAINER_ROOT_ENV = process.env.WEBAUTO_CONTAINER_ROOT || process.env.ROUTECODEX_CONTAINER_ROOT;
5
+ const CONTAINER_ROOT_ENV = process.env.CAMO_CONTAINER_ROOT;
7
6
 
8
7
  export const USER_CONTAINER_ROOT = CONTAINER_ROOT_ENV || path.join(CONFIG_DIR, 'container-lib');
9
8
  export const SUBSCRIPTION_ROOT = path.join(CONFIG_DIR, 'container-subscriptions');
@@ -223,9 +222,10 @@ function collectJsonFiles(dirPath) {
223
222
 
224
223
  function detectContainerLibraryRoot(explicitRoot) {
225
224
  if (explicitRoot) return explicitRoot;
226
- const repoRoot = findRepoRootCandidate();
227
- if (!repoRoot) return null;
228
- const candidate = path.join(repoRoot, 'container-library');
225
+ const cfg = loadConfig();
226
+ const root = cfg.repoRoot || process.env.CAMO_CONTAINER_ROOT || process.cwd();
227
+ if (!root) return null;
228
+ const candidate = path.join(root, 'container-library');
229
229
  return fs.existsSync(candidate) ? candidate : null;
230
230
  }
231
231
 
@@ -251,7 +251,7 @@ export function initContainerSubscriptionDirectory(options = {}) {
251
251
  const containerLibraryRoot = detectContainerLibraryRoot(options.containerLibraryRoot);
252
252
  if (!containerLibraryRoot || !fs.existsSync(containerLibraryRoot)) {
253
253
  throw new Error(
254
- 'container-library not found. Set WEBAUTO_REPO_ROOT or run `camo config repo-root <webauto-path>` first.',
254
+ 'container-library not found. Set CAMO_CONTAINER_ROOT or run `camo config repo-root <path>` first.',
255
255
  );
256
256
  }
257
257
 
@@ -117,7 +117,6 @@ export async function clickElement(profileId, selector, options = {}) {
117
117
  const x = box.x + box.width / 2;
118
118
  const y = box.y + box.height / 2;
119
119
 
120
- await page.mouse.move(x, y);
121
120
  await page.mouse.click(x, y, { button: options.button || 'left', clickCount: options.clickCount || 1 });
122
121
 
123
122
  return {
@@ -535,17 +534,6 @@ export async function closePage(profileId, pageIndex) {
535
534
  /**
536
535
  * Mouse operations
537
536
  */
538
- export async function mouseMove(profileId, x, y, options = {}) {
539
- if (!isBrowserRunning(profileId)) {
540
- throw new Error(`No browser running for profile: ${profileId}`);
541
- }
542
-
543
- const page = await getCurrentPage(profileId);
544
- await page.mouse.move(x, y, { steps: options.steps || 1 });
545
-
546
- return { ok: true, profileId, x, y };
547
- }
548
-
549
537
  export async function mouseClick(profileId, x, y, options = {}) {
550
538
  if (!isBrowserRunning(profileId)) {
551
539
  throw new Error(`No browser running for profile: ${profileId}`);
@@ -38,7 +38,6 @@ export {
38
38
  listPages,
39
39
  switchPage,
40
40
  closePage,
41
- mouseMove,
42
41
  mouseClick,
43
42
  mouseWheel,
44
43
  } from './actions.mjs';
@@ -48,9 +48,13 @@ export function acquireLock(profileId, sessionInfo = {}) {
48
48
 
49
49
  export function releaseLock(profileId) {
50
50
  const lockFile = getLockFile(profileId);
51
- if (fs.existsSync(lockFile)) {
52
- fs.unlinkSync(lockFile);
53
- return true;
51
+ try {
52
+ if (fs.existsSync(lockFile)) {
53
+ fs.unlinkSync(lockFile);
54
+ return true;
55
+ }
56
+ } catch {
57
+ // Ignore missing lock file or racing cleanup.
54
58
  }
55
59
  return false;
56
60
  }