@web-auto/camo 0.1.3 → 0.1.4
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 +137 -0
- 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 +20 -4
- 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 +32 -1
- 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 +220 -0
- package/src/utils/browser-service.mjs +232 -9
- package/src/utils/help.mjs +26 -3
package/src/commands/browser.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { callAPI, ensureCamoufox, ensureBrowserService, getSessionByProfile, che
|
|
|
4
4
|
import { resolveProfileId, ensureUrlScheme, looksLikeUrlToken, getPositionals } from '../utils/args.mjs';
|
|
5
5
|
import { acquireLock, releaseLock, isLocked, getLockInfo, cleanupStaleLocks } from '../lifecycle/lock.mjs';
|
|
6
6
|
import { registerSession, updateSession, getSessionInfo, unregisterSession, listRegisteredSessions, markSessionClosed, cleanupStaleSessions, recoverSession } from '../lifecycle/session-registry.mjs';
|
|
7
|
+
import { startSessionWatchdog, stopAllSessionWatchdogs, stopSessionWatchdog } from '../lifecycle/session-watchdog.mjs';
|
|
7
8
|
|
|
8
9
|
export async function handleStartCommand(args) {
|
|
9
10
|
ensureCamoufox();
|
|
@@ -56,6 +57,7 @@ export async function handleStartCommand(args) {
|
|
|
56
57
|
message: 'Session already running',
|
|
57
58
|
url: existing.current_url,
|
|
58
59
|
}, null, 2));
|
|
60
|
+
startSessionWatchdog(profileId);
|
|
59
61
|
return;
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -84,6 +86,7 @@ export async function handleStartCommand(args) {
|
|
|
84
86
|
url: targetUrl,
|
|
85
87
|
headless,
|
|
86
88
|
});
|
|
89
|
+
startSessionWatchdog(profileId);
|
|
87
90
|
}
|
|
88
91
|
console.log(JSON.stringify(result, null, 2));
|
|
89
92
|
}
|
|
@@ -92,10 +95,20 @@ export async function handleStopCommand(args) {
|
|
|
92
95
|
await ensureBrowserService();
|
|
93
96
|
const profileId = resolveProfileId(args, 1, getDefaultProfile);
|
|
94
97
|
if (!profileId) throw new Error('Usage: camo stop [profileId]');
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
|
|
99
|
+
let result = null;
|
|
100
|
+
let stopError = null;
|
|
101
|
+
try {
|
|
102
|
+
result = await callAPI('stop', { profileId });
|
|
103
|
+
} catch (err) {
|
|
104
|
+
stopError = err;
|
|
105
|
+
} finally {
|
|
106
|
+
stopSessionWatchdog(profileId);
|
|
107
|
+
releaseLock(profileId);
|
|
108
|
+
markSessionClosed(profileId);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (stopError) throw stopError;
|
|
99
112
|
console.log(JSON.stringify(result, null, 2));
|
|
100
113
|
}
|
|
101
114
|
|
|
@@ -405,6 +418,7 @@ export async function handleShutdownCommand() {
|
|
|
405
418
|
} catch {
|
|
406
419
|
// Best effort cleanup
|
|
407
420
|
}
|
|
421
|
+
stopSessionWatchdog(session.profileId);
|
|
408
422
|
releaseLock(session.profileId);
|
|
409
423
|
markSessionClosed(session.profileId);
|
|
410
424
|
}
|
|
@@ -413,10 +427,12 @@ export async function handleShutdownCommand() {
|
|
|
413
427
|
const registered = listRegisteredSessions();
|
|
414
428
|
for (const reg of registered) {
|
|
415
429
|
if (reg.status !== 'closed') {
|
|
430
|
+
stopSessionWatchdog(reg.profileId);
|
|
416
431
|
markSessionClosed(reg.profileId);
|
|
417
432
|
releaseLock(reg.profileId);
|
|
418
433
|
}
|
|
419
434
|
}
|
|
435
|
+
stopAllSessionWatchdogs();
|
|
420
436
|
|
|
421
437
|
const result = await callAPI('service:shutdown', {});
|
|
422
438
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -1,124 +1,328 @@
|
|
|
1
|
-
// Container commands - filter and
|
|
2
|
-
import {
|
|
3
|
-
import { callAPI, getSessionByProfile } from '../utils/browser-service.mjs';
|
|
1
|
+
// Container commands - filter, watch, and subscription targets
|
|
2
|
+
import { getDomSnapshotByProfile, getSessionByProfile, getViewportByProfile } from '../utils/browser-service.mjs';
|
|
4
3
|
import { getDefaultProfile } from '../utils/config.mjs';
|
|
5
4
|
import { getChangeNotifier } from '../container/change-notifier.mjs';
|
|
6
5
|
import { createElementFilter } from '../container/element-filter.mjs';
|
|
6
|
+
import {
|
|
7
|
+
getRegisteredTargets,
|
|
8
|
+
initContainerSubscriptionDirectory,
|
|
9
|
+
listSubscriptionSets,
|
|
10
|
+
registerSubscriptionTargets,
|
|
11
|
+
} from '../container/subscription-registry.mjs';
|
|
12
|
+
import { safeAppendProgressEvent } from '../events/progress-log.mjs';
|
|
7
13
|
|
|
8
14
|
const notifier = getChangeNotifier();
|
|
9
15
|
const elementFilter = createElementFilter();
|
|
10
16
|
|
|
11
|
-
|
|
12
|
-
|
|
17
|
+
const VALUE_FLAGS = new Set([
|
|
18
|
+
'--profile',
|
|
19
|
+
'-p',
|
|
20
|
+
'--selector',
|
|
21
|
+
'-s',
|
|
22
|
+
'--throttle',
|
|
23
|
+
'-t',
|
|
24
|
+
'--source',
|
|
25
|
+
'--site',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
function readFlagValue(args, names) {
|
|
29
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
30
|
+
if (!names.includes(args[i])) continue;
|
|
31
|
+
const value = args[i + 1];
|
|
32
|
+
if (!value || String(value).startsWith('-')) return null;
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function collectPositionals(args, startIndex = 2) {
|
|
39
|
+
const out = [];
|
|
40
|
+
for (let i = startIndex; i < args.length; i += 1) {
|
|
41
|
+
const arg = args[i];
|
|
42
|
+
if (!arg) continue;
|
|
43
|
+
if (VALUE_FLAGS.has(arg)) {
|
|
44
|
+
i += 1;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (String(arg).startsWith('-')) continue;
|
|
48
|
+
out.push(arg);
|
|
49
|
+
}
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function ensureSession(profileId) {
|
|
13
54
|
const session = await getSessionByProfile(profileId);
|
|
14
55
|
if (!session) {
|
|
15
56
|
throw new Error(`No active session for profile: ${profileId || 'default'}`);
|
|
16
57
|
}
|
|
58
|
+
return session;
|
|
59
|
+
}
|
|
17
60
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
61
|
+
function resolveWatchProfileAndSelectors(args) {
|
|
62
|
+
const explicitProfile = readFlagValue(args, ['--profile', '-p']);
|
|
63
|
+
const explicitSelector = readFlagValue(args, ['--selector', '-s']);
|
|
64
|
+
const positionals = collectPositionals(args);
|
|
65
|
+
|
|
66
|
+
if (explicitSelector) {
|
|
67
|
+
const profileId = explicitProfile || positionals[0] || getDefaultProfile();
|
|
68
|
+
return { profileId, selectors: [explicitSelector], source: 'manual' };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (explicitProfile) {
|
|
72
|
+
return {
|
|
73
|
+
profileId: explicitProfile,
|
|
74
|
+
selectors: positionals,
|
|
75
|
+
source: positionals.length > 0 ? 'manual' : 'subscription',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (positionals.length >= 2) {
|
|
80
|
+
return { profileId: positionals[0], selectors: positionals.slice(1), source: 'manual' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (positionals.length === 1) {
|
|
84
|
+
const candidateProfile = getRegisteredTargets(positionals[0])?.profile;
|
|
85
|
+
if (candidateProfile) {
|
|
86
|
+
return { profileId: positionals[0], selectors: [], source: 'subscription' };
|
|
87
|
+
}
|
|
88
|
+
return { profileId: getDefaultProfile(), selectors: [positionals[0]], source: 'manual' };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { profileId: getDefaultProfile(), selectors: [], source: 'subscription' };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function resolveProfileAndSelectors(args) {
|
|
95
|
+
const explicitProfile = readFlagValue(args, ['--profile', '-p']);
|
|
96
|
+
const positionals = collectPositionals(args);
|
|
97
|
+
if (explicitProfile) {
|
|
98
|
+
return { profileId: explicitProfile, selectors: positionals };
|
|
99
|
+
}
|
|
100
|
+
if (positionals.length >= 2) {
|
|
101
|
+
return { profileId: positionals[0], selectors: positionals.slice(1) };
|
|
102
|
+
}
|
|
103
|
+
return { profileId: getDefaultProfile(), selectors: positionals };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function resolveListProfile(args) {
|
|
107
|
+
const explicitProfile = readFlagValue(args, ['--profile', '-p']);
|
|
108
|
+
if (explicitProfile) return explicitProfile;
|
|
109
|
+
const positionals = collectPositionals(args);
|
|
110
|
+
return positionals[0] || getDefaultProfile();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function handleContainerInitCommand(args) {
|
|
114
|
+
const source = readFlagValue(args, ['--source']);
|
|
115
|
+
const force = args.includes('--force');
|
|
116
|
+
const result = initContainerSubscriptionDirectory({
|
|
117
|
+
...(source ? { containerLibraryRoot: source } : {}),
|
|
118
|
+
force,
|
|
119
|
+
});
|
|
120
|
+
console.log(JSON.stringify(result, null, 2));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function handleContainerSetsCommand(args) {
|
|
124
|
+
const site = readFlagValue(args, ['--site']);
|
|
125
|
+
const result = listSubscriptionSets({ ...(site ? { site } : {}) });
|
|
126
|
+
console.log(JSON.stringify(result, null, 2));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export async function handleContainerRegisterCommand(args) {
|
|
130
|
+
const explicitProfile = readFlagValue(args, ['--profile', '-p']);
|
|
131
|
+
const append = args.includes('--append');
|
|
132
|
+
const positionals = collectPositionals(args);
|
|
133
|
+
|
|
134
|
+
let profileId;
|
|
135
|
+
let setIds;
|
|
136
|
+
if (explicitProfile) {
|
|
137
|
+
profileId = explicitProfile;
|
|
138
|
+
setIds = positionals;
|
|
139
|
+
} else if (positionals.length >= 2) {
|
|
140
|
+
profileId = positionals[0];
|
|
141
|
+
setIds = positionals.slice(1);
|
|
142
|
+
} else {
|
|
143
|
+
profileId = getDefaultProfile();
|
|
144
|
+
setIds = positionals;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (!profileId) {
|
|
148
|
+
throw new Error('Usage: camo container register [profileId] <setId...> [--append] [--profile <id>]');
|
|
149
|
+
}
|
|
150
|
+
if (!Array.isArray(setIds) || setIds.length === 0) {
|
|
151
|
+
throw new Error('Usage: camo container register [profileId] <setId...> [--append] [--profile <id>]');
|
|
24
152
|
}
|
|
25
153
|
|
|
154
|
+
const result = registerSubscriptionTargets(profileId, setIds, { append });
|
|
155
|
+
console.log(JSON.stringify(result, null, 2));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export async function handleContainerTargetsCommand(args) {
|
|
159
|
+
const explicitProfile = readFlagValue(args, ['--profile', '-p']);
|
|
160
|
+
const positionals = collectPositionals(args);
|
|
161
|
+
const profileId = explicitProfile || positionals[0] || null;
|
|
162
|
+
const result = getRegisteredTargets(profileId);
|
|
163
|
+
console.log(JSON.stringify(result, null, 2));
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export async function handleContainerFilterCommand(args) {
|
|
167
|
+
const { profileId, selectors } = resolveProfileAndSelectors(args);
|
|
168
|
+
if (!profileId) {
|
|
169
|
+
throw new Error('Usage: camo container filter [profileId] <selector...> [--profile <id>]');
|
|
170
|
+
}
|
|
26
171
|
if (selectors.length === 0) {
|
|
27
|
-
throw new Error('Usage: camo container filter <selector
|
|
172
|
+
throw new Error('Usage: camo container filter [profileId] <selector...> [--profile <id>]');
|
|
28
173
|
}
|
|
29
174
|
|
|
30
|
-
|
|
31
|
-
const
|
|
32
|
-
const snapshot = result.dom_tree || result;
|
|
175
|
+
const session = await ensureSession(profileId);
|
|
176
|
+
const snapshot = await getDomSnapshotByProfile(session.profileId || profileId);
|
|
33
177
|
|
|
34
|
-
// Filter elements
|
|
35
178
|
const matched = [];
|
|
36
179
|
for (const selector of selectors) {
|
|
37
180
|
const elements = notifier.findElements(snapshot, { css: selector });
|
|
38
|
-
matched.push(
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
181
|
+
matched.push(
|
|
182
|
+
...elements.map((element) => ({
|
|
183
|
+
selector,
|
|
184
|
+
path: element.path,
|
|
185
|
+
tag: element.tag,
|
|
186
|
+
id: element.id,
|
|
187
|
+
classes: element.classes,
|
|
188
|
+
text: (element.textSnippet || element.text || '').slice(0, 80),
|
|
189
|
+
})),
|
|
190
|
+
);
|
|
45
191
|
}
|
|
46
192
|
|
|
47
|
-
console.log(JSON.stringify({ ok: true, count: matched.length, elements: matched }, null, 2));
|
|
193
|
+
console.log(JSON.stringify({ ok: true, profileId: session.profileId || profileId, count: matched.length, elements: matched }, null, 2));
|
|
48
194
|
}
|
|
49
195
|
|
|
50
196
|
export async function handleContainerWatchCommand(args) {
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
if (!
|
|
54
|
-
throw new Error(
|
|
197
|
+
const watchRequest = resolveWatchProfileAndSelectors(args);
|
|
198
|
+
const profileId = watchRequest.profileId;
|
|
199
|
+
if (!profileId) {
|
|
200
|
+
throw new Error(
|
|
201
|
+
'Usage: camo container watch [profileId] [--selector <css>|<selector...>] [--throttle ms] [--profile <id>]',
|
|
202
|
+
);
|
|
55
203
|
}
|
|
56
204
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
205
|
+
let selectors = Array.from(
|
|
206
|
+
new Set(
|
|
207
|
+
(watchRequest.selectors || [])
|
|
208
|
+
.map((item) => String(item || '').trim())
|
|
209
|
+
.filter(Boolean),
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
let source = watchRequest.source;
|
|
213
|
+
|
|
214
|
+
if (selectors.length === 0) {
|
|
215
|
+
const registered = getRegisteredTargets(profileId)?.profile;
|
|
216
|
+
selectors = Array.from(
|
|
217
|
+
new Set(
|
|
218
|
+
(registered?.selectors || [])
|
|
219
|
+
.map((item) => item?.css)
|
|
220
|
+
.filter((css) => typeof css === 'string' && css.trim())
|
|
221
|
+
.map((css) => css.trim()),
|
|
222
|
+
),
|
|
223
|
+
);
|
|
224
|
+
source = 'subscription';
|
|
225
|
+
}
|
|
64
226
|
|
|
65
|
-
if (
|
|
66
|
-
throw new Error(
|
|
227
|
+
if (selectors.length === 0) {
|
|
228
|
+
throw new Error(
|
|
229
|
+
`No selectors found for profile: ${profileId}. Use --selector <css> or run camo container register ${profileId} <setId...> first.`,
|
|
230
|
+
);
|
|
67
231
|
}
|
|
68
232
|
|
|
69
|
-
|
|
233
|
+
const session = await ensureSession(profileId);
|
|
234
|
+
const throttleRaw = readFlagValue(args, ['--throttle', '-t']);
|
|
235
|
+
const throttle = Math.max(100, Number(throttleRaw) || 500);
|
|
236
|
+
|
|
237
|
+
console.log(JSON.stringify({
|
|
238
|
+
ok: true,
|
|
239
|
+
message: `Watching ${selectors.length} selector(s) from ${source}`,
|
|
240
|
+
selectors,
|
|
241
|
+
profileId: session.profileId || profileId,
|
|
242
|
+
throttle,
|
|
243
|
+
}));
|
|
244
|
+
safeAppendProgressEvent({
|
|
245
|
+
source: 'container.watch',
|
|
246
|
+
mode: 'normal',
|
|
247
|
+
profileId: session.profileId || profileId,
|
|
248
|
+
event: 'container.watch.start',
|
|
249
|
+
payload: {
|
|
250
|
+
selectors,
|
|
251
|
+
throttle,
|
|
252
|
+
source,
|
|
253
|
+
},
|
|
254
|
+
});
|
|
70
255
|
|
|
71
|
-
// Setup WebSocket connection for DOM updates
|
|
72
|
-
// For now, poll the browser service
|
|
73
256
|
const interval = setInterval(async () => {
|
|
74
257
|
try {
|
|
75
|
-
const
|
|
76
|
-
const snapshot = result.dom_tree || result;
|
|
258
|
+
const snapshot = await getDomSnapshotByProfile(session.profileId || profileId);
|
|
77
259
|
notifier.processSnapshot(snapshot);
|
|
78
260
|
} catch (err) {
|
|
79
|
-
console.error(JSON.stringify({ ok: false, error: err
|
|
261
|
+
console.error(JSON.stringify({ ok: false, error: err?.message || String(err) }));
|
|
80
262
|
}
|
|
81
263
|
}, throttle);
|
|
82
264
|
|
|
83
|
-
|
|
84
|
-
notifier.watch({ css: selector }, {
|
|
265
|
+
const watchers = selectors.map((selector) => notifier.watch({ css: selector }, {
|
|
85
266
|
onAppear: (elements) => {
|
|
86
267
|
console.log(JSON.stringify({ event: 'appear', selector, count: elements.length, elements }));
|
|
268
|
+
safeAppendProgressEvent({
|
|
269
|
+
source: 'container.watch',
|
|
270
|
+
mode: 'normal',
|
|
271
|
+
profileId: session.profileId || profileId,
|
|
272
|
+
event: 'container.appear',
|
|
273
|
+
payload: { selector, count: elements.length },
|
|
274
|
+
});
|
|
87
275
|
},
|
|
88
276
|
onDisappear: (elements) => {
|
|
89
277
|
console.log(JSON.stringify({ event: 'disappear', selector, count: elements.length }));
|
|
278
|
+
safeAppendProgressEvent({
|
|
279
|
+
source: 'container.watch',
|
|
280
|
+
mode: 'normal',
|
|
281
|
+
profileId: session.profileId || profileId,
|
|
282
|
+
event: 'container.disappear',
|
|
283
|
+
payload: { selector, count: elements.length },
|
|
284
|
+
});
|
|
90
285
|
},
|
|
91
286
|
onChange: ({ appeared, disappeared }) => {
|
|
92
287
|
console.log(JSON.stringify({ event: 'change', selector, appeared: appeared.length, disappeared: disappeared.length }));
|
|
288
|
+
safeAppendProgressEvent({
|
|
289
|
+
source: 'container.watch',
|
|
290
|
+
mode: 'normal',
|
|
291
|
+
profileId: session.profileId || profileId,
|
|
292
|
+
event: 'container.change',
|
|
293
|
+
payload: { selector, appeared: appeared.length, disappeared: disappeared.length },
|
|
294
|
+
});
|
|
93
295
|
},
|
|
94
296
|
throttle,
|
|
95
|
-
});
|
|
297
|
+
}));
|
|
96
298
|
|
|
97
|
-
|
|
98
|
-
process.on('SIGINT', () => {
|
|
299
|
+
process.once('SIGINT', () => {
|
|
99
300
|
clearInterval(interval);
|
|
301
|
+
for (const stopWatch of watchers) stopWatch();
|
|
100
302
|
notifier.destroy();
|
|
101
303
|
console.log(JSON.stringify({ ok: true, message: 'Watch stopped' }));
|
|
304
|
+
safeAppendProgressEvent({
|
|
305
|
+
source: 'container.watch',
|
|
306
|
+
mode: 'normal',
|
|
307
|
+
profileId: session.profileId || profileId,
|
|
308
|
+
event: 'container.watch.stop',
|
|
309
|
+
payload: { selectors },
|
|
310
|
+
});
|
|
102
311
|
process.exit(0);
|
|
103
312
|
});
|
|
104
313
|
}
|
|
105
314
|
|
|
106
315
|
export async function handleContainerListCommand(args) {
|
|
107
|
-
const profileId =
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
throw new Error(`No active session for profile: ${profileId || 'default'}`);
|
|
316
|
+
const profileId = resolveListProfile(args);
|
|
317
|
+
if (!profileId) {
|
|
318
|
+
throw new Error('Usage: camo container list [profileId] [--profile <id>]');
|
|
111
319
|
}
|
|
320
|
+
const session = await ensureSession(profileId);
|
|
112
321
|
|
|
113
|
-
const
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
// Get viewport info
|
|
117
|
-
const viewportResult = await callAPI(`/session/${session.session_id}/viewport`);
|
|
118
|
-
const viewport = viewportResult.viewport || { width: 1280, height: 720 };
|
|
322
|
+
const snapshot = await getDomSnapshotByProfile(session.profileId || profileId);
|
|
323
|
+
const viewport = await getViewportByProfile(session.profileId || profileId);
|
|
119
324
|
|
|
120
|
-
|
|
121
|
-
const collectElements = (node, path = 'root') => {
|
|
325
|
+
const collectElements = (node, domPath = 'root') => {
|
|
122
326
|
const elements = [];
|
|
123
327
|
if (!node) return elements;
|
|
124
328
|
|
|
@@ -126,10 +330,9 @@ export async function handleContainerListCommand(args) {
|
|
|
126
330
|
if (rect && viewport) {
|
|
127
331
|
const inViewport = elementFilter.isInViewport(rect, viewport);
|
|
128
332
|
const visibilityRatio = elementFilter.getVisibilityRatio(rect, viewport);
|
|
129
|
-
|
|
130
333
|
if (inViewport && visibilityRatio > 0.1) {
|
|
131
334
|
elements.push({
|
|
132
|
-
path,
|
|
335
|
+
path: domPath,
|
|
133
336
|
tag: node.tag,
|
|
134
337
|
id: node.id,
|
|
135
338
|
classes: node.classes?.slice(0, 3),
|
|
@@ -139,9 +342,9 @@ export async function handleContainerListCommand(args) {
|
|
|
139
342
|
}
|
|
140
343
|
}
|
|
141
344
|
|
|
142
|
-
if (node.children) {
|
|
143
|
-
for (let i = 0; i < node.children.length; i
|
|
144
|
-
elements.push(...collectElements(node.children[i], `${
|
|
345
|
+
if (Array.isArray(node.children)) {
|
|
346
|
+
for (let i = 0; i < node.children.length; i += 1) {
|
|
347
|
+
elements.push(...collectElements(node.children[i], `${domPath}/${i}`));
|
|
145
348
|
}
|
|
146
349
|
}
|
|
147
350
|
|
|
@@ -149,30 +352,50 @@ export async function handleContainerListCommand(args) {
|
|
|
149
352
|
};
|
|
150
353
|
|
|
151
354
|
const elements = collectElements(snapshot);
|
|
152
|
-
console.log(JSON.stringify({
|
|
355
|
+
console.log(JSON.stringify({
|
|
356
|
+
ok: true,
|
|
357
|
+
profileId: session.profileId || profileId,
|
|
358
|
+
viewport,
|
|
359
|
+
count: elements.length,
|
|
360
|
+
elements: elements.slice(0, 50),
|
|
361
|
+
}, null, 2));
|
|
153
362
|
}
|
|
154
363
|
|
|
155
364
|
export async function handleContainerCommand(args) {
|
|
156
365
|
const sub = args[1];
|
|
157
366
|
|
|
158
367
|
switch (sub) {
|
|
368
|
+
case 'init':
|
|
369
|
+
return handleContainerInitCommand(args);
|
|
370
|
+
case 'sets':
|
|
371
|
+
return handleContainerSetsCommand(args);
|
|
372
|
+
case 'register':
|
|
373
|
+
return handleContainerRegisterCommand(args);
|
|
374
|
+
case 'targets':
|
|
375
|
+
return handleContainerTargetsCommand(args);
|
|
159
376
|
case 'filter':
|
|
160
|
-
return handleContainerFilterCommand(args
|
|
377
|
+
return handleContainerFilterCommand(args);
|
|
161
378
|
case 'watch':
|
|
162
|
-
return handleContainerWatchCommand(args
|
|
379
|
+
return handleContainerWatchCommand(args);
|
|
163
380
|
case 'list':
|
|
164
|
-
return handleContainerListCommand(args
|
|
381
|
+
return handleContainerListCommand(args);
|
|
165
382
|
default:
|
|
166
|
-
console.log(`Usage: camo container <filter|watch|list> [options]
|
|
383
|
+
console.log(`Usage: camo container <init|sets|register|targets|filter|watch|list> [options]
|
|
167
384
|
|
|
168
385
|
Commands:
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
386
|
+
init [--source <container-library-dir>] [--force] Initialize subscription directory and migrate container sets
|
|
387
|
+
sets [--site <siteKey>] List migrated subscription sets
|
|
388
|
+
register [profileId] <setId...> [--append] Register subscription targets for profile
|
|
389
|
+
targets [profileId] Show registered targets
|
|
390
|
+
filter [profileId] <selector...> Filter DOM elements by CSS selector
|
|
391
|
+
watch [profileId] [--selector <css>] [--throttle <ms>] Watch for element changes (defaults to registered selectors)
|
|
392
|
+
list [profileId] List visible elements in viewport
|
|
172
393
|
|
|
173
394
|
Options:
|
|
174
|
-
--profile, -p <id>
|
|
175
|
-
--
|
|
395
|
+
--profile, -p <id> Profile to use
|
|
396
|
+
--selector, -s <css> Selector for watch
|
|
397
|
+
--throttle, -t <ms> Poll interval for watch (default: 500)
|
|
398
|
+
--site <siteKey> Filter set list by site
|
|
176
399
|
`);
|
|
177
400
|
}
|
|
178
401
|
}
|