@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
|
@@ -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/config.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import os from 'node:os';
|
|
|
6
6
|
export const CONFIG_DIR = path.join(os.homedir(), '.webauto');
|
|
7
7
|
export const PROFILES_DIR = path.join(CONFIG_DIR, 'profiles');
|
|
8
8
|
export const CONFIG_FILE = path.join(CONFIG_DIR, 'camo-cli.json');
|
|
9
|
+
export const PROFILE_META_FILE = 'camo-profile.json';
|
|
9
10
|
export const BROWSER_SERVICE_URL = process.env.WEBAUTO_BROWSER_URL || 'http://127.0.0.1:7704';
|
|
10
11
|
|
|
11
12
|
export function ensureDir(p) {
|
|
@@ -55,13 +56,13 @@ export function createProfile(profileId) {
|
|
|
55
56
|
if (!isValidProfileId(profileId)) {
|
|
56
57
|
throw new Error('Invalid profileId. Use only letters, numbers, dot, underscore, dash.');
|
|
57
58
|
}
|
|
58
|
-
const profileDir =
|
|
59
|
+
const profileDir = getProfileDir(profileId);
|
|
59
60
|
if (fs.existsSync(profileDir)) throw new Error(`Profile already exists: ${profileId}`);
|
|
60
61
|
ensureDir(profileDir);
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
export function deleteProfile(profileId) {
|
|
64
|
-
const profileDir =
|
|
65
|
+
const profileDir = getProfileDir(profileId);
|
|
65
66
|
if (!fs.existsSync(profileDir)) throw new Error(`Profile not found: ${profileId}`);
|
|
66
67
|
fs.rmSync(profileDir, { recursive: true, force: true });
|
|
67
68
|
}
|
|
@@ -82,6 +83,61 @@ export function getDefaultProfile() {
|
|
|
82
83
|
return loadConfig().defaultProfile;
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
export function getProfileDir(profileId) {
|
|
87
|
+
return path.join(PROFILES_DIR, String(profileId || '').trim());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function getProfileMetaFile(profileId) {
|
|
91
|
+
return path.join(getProfileDir(profileId), PROFILE_META_FILE);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function loadProfileMeta(profileId) {
|
|
95
|
+
if (!isValidProfileId(profileId)) return {};
|
|
96
|
+
return readJson(getProfileMetaFile(profileId)) || {};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function saveProfileMeta(profileId, patch) {
|
|
100
|
+
if (!isValidProfileId(profileId)) return null;
|
|
101
|
+
const profileDir = getProfileDir(profileId);
|
|
102
|
+
ensureDir(profileDir);
|
|
103
|
+
const current = loadProfileMeta(profileId);
|
|
104
|
+
const next = {
|
|
105
|
+
...current,
|
|
106
|
+
...patch,
|
|
107
|
+
updatedAt: Date.now(),
|
|
108
|
+
};
|
|
109
|
+
writeJson(getProfileMetaFile(profileId), next);
|
|
110
|
+
return next;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function getProfileWindowSize(profileId) {
|
|
114
|
+
const meta = loadProfileMeta(profileId);
|
|
115
|
+
const width = Number(meta?.window?.width);
|
|
116
|
+
const height = Number(meta?.window?.height);
|
|
117
|
+
if (!Number.isFinite(width) || !Number.isFinite(height)) return null;
|
|
118
|
+
if (width < 320 || height < 240) return null;
|
|
119
|
+
return {
|
|
120
|
+
width: Math.floor(width),
|
|
121
|
+
height: Math.floor(height),
|
|
122
|
+
updatedAt: Number(meta?.window?.updatedAt) || Number(meta?.updatedAt) || null,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function setProfileWindowSize(profileId, width, height) {
|
|
127
|
+
const parsedWidth = Number(width);
|
|
128
|
+
const parsedHeight = Number(height);
|
|
129
|
+
if (!Number.isFinite(parsedWidth) || !Number.isFinite(parsedHeight)) return null;
|
|
130
|
+
if (parsedWidth < 320 || parsedHeight < 240) return null;
|
|
131
|
+
const now = Date.now();
|
|
132
|
+
return saveProfileMeta(profileId, {
|
|
133
|
+
window: {
|
|
134
|
+
width: Math.floor(parsedWidth),
|
|
135
|
+
height: Math.floor(parsedHeight),
|
|
136
|
+
updatedAt: now,
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
85
141
|
const START_SCRIPT_REL = path.join('runtime', 'infra', 'utils', 'scripts', 'service', 'start-browser-service.mjs');
|
|
86
142
|
|
|
87
143
|
export function hasStartScript(root) {
|
package/src/utils/help.mjs
CHANGED
|
@@ -24,7 +24,7 @@ CONFIG:
|
|
|
24
24
|
|
|
25
25
|
BROWSER CONTROL:
|
|
26
26
|
init Ensure camoufox + ensure browser-service daemon
|
|
27
|
-
start [profileId] [--url <url>] [--headless]
|
|
27
|
+
start [profileId] [--url <url>] [--headless] [--width <w> --height <h>]
|
|
28
28
|
stop [profileId]
|
|
29
29
|
status [profileId]
|
|
30
30
|
list Alias of status
|
|
@@ -91,6 +91,7 @@ EXAMPLES:
|
|
|
91
91
|
camo profile create myprofile
|
|
92
92
|
camo profile default myprofile
|
|
93
93
|
camo start --url https://example.com
|
|
94
|
+
camo start myprofile --width 1920 --height 1020
|
|
94
95
|
camo goto https://www.xiaohongshu.com
|
|
95
96
|
camo scroll --down --amount 500
|
|
96
97
|
camo click "#search-input"
|
|
@@ -115,13 +116,36 @@ EXAMPLES:
|
|
|
115
116
|
camo stop
|
|
116
117
|
|
|
117
118
|
CONTAINER FILTER & SUBSCRIPTION:
|
|
118
|
-
container
|
|
119
|
-
container
|
|
120
|
-
container
|
|
119
|
+
container init [--source <dir>] [--force] Initialize subscription dir + migrate container sets
|
|
120
|
+
container sets [--site <siteKey>] List migrated subscription sets
|
|
121
|
+
container register [profileId] <setId...> Register targets (path / url+dom markers) for profile
|
|
122
|
+
container targets [profileId] Show registered subscription targets
|
|
123
|
+
container filter [profileId] <selector...> Filter DOM elements by CSS selector
|
|
124
|
+
container watch [profileId] [--selector <css>] Watch for element changes (or use registered selectors)
|
|
125
|
+
container list [profileId] List visible elements in viewport
|
|
126
|
+
|
|
127
|
+
AUTOSCRIPT (STRATEGY LAYER):
|
|
128
|
+
autoscript scaffold xhs-unified [--output <file>] Generate xiaohongshu unified-harvest autoscript template
|
|
129
|
+
autoscript validate <file> Validate autoscript schema and references
|
|
130
|
+
autoscript explain <file> Print normalized graph and defaults
|
|
131
|
+
autoscript snapshot <jsonl-file> [--out <snapshot-file>] Build resumable snapshot from run JSONL
|
|
132
|
+
autoscript replay <jsonl-file> [--summary-file <path>] Rebuild summary from run JSONL
|
|
133
|
+
autoscript run <file> [--profile <id>] [--jsonl-file <path>] [--summary-file <path>] Run autoscript runtime
|
|
134
|
+
autoscript resume <file> --snapshot <snapshot-file> [--from-node <nodeId>] [--profile <id>] [--jsonl-file <path>] [--summary-file <path>]
|
|
135
|
+
autoscript mock-run <file> --fixture <fixture.json> [--profile <id>] [--jsonl-file <path>] [--summary-file <path>]
|
|
136
|
+
|
|
137
|
+
PROGRESS EVENTS:
|
|
138
|
+
events serve [--host 127.0.0.1] [--port 7788] Start progress websocket server (/events)
|
|
139
|
+
events tail [filters...] Tail progress events via websocket
|
|
140
|
+
events recent [--limit 50] Show recent persisted events
|
|
141
|
+
events emit --event <name> Emit a manual test event
|
|
142
|
+
(non-events commands auto-start daemon by default)
|
|
121
143
|
|
|
122
144
|
ENV:
|
|
123
145
|
WEBAUTO_BROWSER_URL Default: http://127.0.0.1:7704
|
|
124
146
|
WEBAUTO_REPO_ROOT Optional explicit webauto repo root
|
|
147
|
+
CAMO_PROGRESS_EVENTS_FILE Optional path override for progress jsonl
|
|
148
|
+
CAMO_PROGRESS_WS_HOST / CAMO_PROGRESS_WS_PORT Progress daemon host/port (defaults: 127.0.0.1:7788)
|
|
125
149
|
`);
|
|
126
150
|
}
|
|
127
151
|
|