@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.
- package/README.md +18 -19
- package/bin/browser-service.mjs +11 -0
- package/package.json +7 -2
- package/scripts/install.mjs +3 -3
- package/src/cli.mjs +8 -5
- package/src/commands/attach.mjs +141 -0
- package/src/commands/browser.mjs +5 -16
- package/src/commands/mouse.mjs +2 -12
- package/src/container/runtime-core/operations/index.mjs +6 -15
- package/src/container/subscription-registry.mjs +6 -6
- package/src/core/actions.mjs +0 -12
- package/src/core/index.mjs +0 -1
- package/src/lifecycle/lock.mjs +7 -3
- package/src/services/browser-service/index.js +651 -0
- package/src/services/browser-service/index.js.map +1 -0
- package/src/services/browser-service/internal/BrowserSession.input.test.js +322 -0
- package/src/services/browser-service/internal/BrowserSession.input.test.js.map +1 -0
- package/src/services/browser-service/internal/BrowserSession.js +304 -0
- package/src/services/browser-service/internal/BrowserSession.js.map +1 -0
- package/src/services/browser-service/internal/ElementRegistry.js +61 -0
- package/src/services/browser-service/internal/ElementRegistry.js.map +1 -0
- package/src/services/browser-service/internal/ProfileLock.js +85 -0
- package/src/services/browser-service/internal/ProfileLock.js.map +1 -0
- package/src/services/browser-service/internal/SessionManager.js +184 -0
- package/src/services/browser-service/internal/SessionManager.js.map +1 -0
- package/src/services/browser-service/internal/SessionManager.test.js +40 -0
- package/src/services/browser-service/internal/SessionManager.test.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/cookies.js +145 -0
- package/src/services/browser-service/internal/browser-session/cookies.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/input-ops.js +127 -0
- package/src/services/browser-service/internal/browser-session/input-ops.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/input-pipeline.js +133 -0
- package/src/services/browser-service/internal/browser-session/input-pipeline.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/logging.js +46 -0
- package/src/services/browser-service/internal/browser-session/navigation.js +39 -0
- package/src/services/browser-service/internal/browser-session/navigation.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/page-hooks.js +443 -0
- package/src/services/browser-service/internal/browser-session/page-hooks.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/page-management.js +212 -0
- package/src/services/browser-service/internal/browser-session/page-management.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/recording.js +199 -0
- package/src/services/browser-service/internal/browser-session/recording.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/runtime-events.js +62 -0
- package/src/services/browser-service/internal/browser-session/runtime-events.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/session-core.js +85 -0
- package/src/services/browser-service/internal/browser-session/session-core.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/session-state.js +39 -0
- package/src/services/browser-service/internal/browser-session/session-state.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/types.js +15 -0
- package/src/services/browser-service/internal/browser-session/types.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/utils.js +69 -0
- package/src/services/browser-service/internal/browser-session/utils.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/viewport-manager.js +47 -0
- package/src/services/browser-service/internal/browser-session/viewport-manager.js.map +1 -0
- package/src/services/browser-service/internal/browser-session/viewport.js +216 -0
- package/src/services/browser-service/internal/browser-session/viewport.js.map +1 -0
- package/src/services/browser-service/internal/container-matcher.js +852 -0
- package/src/services/browser-service/internal/container-matcher.js.map +1 -0
- package/src/services/browser-service/internal/container-registry.js +182 -0
- package/src/services/browser-service/internal/engine-manager.js +259 -0
- package/src/services/browser-service/internal/engine-manager.js.map +1 -0
- package/src/services/browser-service/internal/fingerprint.js +203 -0
- package/src/services/browser-service/internal/fingerprint.js.map +1 -0
- package/src/services/browser-service/internal/heartbeat.js +137 -0
- package/src/services/browser-service/internal/logging.js +46 -0
- package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -0
- package/src/services/browser-service/internal/pageRuntime.js +29 -0
- package/src/services/browser-service/internal/pageRuntime.js.map +1 -0
- package/src/services/browser-service/internal/runtimeInjector.js +31 -0
- package/src/services/browser-service/internal/runtimeInjector.js.map +1 -0
- package/src/services/browser-service/internal/service-process-logger.js +140 -0
- package/src/services/browser-service/internal/state-bus.js +46 -0
- package/src/services/browser-service/internal/state-bus.js.map +1 -0
- package/src/services/browser-service/internal/storage-paths.js +42 -0
- package/src/services/browser-service/internal/storage-paths.js.map +1 -0
- package/src/services/browser-service/internal/ws-server.js +1194 -0
- package/src/services/browser-service/internal/ws-server.js.map +1 -0
- package/src/services/browser-service/internal/ws-server.test.js +59 -0
- package/src/services/browser-service/internal/ws-server.test.js.map +1 -0
- package/src/services/browser-service/server.mjs +6 -0
- package/src/services/controller/cli-bridge.js +93 -0
- package/src/services/controller/container-index.js +50 -0
- package/src/services/controller/container-storage.js +36 -0
- package/src/services/controller/controller-actions.js +207 -0
- package/src/services/controller/controller.js +1138 -0
- package/src/services/controller/selectors.js +54 -0
- package/src/services/controller/transport.js +118 -0
- package/src/utils/browser-service.mjs +100 -125
- package/src/utils/config.mjs +22 -21
- package/src/utils/help.mjs +11 -9
- 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
|
|
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: `~/.
|
|
353
|
-
- Profiles directory: `~/.
|
|
354
|
-
- Fingerprints directory: `~/.
|
|
355
|
-
- Session registry: `~/.
|
|
356
|
-
- Lock files: `~/.
|
|
357
|
-
- GeoIP database: `~/.
|
|
358
|
-
- User container root: `~/.
|
|
359
|
-
- Subscription root: `~/.
|
|
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/
|
|
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
|
-
- `
|
|
444
|
-
- `
|
|
445
|
-
- `
|
|
446
|
-
- `
|
|
447
|
-
- `
|
|
448
|
-
- `
|
|
449
|
-
- `
|
|
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 `~/.
|
|
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.
|
|
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
|
}
|
package/scripts/install.mjs
CHANGED
|
@@ -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', '
|
|
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/
|
|
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, '
|
|
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,
|
|
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
|
+
}
|
package/src/commands/browser.mjs
CHANGED
|
@@ -139,7 +139,7 @@ async function resolveVisibleTargetPoint(profileId, selector, options = {}) {
|
|
|
139
139
|
};
|
|
140
140
|
if (highlight) {
|
|
141
141
|
try {
|
|
142
|
-
const id = '
|
|
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('
|
|
1124
|
+
const overlay = document.getElementById('camo-highlight-overlay');
|
|
1136
1125
|
if (overlay) overlay.remove();
|
|
1137
|
-
document.querySelectorAll('[data-
|
|
1138
|
-
el.style.outline = el.dataset.
|
|
1139
|
-
delete el.dataset.
|
|
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
|
})()`
|
package/src/commands/mouse.mjs
CHANGED
|
@@ -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 === '
|
|
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 <
|
|
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 {
|
|
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.
|
|
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
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
254
|
+
'container-library not found. Set CAMO_CONTAINER_ROOT or run `camo config repo-root <path>` first.',
|
|
255
255
|
);
|
|
256
256
|
}
|
|
257
257
|
|
package/src/core/actions.mjs
CHANGED
|
@@ -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}`);
|
package/src/core/index.mjs
CHANGED
package/src/lifecycle/lock.mjs
CHANGED
|
@@ -48,9 +48,13 @@ export function acquireLock(profileId, sessionInfo = {}) {
|
|
|
48
48
|
|
|
49
49
|
export function releaseLock(profileId) {
|
|
50
50
|
const lockFile = getLockFile(profileId);
|
|
51
|
-
|
|
52
|
-
fs.
|
|
53
|
-
|
|
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
|
}
|