@web-auto/camo 0.1.3 → 0.1.5
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 +142 -1
- package/package.json +2 -1
- package/scripts/check-file-size.mjs +80 -0
- package/scripts/file-size-policy.json +8 -0
- package/src/autoscript/action-providers/index.mjs +9 -0
- package/src/autoscript/action-providers/xhs/comments.mjs +412 -0
- package/src/autoscript/action-providers/xhs/common.mjs +77 -0
- package/src/autoscript/action-providers/xhs/detail.mjs +181 -0
- package/src/autoscript/action-providers/xhs/interaction.mjs +466 -0
- package/src/autoscript/action-providers/xhs/like-rules.mjs +57 -0
- package/src/autoscript/action-providers/xhs/persistence.mjs +167 -0
- package/src/autoscript/action-providers/xhs/search.mjs +174 -0
- package/src/autoscript/action-providers/xhs.mjs +133 -0
- package/src/autoscript/impact-engine.mjs +78 -0
- package/src/autoscript/runtime.mjs +1015 -0
- package/src/autoscript/schema.mjs +370 -0
- package/src/autoscript/xhs-unified-template.mjs +931 -0
- package/src/cli.mjs +185 -79
- package/src/commands/autoscript.mjs +1100 -0
- package/src/commands/browser.mjs +215 -5
- package/src/commands/container.mjs +298 -75
- package/src/commands/events.mjs +152 -0
- package/src/commands/lifecycle.mjs +17 -3
- package/src/commands/window.mjs +94 -2
- package/src/container/change-notifier.mjs +165 -24
- package/src/container/element-filter.mjs +51 -5
- package/src/container/runtime-core/checkpoint.mjs +195 -0
- package/src/container/runtime-core/index.mjs +21 -0
- package/src/container/runtime-core/operations/index.mjs +351 -0
- package/src/container/runtime-core/operations/selector-scripts.mjs +68 -0
- package/src/container/runtime-core/operations/tab-pool.mjs +544 -0
- package/src/container/runtime-core/operations/viewport.mjs +143 -0
- package/src/container/runtime-core/subscription.mjs +87 -0
- package/src/container/runtime-core/utils.mjs +94 -0
- package/src/container/runtime-core/validation.mjs +127 -0
- package/src/container/runtime-core.mjs +1 -0
- package/src/container/subscription-registry.mjs +459 -0
- package/src/core/actions.mjs +573 -0
- package/src/core/browser.mjs +270 -0
- package/src/core/index.mjs +53 -0
- package/src/core/utils.mjs +87 -0
- package/src/events/daemon-entry.mjs +33 -0
- package/src/events/daemon.mjs +80 -0
- package/src/events/progress-log.mjs +109 -0
- package/src/events/ws-server.mjs +239 -0
- package/src/lib/client.mjs +8 -5
- package/src/lifecycle/session-registry.mjs +8 -4
- package/src/lifecycle/session-watchdog.mjs +267 -0
- package/src/utils/browser-service.mjs +232 -9
- package/src/utils/config.mjs +58 -2
- package/src/utils/help.mjs +28 -4
package/src/commands/browser.mjs
CHANGED
|
@@ -1,9 +1,144 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
listProfiles,
|
|
4
|
+
getDefaultProfile,
|
|
5
|
+
getProfileWindowSize,
|
|
6
|
+
setProfileWindowSize,
|
|
7
|
+
} from '../utils/config.mjs';
|
|
3
8
|
import { callAPI, ensureCamoufox, ensureBrowserService, getSessionByProfile, checkBrowserService } from '../utils/browser-service.mjs';
|
|
4
9
|
import { resolveProfileId, ensureUrlScheme, looksLikeUrlToken, getPositionals } from '../utils/args.mjs';
|
|
5
10
|
import { acquireLock, releaseLock, isLocked, getLockInfo, cleanupStaleLocks } from '../lifecycle/lock.mjs';
|
|
6
11
|
import { registerSession, updateSession, getSessionInfo, unregisterSession, listRegisteredSessions, markSessionClosed, cleanupStaleSessions, recoverSession } from '../lifecycle/session-registry.mjs';
|
|
12
|
+
import { startSessionWatchdog, stopAllSessionWatchdogs, stopSessionWatchdog } from '../lifecycle/session-watchdog.mjs';
|
|
13
|
+
|
|
14
|
+
const START_WINDOW_MIN_WIDTH = 960;
|
|
15
|
+
const START_WINDOW_MIN_HEIGHT = 700;
|
|
16
|
+
const START_WINDOW_MAX_RESERVE = 240;
|
|
17
|
+
const START_WINDOW_DEFAULT_RESERVE = 72;
|
|
18
|
+
|
|
19
|
+
function sleep(ms) {
|
|
20
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function parseNumber(value, fallback = 0) {
|
|
24
|
+
const parsed = Number(value);
|
|
25
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function clamp(value, min, max) {
|
|
29
|
+
return Math.min(Math.max(value, min), max);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function computeTargetViewportFromWindowMetrics(measured) {
|
|
33
|
+
const innerWidth = Math.max(320, parseNumber(measured?.innerWidth, 0));
|
|
34
|
+
const innerHeight = Math.max(240, parseNumber(measured?.innerHeight, 0));
|
|
35
|
+
const outerWidth = Math.max(320, parseNumber(measured?.outerWidth, innerWidth));
|
|
36
|
+
const outerHeight = Math.max(240, parseNumber(measured?.outerHeight, innerHeight));
|
|
37
|
+
|
|
38
|
+
const rawDeltaW = Math.max(0, outerWidth - innerWidth);
|
|
39
|
+
const rawDeltaH = Math.max(0, outerHeight - innerHeight);
|
|
40
|
+
const frameW = rawDeltaW > 400 ? 16 : Math.min(rawDeltaW, 120);
|
|
41
|
+
const frameH = rawDeltaH > 400 ? 88 : Math.min(rawDeltaH, 180);
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
width: Math.max(320, outerWidth - frameW),
|
|
45
|
+
height: Math.max(240, outerHeight - frameH),
|
|
46
|
+
frameW,
|
|
47
|
+
frameH,
|
|
48
|
+
innerWidth,
|
|
49
|
+
innerHeight,
|
|
50
|
+
outerWidth,
|
|
51
|
+
outerHeight,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function computeStartWindowSize(metrics, options = {}) {
|
|
56
|
+
const display = metrics?.metrics || metrics || {};
|
|
57
|
+
const reserveFromEnv = parseNumber(process.env.CAMO_DEFAULT_WINDOW_VERTICAL_RESERVE, START_WINDOW_DEFAULT_RESERVE);
|
|
58
|
+
const reserve = clamp(
|
|
59
|
+
parseNumber(options.reservePx, reserveFromEnv),
|
|
60
|
+
0,
|
|
61
|
+
START_WINDOW_MAX_RESERVE,
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const workWidth = parseNumber(display.workWidth, 0);
|
|
65
|
+
const workHeight = parseNumber(display.workHeight, 0);
|
|
66
|
+
const width = parseNumber(display.width, 0);
|
|
67
|
+
const height = parseNumber(display.height, 0);
|
|
68
|
+
const baseW = Math.floor(workWidth > 0 ? workWidth : width);
|
|
69
|
+
const baseH = Math.floor(workHeight > 0 ? workHeight : height);
|
|
70
|
+
|
|
71
|
+
if (baseW <= 0 || baseH <= 0) {
|
|
72
|
+
return {
|
|
73
|
+
width: 1920,
|
|
74
|
+
height: 1000,
|
|
75
|
+
reservePx: reserve,
|
|
76
|
+
source: 'fallback',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
width: Math.max(START_WINDOW_MIN_WIDTH, baseW),
|
|
82
|
+
height: Math.max(START_WINDOW_MIN_HEIGHT, baseH - reserve),
|
|
83
|
+
reservePx: reserve,
|
|
84
|
+
source: workWidth > 0 || workHeight > 0 ? 'workArea' : 'screen',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function probeWindowMetrics(profileId) {
|
|
89
|
+
const measured = await callAPI('evaluate', {
|
|
90
|
+
profileId,
|
|
91
|
+
script: '({ innerWidth: window.innerWidth, innerHeight: window.innerHeight, outerWidth: window.outerWidth, outerHeight: window.outerHeight })',
|
|
92
|
+
});
|
|
93
|
+
return measured?.result || {};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function syncWindowViewportAfterResize(profileId, width, height, options = {}) {
|
|
97
|
+
const settleMs = Math.max(40, parseNumber(options.settleMs, 120));
|
|
98
|
+
const attempts = Math.max(1, Math.min(8, Math.floor(parseNumber(options.attempts, 4))));
|
|
99
|
+
const tolerancePx = Math.max(0, parseNumber(options.tolerancePx, 3));
|
|
100
|
+
|
|
101
|
+
const windowResult = await callAPI('window:resize', { profileId, width, height });
|
|
102
|
+
await sleep(settleMs);
|
|
103
|
+
|
|
104
|
+
let measured = {};
|
|
105
|
+
let verified = {};
|
|
106
|
+
let viewport = null;
|
|
107
|
+
let matched = false;
|
|
108
|
+
let target = { width: 1280, height: 720, frameW: 16, frameH: 88 };
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < attempts; i += 1) {
|
|
111
|
+
measured = await probeWindowMetrics(profileId);
|
|
112
|
+
target = computeTargetViewportFromWindowMetrics(measured);
|
|
113
|
+
viewport = await callAPI('page:setViewport', {
|
|
114
|
+
profileId,
|
|
115
|
+
width: target.width,
|
|
116
|
+
height: target.height,
|
|
117
|
+
});
|
|
118
|
+
await sleep(settleMs);
|
|
119
|
+
verified = await probeWindowMetrics(profileId);
|
|
120
|
+
const dw = Math.abs(parseNumber(verified?.innerWidth, 0) - target.width);
|
|
121
|
+
const dh = Math.abs(parseNumber(verified?.innerHeight, 0) - target.height);
|
|
122
|
+
if (dw <= tolerancePx && dh <= tolerancePx) {
|
|
123
|
+
matched = true;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
window: windowResult,
|
|
130
|
+
measured,
|
|
131
|
+
verified,
|
|
132
|
+
targetViewport: {
|
|
133
|
+
width: target.width,
|
|
134
|
+
height: target.height,
|
|
135
|
+
frameW: target.frameW,
|
|
136
|
+
frameH: target.frameH,
|
|
137
|
+
matched,
|
|
138
|
+
},
|
|
139
|
+
viewport,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
7
142
|
|
|
8
143
|
export async function handleStartCommand(args) {
|
|
9
144
|
ensureCamoufox();
|
|
@@ -13,6 +148,19 @@ export async function handleStartCommand(args) {
|
|
|
13
148
|
|
|
14
149
|
const urlIdx = args.indexOf('--url');
|
|
15
150
|
const explicitUrl = urlIdx >= 0 ? args[urlIdx + 1] : undefined;
|
|
151
|
+
const widthIdx = args.indexOf('--width');
|
|
152
|
+
const heightIdx = args.indexOf('--height');
|
|
153
|
+
const explicitWidth = widthIdx >= 0 ? parseNumber(args[widthIdx + 1], NaN) : NaN;
|
|
154
|
+
const explicitHeight = heightIdx >= 0 ? parseNumber(args[heightIdx + 1], NaN) : NaN;
|
|
155
|
+
const hasExplicitWidth = Number.isFinite(explicitWidth);
|
|
156
|
+
const hasExplicitHeight = Number.isFinite(explicitHeight);
|
|
157
|
+
if (hasExplicitWidth !== hasExplicitHeight) {
|
|
158
|
+
throw new Error('Usage: camo start [profileId] [--url <url>] [--headless] [--width <w> --height <h>]');
|
|
159
|
+
}
|
|
160
|
+
if ((hasExplicitWidth && explicitWidth < START_WINDOW_MIN_WIDTH) || (hasExplicitHeight && explicitHeight < START_WINDOW_MIN_HEIGHT)) {
|
|
161
|
+
throw new Error(`Window size too small. Minimum is ${START_WINDOW_MIN_WIDTH}x${START_WINDOW_MIN_HEIGHT}`);
|
|
162
|
+
}
|
|
163
|
+
const hasExplicitWindowSize = hasExplicitWidth && hasExplicitHeight;
|
|
16
164
|
const profileSet = new Set(listProfiles());
|
|
17
165
|
let implicitUrl;
|
|
18
166
|
|
|
@@ -20,6 +168,7 @@ export async function handleStartCommand(args) {
|
|
|
20
168
|
for (let i = 1; i < args.length; i++) {
|
|
21
169
|
const arg = args[i];
|
|
22
170
|
if (arg === '--url') { i++; continue; }
|
|
171
|
+
if (arg === '--width' || arg === '--height') { i++; continue; }
|
|
23
172
|
if (arg === '--headless') continue;
|
|
24
173
|
if (arg.startsWith('--')) continue;
|
|
25
174
|
|
|
@@ -56,6 +205,7 @@ export async function handleStartCommand(args) {
|
|
|
56
205
|
message: 'Session already running',
|
|
57
206
|
url: existing.current_url,
|
|
58
207
|
}, null, 2));
|
|
208
|
+
startSessionWatchdog(profileId);
|
|
59
209
|
return;
|
|
60
210
|
}
|
|
61
211
|
|
|
@@ -84,6 +234,53 @@ export async function handleStartCommand(args) {
|
|
|
84
234
|
url: targetUrl,
|
|
85
235
|
headless,
|
|
86
236
|
});
|
|
237
|
+
startSessionWatchdog(profileId);
|
|
238
|
+
|
|
239
|
+
if (!headless) {
|
|
240
|
+
let windowTarget = null;
|
|
241
|
+
if (hasExplicitWindowSize) {
|
|
242
|
+
windowTarget = {
|
|
243
|
+
width: Math.floor(explicitWidth),
|
|
244
|
+
height: Math.floor(explicitHeight),
|
|
245
|
+
source: 'explicit',
|
|
246
|
+
};
|
|
247
|
+
} else {
|
|
248
|
+
const rememberedWindow = getProfileWindowSize(profileId);
|
|
249
|
+
if (rememberedWindow) {
|
|
250
|
+
windowTarget = {
|
|
251
|
+
width: rememberedWindow.width,
|
|
252
|
+
height: rememberedWindow.height,
|
|
253
|
+
source: 'profile',
|
|
254
|
+
updatedAt: rememberedWindow.updatedAt,
|
|
255
|
+
};
|
|
256
|
+
} else {
|
|
257
|
+
const display = await callAPI('system:display', {}).catch(() => null);
|
|
258
|
+
windowTarget = computeStartWindowSize(display);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
result.startWindow = {
|
|
263
|
+
width: windowTarget.width,
|
|
264
|
+
height: windowTarget.height,
|
|
265
|
+
source: windowTarget.source,
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const syncResult = await syncWindowViewportAfterResize(
|
|
269
|
+
profileId,
|
|
270
|
+
windowTarget.width,
|
|
271
|
+
windowTarget.height,
|
|
272
|
+
).catch((err) => ({ error: err?.message || String(err) }));
|
|
273
|
+
result.windowSync = syncResult;
|
|
274
|
+
|
|
275
|
+
const measuredOuterWidth = Number(syncResult?.verified?.outerWidth);
|
|
276
|
+
const measuredOuterHeight = Number(syncResult?.verified?.outerHeight);
|
|
277
|
+
const savedWindow = setProfileWindowSize(
|
|
278
|
+
profileId,
|
|
279
|
+
Number.isFinite(measuredOuterWidth) ? measuredOuterWidth : windowTarget.width,
|
|
280
|
+
Number.isFinite(measuredOuterHeight) ? measuredOuterHeight : windowTarget.height,
|
|
281
|
+
);
|
|
282
|
+
result.profileWindow = savedWindow?.window || null;
|
|
283
|
+
}
|
|
87
284
|
}
|
|
88
285
|
console.log(JSON.stringify(result, null, 2));
|
|
89
286
|
}
|
|
@@ -92,10 +289,20 @@ export async function handleStopCommand(args) {
|
|
|
92
289
|
await ensureBrowserService();
|
|
93
290
|
const profileId = resolveProfileId(args, 1, getDefaultProfile);
|
|
94
291
|
if (!profileId) throw new Error('Usage: camo stop [profileId]');
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
292
|
+
|
|
293
|
+
let result = null;
|
|
294
|
+
let stopError = null;
|
|
295
|
+
try {
|
|
296
|
+
result = await callAPI('stop', { profileId });
|
|
297
|
+
} catch (err) {
|
|
298
|
+
stopError = err;
|
|
299
|
+
} finally {
|
|
300
|
+
stopSessionWatchdog(profileId);
|
|
301
|
+
releaseLock(profileId);
|
|
302
|
+
markSessionClosed(profileId);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (stopError) throw stopError;
|
|
99
306
|
console.log(JSON.stringify(result, null, 2));
|
|
100
307
|
}
|
|
101
308
|
|
|
@@ -405,6 +612,7 @@ export async function handleShutdownCommand() {
|
|
|
405
612
|
} catch {
|
|
406
613
|
// Best effort cleanup
|
|
407
614
|
}
|
|
615
|
+
stopSessionWatchdog(session.profileId);
|
|
408
616
|
releaseLock(session.profileId);
|
|
409
617
|
markSessionClosed(session.profileId);
|
|
410
618
|
}
|
|
@@ -413,10 +621,12 @@ export async function handleShutdownCommand() {
|
|
|
413
621
|
const registered = listRegisteredSessions();
|
|
414
622
|
for (const reg of registered) {
|
|
415
623
|
if (reg.status !== 'closed') {
|
|
624
|
+
stopSessionWatchdog(reg.profileId);
|
|
416
625
|
markSessionClosed(reg.profileId);
|
|
417
626
|
releaseLock(reg.profileId);
|
|
418
627
|
}
|
|
419
628
|
}
|
|
629
|
+
stopAllSessionWatchdogs();
|
|
420
630
|
|
|
421
631
|
const result = await callAPI('service:shutdown', {});
|
|
422
632
|
console.log(JSON.stringify(result, null, 2));
|