@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
|
@@ -26,7 +26,230 @@ export async function callAPI(action, payload = {}) {
|
|
|
26
26
|
|
|
27
27
|
export async function getSessionByProfile(profileId) {
|
|
28
28
|
const status = await callAPI('getStatus', {});
|
|
29
|
-
|
|
29
|
+
const activeSession = status?.sessions?.find((s) => s.profileId === profileId) || null;
|
|
30
|
+
if (activeSession) {
|
|
31
|
+
return activeSession;
|
|
32
|
+
}
|
|
33
|
+
if (!profileId) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Some browser-service builds do not populate getStatus.sessions reliably.
|
|
38
|
+
// Fallback to page:list so runtime can still attach to an active profile tab set.
|
|
39
|
+
try {
|
|
40
|
+
const pagePayload = await callAPI('page:list', { profileId });
|
|
41
|
+
const pages = Array.isArray(pagePayload?.pages)
|
|
42
|
+
? pagePayload.pages
|
|
43
|
+
: Array.isArray(pagePayload?.data?.pages)
|
|
44
|
+
? pagePayload.data.pages
|
|
45
|
+
: [];
|
|
46
|
+
if (!pages.length) return null;
|
|
47
|
+
const activeIndex = Number(pagePayload?.activeIndex ?? pagePayload?.data?.activeIndex);
|
|
48
|
+
const activePage = Number.isFinite(activeIndex)
|
|
49
|
+
? pages.find((page) => Number(page?.index) === activeIndex)
|
|
50
|
+
: (pages.find((page) => page?.active) || pages[0]);
|
|
51
|
+
return {
|
|
52
|
+
profileId,
|
|
53
|
+
session_id: profileId,
|
|
54
|
+
sessionId: profileId,
|
|
55
|
+
current_url: activePage?.url || null,
|
|
56
|
+
recoveredFromPages: true,
|
|
57
|
+
};
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildDomSnapshotScript(maxDepth, maxChildren) {
|
|
64
|
+
return `(() => {
|
|
65
|
+
const MAX_DEPTH = ${maxDepth};
|
|
66
|
+
const MAX_CHILDREN = ${maxChildren};
|
|
67
|
+
const viewportWidth = Number(window.innerWidth || 0);
|
|
68
|
+
const viewportHeight = Number(window.innerHeight || 0);
|
|
69
|
+
|
|
70
|
+
const normalizeRect = (rect) => {
|
|
71
|
+
if (!rect) return null;
|
|
72
|
+
const left = Number(rect.left ?? rect.x ?? 0);
|
|
73
|
+
const top = Number(rect.top ?? rect.y ?? 0);
|
|
74
|
+
const width = Number(rect.width ?? 0);
|
|
75
|
+
const height = Number(rect.height ?? 0);
|
|
76
|
+
return {
|
|
77
|
+
left,
|
|
78
|
+
top,
|
|
79
|
+
right: left + width,
|
|
80
|
+
bottom: top + height,
|
|
81
|
+
x: left,
|
|
82
|
+
y: top,
|
|
83
|
+
width,
|
|
84
|
+
height,
|
|
85
|
+
};
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const sanitizeClasses = (el) => {
|
|
89
|
+
const classAttr = typeof el.className === 'string'
|
|
90
|
+
? el.className
|
|
91
|
+
: (el.getAttribute && el.getAttribute('class')) || '';
|
|
92
|
+
return classAttr.split(/\\s+/).filter(Boolean).slice(0, 24);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const collectAttrs = (el) => {
|
|
96
|
+
if (!el || !el.getAttribute) return null;
|
|
97
|
+
const keys = [
|
|
98
|
+
'href',
|
|
99
|
+
'src',
|
|
100
|
+
'name',
|
|
101
|
+
'type',
|
|
102
|
+
'value',
|
|
103
|
+
'placeholder',
|
|
104
|
+
'role',
|
|
105
|
+
'aria-label',
|
|
106
|
+
'aria-hidden',
|
|
107
|
+
'title',
|
|
108
|
+
];
|
|
109
|
+
const attrs = {};
|
|
110
|
+
for (const key of keys) {
|
|
111
|
+
const value = el.getAttribute(key);
|
|
112
|
+
if (value === null || value === undefined || value === '') continue;
|
|
113
|
+
attrs[key] = String(value).slice(0, 400);
|
|
114
|
+
}
|
|
115
|
+
return Object.keys(attrs).length > 0 ? attrs : null;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const inViewport = (rect) => {
|
|
119
|
+
if (!rect) return false;
|
|
120
|
+
if (rect.width <= 0 || rect.height <= 0) return false;
|
|
121
|
+
return (
|
|
122
|
+
rect.right > 0
|
|
123
|
+
&& rect.bottom > 0
|
|
124
|
+
&& rect.left < viewportWidth
|
|
125
|
+
&& rect.top < viewportHeight
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const isRendered = (el) => {
|
|
130
|
+
try {
|
|
131
|
+
const style = window.getComputedStyle(el);
|
|
132
|
+
if (!style) return false;
|
|
133
|
+
if (style.display === 'none') return false;
|
|
134
|
+
if (style.visibility === 'hidden' || style.visibility === 'collapse') return false;
|
|
135
|
+
const opacity = Number.parseFloat(String(style.opacity || '1'));
|
|
136
|
+
if (Number.isFinite(opacity) && opacity <= 0.01) return false;
|
|
137
|
+
return true;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const clampPoint = (value, max) => {
|
|
144
|
+
if (!Number.isFinite(value)) return 0;
|
|
145
|
+
if (max <= 1) return 0;
|
|
146
|
+
return Math.max(0, Math.min(max - 1, value));
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const hitTestVisible = (el, rect) => {
|
|
150
|
+
if (!rect || viewportWidth <= 0 || viewportHeight <= 0) return false;
|
|
151
|
+
const samplePoints = [
|
|
152
|
+
[rect.left + rect.width * 0.5, rect.top + rect.height * 0.5],
|
|
153
|
+
[rect.left + rect.width * 0.2, rect.top + rect.height * 0.2],
|
|
154
|
+
[rect.left + rect.width * 0.8, rect.top + rect.height * 0.8],
|
|
155
|
+
];
|
|
156
|
+
for (const [rawX, rawY] of samplePoints) {
|
|
157
|
+
const x = clampPoint(rawX, viewportWidth);
|
|
158
|
+
const y = clampPoint(rawY, viewportHeight);
|
|
159
|
+
const topEl = document.elementFromPoint(x, y);
|
|
160
|
+
if (!topEl) continue;
|
|
161
|
+
if (topEl === el) return true;
|
|
162
|
+
if (el.contains && el.contains(topEl)) return true;
|
|
163
|
+
if (topEl.contains && topEl.contains(el)) return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const collect = (el, depth = 0, path = 'root') => {
|
|
169
|
+
if (!el || depth > MAX_DEPTH) return null;
|
|
170
|
+
const classes = sanitizeClasses(el);
|
|
171
|
+
const rect = normalizeRect(el.getBoundingClientRect ? el.getBoundingClientRect() : null);
|
|
172
|
+
const tag = String(el.tagName || el.nodeName || '').toLowerCase();
|
|
173
|
+
const id = el.id || null;
|
|
174
|
+
const text = typeof el.textContent === 'string'
|
|
175
|
+
? el.textContent.replace(/\\s+/g, ' ').trim()
|
|
176
|
+
: '';
|
|
177
|
+
const selector = tag
|
|
178
|
+
? \`\${tag}\${id ? '#' + id : ''}\${classes.length ? '.' + classes.slice(0, 3).join('.') : ''}\`
|
|
179
|
+
: null;
|
|
180
|
+
|
|
181
|
+
const node = {
|
|
182
|
+
tag,
|
|
183
|
+
id,
|
|
184
|
+
classes,
|
|
185
|
+
selector,
|
|
186
|
+
path,
|
|
187
|
+
};
|
|
188
|
+
const attrs = collectAttrs(el);
|
|
189
|
+
if (attrs) node.attrs = attrs;
|
|
190
|
+
if (attrs && attrs.href) node.href = attrs.href;
|
|
191
|
+
if (rect) node.rect = rect;
|
|
192
|
+
if (text) node.textSnippet = text.slice(0, 120);
|
|
193
|
+
if (rect) {
|
|
194
|
+
const rendered = isRendered(el);
|
|
195
|
+
const withinViewport = inViewport(rect);
|
|
196
|
+
const visible = rendered && withinViewport && hitTestVisible(el, rect);
|
|
197
|
+
node.visible = visible;
|
|
198
|
+
} else {
|
|
199
|
+
node.visible = false;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const children = Array.from(el.children || []);
|
|
203
|
+
if (children.length > 0 && depth < MAX_DEPTH) {
|
|
204
|
+
node.children = [];
|
|
205
|
+
const limit = Math.min(children.length, MAX_CHILDREN);
|
|
206
|
+
for (let i = 0; i < limit; i += 1) {
|
|
207
|
+
const child = collect(children[i], depth + 1, \`\${path}/\${i}\`);
|
|
208
|
+
if (child) node.children.push(child);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return node;
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const root = collect(document.body || document.documentElement, 0, 'root');
|
|
216
|
+
return {
|
|
217
|
+
dom_tree: root,
|
|
218
|
+
viewport: {
|
|
219
|
+
width: viewportWidth,
|
|
220
|
+
height: viewportHeight,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
223
|
+
})()`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function getDomSnapshotByProfile(profileId, options = {}) {
|
|
227
|
+
const maxDepth = Math.max(1, Math.min(20, Number(options.maxDepth) || 10));
|
|
228
|
+
const maxChildren = Math.max(1, Math.min(500, Number(options.maxChildren) || 120));
|
|
229
|
+
const response = await callAPI('evaluate', {
|
|
230
|
+
profileId,
|
|
231
|
+
script: buildDomSnapshotScript(maxDepth, maxChildren),
|
|
232
|
+
});
|
|
233
|
+
const payload = response?.result || response || {};
|
|
234
|
+
const tree = payload.dom_tree || null;
|
|
235
|
+
if (tree && payload.viewport && typeof payload.viewport === 'object') {
|
|
236
|
+
tree.__viewport = {
|
|
237
|
+
width: Number(payload.viewport.width) || 0,
|
|
238
|
+
height: Number(payload.viewport.height) || 0,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
return tree;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function getViewportByProfile(profileId) {
|
|
245
|
+
const response = await callAPI('evaluate', {
|
|
246
|
+
profileId,
|
|
247
|
+
script: `(() => ({ width: Number(window.innerWidth || 0), height: Number(window.innerHeight || 0) }))()`,
|
|
248
|
+
});
|
|
249
|
+
const viewport = response?.result || response?.viewport || {};
|
|
250
|
+
const width = Number(viewport?.width) || 1280;
|
|
251
|
+
const height = Number(viewport?.height) || 720;
|
|
252
|
+
return { width, height };
|
|
30
253
|
}
|
|
31
254
|
|
|
32
255
|
export async function checkBrowserService() {
|
|
@@ -89,15 +312,15 @@ function scanCommonRepoRoots() {
|
|
|
89
312
|
const roots = [
|
|
90
313
|
path.join(home, 'Documents', 'github'),
|
|
91
314
|
path.join(home, 'github'),
|
|
92
|
-
|
|
315
|
+
path.join(home, 'code'),
|
|
93
316
|
path.join(home, 'projects'),
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
317
|
+
path.join('/Volumes', 'extension', 'code'),
|
|
318
|
+
path.join('C:', 'code'),
|
|
319
|
+
path.join('D:', 'code'),
|
|
320
|
+
path.join('C:', 'projects'),
|
|
321
|
+
path.join('D:', 'projects'),
|
|
322
|
+
path.join('C:', 'Users', os.userInfo().username, 'code'),
|
|
323
|
+
path.join('C:', 'Users', os.userInfo().username, 'projects'),
|
|
101
324
|
path.join('C:', 'Users', os.userInfo().username, 'Documents', 'github'),
|
|
102
325
|
].filter(Boolean);
|
|
103
326
|
|
package/src/utils/help.mjs
CHANGED
|
@@ -115,13 +115,36 @@ EXAMPLES:
|
|
|
115
115
|
camo stop
|
|
116
116
|
|
|
117
117
|
CONTAINER FILTER & SUBSCRIPTION:
|
|
118
|
-
container
|
|
119
|
-
container
|
|
120
|
-
container
|
|
118
|
+
container init [--source <dir>] [--force] Initialize subscription dir + migrate container sets
|
|
119
|
+
container sets [--site <siteKey>] List migrated subscription sets
|
|
120
|
+
container register [profileId] <setId...> Register targets (path / url+dom markers) for profile
|
|
121
|
+
container targets [profileId] Show registered subscription targets
|
|
122
|
+
container filter [profileId] <selector...> Filter DOM elements by CSS selector
|
|
123
|
+
container watch [profileId] [--selector <css>] Watch for element changes (or use registered selectors)
|
|
124
|
+
container list [profileId] List visible elements in viewport
|
|
125
|
+
|
|
126
|
+
AUTOSCRIPT (STRATEGY LAYER):
|
|
127
|
+
autoscript scaffold xhs-unified [--output <file>] Generate xiaohongshu unified-harvest autoscript template
|
|
128
|
+
autoscript validate <file> Validate autoscript schema and references
|
|
129
|
+
autoscript explain <file> Print normalized graph and defaults
|
|
130
|
+
autoscript snapshot <jsonl-file> [--out <snapshot-file>] Build resumable snapshot from run JSONL
|
|
131
|
+
autoscript replay <jsonl-file> [--summary-file <path>] Rebuild summary from run JSONL
|
|
132
|
+
autoscript run <file> [--profile <id>] [--jsonl-file <path>] [--summary-file <path>] Run autoscript runtime
|
|
133
|
+
autoscript resume <file> --snapshot <snapshot-file> [--from-node <nodeId>] [--profile <id>] [--jsonl-file <path>] [--summary-file <path>]
|
|
134
|
+
autoscript mock-run <file> --fixture <fixture.json> [--profile <id>] [--jsonl-file <path>] [--summary-file <path>]
|
|
135
|
+
|
|
136
|
+
PROGRESS EVENTS:
|
|
137
|
+
events serve [--host 127.0.0.1] [--port 7788] Start progress websocket server (/events)
|
|
138
|
+
events tail [filters...] Tail progress events via websocket
|
|
139
|
+
events recent [--limit 50] Show recent persisted events
|
|
140
|
+
events emit --event <name> Emit a manual test event
|
|
141
|
+
(non-events commands auto-start daemon by default)
|
|
121
142
|
|
|
122
143
|
ENV:
|
|
123
144
|
WEBAUTO_BROWSER_URL Default: http://127.0.0.1:7704
|
|
124
145
|
WEBAUTO_REPO_ROOT Optional explicit webauto repo root
|
|
146
|
+
CAMO_PROGRESS_EVENTS_FILE Optional path override for progress jsonl
|
|
147
|
+
CAMO_PROGRESS_WS_HOST / CAMO_PROGRESS_WS_PORT Progress daemon host/port (defaults: 127.0.0.1:7788)
|
|
125
148
|
`);
|
|
126
149
|
}
|
|
127
150
|
|