@web-auto/camo 0.1.26 → 0.2.1
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/LICENSE +21 -21
- package/README.md +586 -586
- package/bin/browser-service.mjs +11 -11
- package/bin/camo.mjs +22 -22
- package/package.json +48 -48
- package/scripts/build.mjs +19 -19
- package/scripts/bump-version.mjs +34 -34
- package/scripts/check-file-size.mjs +80 -80
- package/scripts/file-size-policy.json +12 -2
- package/scripts/install.mjs +76 -76
- package/scripts/release.sh +54 -54
- package/src/autoscript/action-providers/index.mjs +6 -6
- package/src/autoscript/impact-engine.mjs +78 -78
- package/src/autoscript/runtime.mjs +1017 -1017
- package/src/autoscript/schema.mjs +376 -376
- package/src/cli.mjs +405 -405
- package/src/commands/attach.mjs +141 -141
- package/src/commands/autoscript.mjs +1011 -1011
- package/src/commands/browser.mjs +1255 -1257
- package/src/commands/container.mjs +401 -401
- package/src/commands/cookies.mjs +69 -69
- package/src/commands/create.mjs +98 -98
- package/src/commands/devtools.mjs +349 -349
- package/src/commands/events.mjs +152 -152
- package/src/commands/highlight-mode.mjs +24 -24
- package/src/commands/init.mjs +68 -68
- package/src/commands/lifecycle.mjs +275 -275
- package/src/commands/mouse.mjs +45 -45
- package/src/commands/profile.mjs +46 -46
- package/src/commands/record.mjs +115 -115
- package/src/commands/system.mjs +14 -14
- package/src/commands/window.mjs +123 -123
- package/src/container/change-notifier.mjs +362 -362
- package/src/container/element-filter.mjs +143 -143
- package/src/container/index.mjs +3 -3
- package/src/container/runtime-core/checkpoint.mjs +209 -209
- package/src/container/runtime-core/index.mjs +21 -21
- package/src/container/runtime-core/operations/index.mjs +774 -774
- package/src/container/runtime-core/operations/selector-scripts.mjs +277 -277
- package/src/container/runtime-core/operations/tab-pool.mjs +746 -746
- package/src/container/runtime-core/operations/viewport.mjs +189 -189
- package/src/container/runtime-core/search.mjs +190 -190
- package/src/container/runtime-core/subscription.mjs +224 -224
- package/src/container/runtime-core/utils.mjs +94 -94
- package/src/container/runtime-core/validation.mjs +127 -184
- package/src/container/runtime-core.mjs +1 -1
- package/src/container/subscription-registry.mjs +459 -459
- package/src/core/actions.mjs +561 -561
- package/src/core/browser.mjs +266 -266
- package/src/core/index.mjs +52 -52
- package/src/core/utils.mjs +91 -91
- package/src/events/daemon-entry.mjs +33 -33
- package/src/events/daemon.mjs +80 -80
- package/src/events/progress-log.mjs +109 -109
- package/src/events/ws-server.mjs +239 -239
- package/src/lib/client.mjs +200 -200
- package/src/lifecycle/cleanup.mjs +83 -83
- package/src/lifecycle/lock.mjs +126 -126
- package/src/lifecycle/session-registry.mjs +279 -279
- package/src/lifecycle/session-view.mjs +76 -76
- package/src/lifecycle/session-watchdog.mjs +281 -281
- package/src/services/browser-service/index.js +671 -674
- package/src/services/browser-service/internal/BrowserSession.input.test.js +389 -389
- package/src/services/browser-service/internal/BrowserSession.js +325 -336
- package/src/services/browser-service/internal/ElementRegistry.js +60 -60
- package/src/services/browser-service/internal/ProfileLock.js +84 -84
- package/src/services/browser-service/internal/SessionManager.js +184 -184
- package/src/services/browser-service/internal/SessionManager.test.js +39 -39
- package/src/services/browser-service/internal/browser-session/cookies.js +144 -144
- package/src/services/browser-service/internal/browser-session/input-ops.js +222 -219
- package/src/services/browser-service/internal/browser-session/input-pipeline.js +144 -144
- package/src/services/browser-service/internal/browser-session/logging.js +46 -46
- package/src/services/browser-service/internal/browser-session/navigation.js +38 -38
- package/src/services/browser-service/internal/browser-session/page-hooks.js +442 -442
- package/src/services/browser-service/internal/browser-session/page-management.js +302 -336
- package/src/services/browser-service/internal/browser-session/page-management.test.js +148 -148
- package/src/services/browser-service/internal/browser-session/recording.js +198 -198
- package/src/services/browser-service/internal/browser-session/runtime-events.js +61 -61
- package/src/services/browser-service/internal/browser-session/session-core.js +84 -84
- package/src/services/browser-service/internal/browser-session/session-state.js +38 -38
- package/src/services/browser-service/internal/browser-session/types.js +14 -14
- package/src/services/browser-service/internal/browser-session/utils.js +95 -95
- package/src/services/browser-service/internal/browser-session/viewport-manager.js +46 -46
- package/src/services/browser-service/internal/browser-session/viewport.js +215 -215
- package/src/services/browser-service/internal/container-matcher.js +851 -851
- package/src/services/browser-service/internal/container-registry.js +182 -182
- package/src/services/browser-service/internal/engine-manager.js +259 -259
- package/src/services/browser-service/internal/fingerprint.js +203 -203
- package/src/services/browser-service/internal/heartbeat.js +137 -137
- package/src/services/browser-service/internal/logging.js +46 -46
- package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -1317
- package/src/services/browser-service/internal/pageRuntime.js +28 -28
- package/src/services/browser-service/internal/runtimeInjector.js +31 -31
- package/src/services/browser-service/internal/service-process-logger.js +140 -140
- package/src/services/browser-service/internal/state-bus.js +45 -45
- package/src/services/browser-service/internal/storage-paths.js +42 -42
- package/src/services/browser-service/internal/ws-server.js +1194 -1194
- package/src/services/browser-service/internal/ws-server.test.js +58 -58
- package/src/services/browser-service/server.mjs +6 -6
- package/src/services/controller/cli-bridge.js +93 -93
- package/src/services/controller/container-index.js +50 -50
- package/src/services/controller/container-storage.js +36 -36
- package/src/services/controller/controller-actions.js +207 -207
- package/src/services/controller/controller.js +1138 -1138
- package/src/services/controller/selectors.js +54 -54
- package/src/services/controller/transport.js +125 -125
- package/src/utils/args.mjs +26 -26
- package/src/utils/browser-service.mjs +544 -544
- package/src/utils/command-log.mjs +64 -64
- package/src/utils/config.mjs +214 -214
- package/src/utils/fingerprint.mjs +181 -181
- package/src/utils/help.mjs +216 -216
- package/src/utils/js-policy.mjs +13 -13
- package/src/utils/ws-client.mjs +30 -30
- package/src/container/runtime-core/operations/tab-pool.mjs.bak +0 -762
- package/src/container/runtime-core/operations/tab-pool.mjs.syntax-error +0 -762
- package/src/services/browser-service/index.js.bak +0 -671
|
@@ -1,349 +1,349 @@
|
|
|
1
|
-
import { getDefaultProfile, listProfiles } from '../utils/config.mjs';
|
|
2
|
-
import { callAPI } from '../utils/browser-service.mjs';
|
|
3
|
-
|
|
4
|
-
const DEFAULT_LIMIT = 120;
|
|
5
|
-
const MAX_LIMIT = 1000;
|
|
6
|
-
|
|
7
|
-
function parseNumber(value, fallback) {
|
|
8
|
-
const num = Number(value);
|
|
9
|
-
return Number.isFinite(num) ? num : fallback;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function clamp(value, min, max) {
|
|
13
|
-
return Math.min(Math.max(value, min), max);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function readFlagValue(args, names) {
|
|
17
|
-
for (let i = 0; i < args.length; i += 1) {
|
|
18
|
-
if (!names.includes(args[i])) continue;
|
|
19
|
-
const value = args[i + 1];
|
|
20
|
-
if (!value || String(value).startsWith('-')) return null;
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function collectPositionals(args, startIndex = 2) {
|
|
27
|
-
const values = [];
|
|
28
|
-
for (let i = startIndex; i < args.length; i += 1) {
|
|
29
|
-
const token = args[i];
|
|
30
|
-
if (!token || String(token).startsWith('--')) {
|
|
31
|
-
continue;
|
|
32
|
-
}
|
|
33
|
-
const prev = args[i - 1];
|
|
34
|
-
if (prev && ['--profile', '-p', '--limit', '-n', '--levels', '--since'].includes(prev)) {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
values.push(String(token));
|
|
38
|
-
}
|
|
39
|
-
return values;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function pickProfileAndExpression(args, subcommand) {
|
|
43
|
-
const explicitProfile = readFlagValue(args, ['--profile', '-p']);
|
|
44
|
-
const profileSet = new Set(listProfiles());
|
|
45
|
-
const positionals = collectPositionals(args, 2);
|
|
46
|
-
|
|
47
|
-
let profileId = explicitProfile || null;
|
|
48
|
-
let expression = null;
|
|
49
|
-
|
|
50
|
-
if (subcommand === 'eval') {
|
|
51
|
-
if (positionals.length === 0) {
|
|
52
|
-
return { profileId: profileId || getDefaultProfile(), expression: null };
|
|
53
|
-
}
|
|
54
|
-
if (!profileId && positionals.length >= 2) {
|
|
55
|
-
profileId = positionals[0];
|
|
56
|
-
expression = positionals.slice(1).join(' ').trim();
|
|
57
|
-
} else if (!profileId && profileSet.has(positionals[0])) {
|
|
58
|
-
profileId = positionals[0];
|
|
59
|
-
expression = positionals.slice(1).join(' ').trim();
|
|
60
|
-
} else {
|
|
61
|
-
expression = positionals.join(' ').trim();
|
|
62
|
-
}
|
|
63
|
-
return { profileId: profileId || getDefaultProfile(), expression };
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (positionals.length > 0) {
|
|
67
|
-
if (!profileId && (profileSet.has(positionals[0]) || subcommand === 'logs' || subcommand === 'clear')) {
|
|
68
|
-
profileId = positionals[0];
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return { profileId: profileId || getDefaultProfile(), expression: null };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function buildConsoleInstallScript(maxEntries) {
|
|
75
|
-
return `(function installCamoDevtoolsConsoleCollector() {
|
|
76
|
-
const KEY = '__camo_console_collector_v1__';
|
|
77
|
-
const BUFFER_KEY = '__camo_console_buffer_v1__';
|
|
78
|
-
const MAX = ${Math.max(100, Math.floor(maxEntries || MAX_LIMIT))};
|
|
79
|
-
const now = () => Date.now();
|
|
80
|
-
|
|
81
|
-
const stringify = (value) => {
|
|
82
|
-
if (typeof value === 'string') return value;
|
|
83
|
-
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') return String(value);
|
|
84
|
-
if (value === null) return 'null';
|
|
85
|
-
if (typeof value === 'undefined') return 'undefined';
|
|
86
|
-
if (typeof value === 'function') return '[function]';
|
|
87
|
-
if (typeof value === 'symbol') return String(value);
|
|
88
|
-
if (value instanceof Error) return value.stack || value.message || String(value);
|
|
89
|
-
try {
|
|
90
|
-
return JSON.stringify(value);
|
|
91
|
-
} catch {
|
|
92
|
-
return Object.prototype.toString.call(value);
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const pushEntry = (level, args) => {
|
|
97
|
-
const target = window[BUFFER_KEY];
|
|
98
|
-
if (!Array.isArray(target)) return;
|
|
99
|
-
const text = Array.from(args || []).map(stringify).join(' ');
|
|
100
|
-
target.push({
|
|
101
|
-
ts: now(),
|
|
102
|
-
level,
|
|
103
|
-
text,
|
|
104
|
-
href: String(window.location?.href || ''),
|
|
105
|
-
});
|
|
106
|
-
if (target.length > MAX) {
|
|
107
|
-
target.splice(0, target.length - MAX);
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
if (!Array.isArray(window[BUFFER_KEY])) {
|
|
112
|
-
window[BUFFER_KEY] = [];
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (!window[KEY]) {
|
|
116
|
-
const levels = ['log', 'info', 'warn', 'error', 'debug'];
|
|
117
|
-
const originals = {};
|
|
118
|
-
for (const level of levels) {
|
|
119
|
-
const raw = typeof console[level] === 'function' ? console[level] : console.log;
|
|
120
|
-
originals[level] = raw.bind(console);
|
|
121
|
-
console[level] = (...args) => {
|
|
122
|
-
try {
|
|
123
|
-
pushEntry(level, args);
|
|
124
|
-
} catch {}
|
|
125
|
-
return originals[level](...args);
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
window.addEventListener('error', (event) => {
|
|
130
|
-
try {
|
|
131
|
-
const message = event?.message || 'window.error';
|
|
132
|
-
pushEntry('error', [message]);
|
|
133
|
-
} catch {}
|
|
134
|
-
});
|
|
135
|
-
window.addEventListener('unhandledrejection', (event) => {
|
|
136
|
-
try {
|
|
137
|
-
const reason = event?.reason instanceof Error
|
|
138
|
-
? (event.reason.stack || event.reason.message)
|
|
139
|
-
: stringify(event?.reason);
|
|
140
|
-
pushEntry('error', ['unhandledrejection', reason]);
|
|
141
|
-
} catch {}
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
window[KEY] = { installedAt: now(), max: MAX };
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
ok: true,
|
|
149
|
-
installed: true,
|
|
150
|
-
entries: Array.isArray(window[BUFFER_KEY]) ? window[BUFFER_KEY].length : 0,
|
|
151
|
-
max: MAX,
|
|
152
|
-
};
|
|
153
|
-
})();`;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function buildConsoleReadScript(options = {}) {
|
|
157
|
-
const limit = clamp(parseNumber(options.limit, DEFAULT_LIMIT), 1, MAX_LIMIT);
|
|
158
|
-
const sinceTs = Math.max(0, parseNumber(options.sinceTs, 0) || 0);
|
|
159
|
-
const clear = options.clear === true;
|
|
160
|
-
const levels = Array.isArray(options.levels)
|
|
161
|
-
? options.levels.map((item) => String(item || '').trim().toLowerCase()).filter(Boolean)
|
|
162
|
-
: [];
|
|
163
|
-
const levelsLiteral = JSON.stringify(levels);
|
|
164
|
-
|
|
165
|
-
return `(function readCamoDevtoolsConsole() {
|
|
166
|
-
const BUFFER_KEY = '__camo_console_buffer_v1__';
|
|
167
|
-
const raw = Array.isArray(window[BUFFER_KEY]) ? window[BUFFER_KEY] : [];
|
|
168
|
-
const levelSet = new Set(${levelsLiteral});
|
|
169
|
-
const list = raw.filter((entry) => {
|
|
170
|
-
if (!entry || typeof entry !== 'object') return false;
|
|
171
|
-
const ts = Number(entry.ts || 0);
|
|
172
|
-
if (ts < ${sinceTs}) return false;
|
|
173
|
-
if (levelSet.size === 0) return true;
|
|
174
|
-
return levelSet.has(String(entry.level || '').toLowerCase());
|
|
175
|
-
});
|
|
176
|
-
const entries = list.slice(Math.max(0, list.length - ${limit}));
|
|
177
|
-
if (${clear ? 'true' : 'false'}) {
|
|
178
|
-
window[BUFFER_KEY] = [];
|
|
179
|
-
}
|
|
180
|
-
return {
|
|
181
|
-
ok: true,
|
|
182
|
-
total: raw.length,
|
|
183
|
-
returned: entries.length,
|
|
184
|
-
sinceTs: ${sinceTs},
|
|
185
|
-
levels: Array.from(levelSet),
|
|
186
|
-
cleared: ${clear ? 'true' : 'false'},
|
|
187
|
-
entries,
|
|
188
|
-
};
|
|
189
|
-
})();`;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function buildEvalScript(expression) {
|
|
193
|
-
return `(async function runCamoDevtoolsEval() {
|
|
194
|
-
const expr = ${JSON.stringify(expression || '')};
|
|
195
|
-
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
196
|
-
const resultPayload = { ok: true, mode: 'expression', value: null, valueType: null };
|
|
197
|
-
|
|
198
|
-
const toSerializable = (value, depth = 0, seen = new WeakSet()) => {
|
|
199
|
-
if (value === null) return null;
|
|
200
|
-
if (typeof value === 'undefined') return '[undefined]';
|
|
201
|
-
if (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean') return value;
|
|
202
|
-
if (typeof value === 'bigint') return value.toString();
|
|
203
|
-
if (typeof value === 'function') return '[function]';
|
|
204
|
-
if (typeof value === 'symbol') return value.toString();
|
|
205
|
-
if (value instanceof Error) return { name: value.name, message: value.message, stack: value.stack || null };
|
|
206
|
-
if (depth >= 3) return '[max-depth]';
|
|
207
|
-
if (Array.isArray(value)) return value.slice(0, 30).map((item) => toSerializable(item, depth + 1, seen));
|
|
208
|
-
if (typeof value === 'object') {
|
|
209
|
-
if (seen.has(value)) return '[circular]';
|
|
210
|
-
seen.add(value);
|
|
211
|
-
const out = {};
|
|
212
|
-
const keys = Object.keys(value).slice(0, 30);
|
|
213
|
-
for (const key of keys) {
|
|
214
|
-
out[key] = toSerializable(value[key], depth + 1, seen);
|
|
215
|
-
}
|
|
216
|
-
return out;
|
|
217
|
-
}
|
|
218
|
-
try {
|
|
219
|
-
return JSON.parse(JSON.stringify(value));
|
|
220
|
-
} catch {
|
|
221
|
-
return String(value);
|
|
222
|
-
}
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
const fn = new AsyncFunction('return (' + expr + ')');
|
|
227
|
-
const value = await fn();
|
|
228
|
-
resultPayload.value = toSerializable(value);
|
|
229
|
-
resultPayload.valueType = typeof value;
|
|
230
|
-
return resultPayload;
|
|
231
|
-
} catch (exprError) {
|
|
232
|
-
try {
|
|
233
|
-
const fn = new AsyncFunction(expr);
|
|
234
|
-
const value = await fn();
|
|
235
|
-
resultPayload.mode = 'statement';
|
|
236
|
-
resultPayload.value = toSerializable(value);
|
|
237
|
-
resultPayload.valueType = typeof value;
|
|
238
|
-
return resultPayload;
|
|
239
|
-
} catch (statementError) {
|
|
240
|
-
return {
|
|
241
|
-
ok: false,
|
|
242
|
-
mode: 'statement',
|
|
243
|
-
error: {
|
|
244
|
-
message: statementError?.message || String(statementError),
|
|
245
|
-
stack: statementError?.stack || null,
|
|
246
|
-
expressionError: exprError?.message || String(exprError),
|
|
247
|
-
},
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
})();`;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
async function ensureConsoleCollector(profileId, maxEntries = MAX_LIMIT) {
|
|
255
|
-
return callAPI('evaluate', {
|
|
256
|
-
profileId,
|
|
257
|
-
script: buildConsoleInstallScript(maxEntries),
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async function handleLogs(args) {
|
|
262
|
-
const { profileId } = pickProfileAndExpression(args, 'logs');
|
|
263
|
-
if (!profileId) {
|
|
264
|
-
throw new Error('No default profile set. Run: camo profile default <profileId>');
|
|
265
|
-
}
|
|
266
|
-
const limit = clamp(parseNumber(readFlagValue(args, ['--limit', '-n']), DEFAULT_LIMIT), 1, MAX_LIMIT);
|
|
267
|
-
const sinceTs = Math.max(0, parseNumber(readFlagValue(args, ['--since']), 0) || 0);
|
|
268
|
-
const levelsRaw = readFlagValue(args, ['--levels', '--level']) || '';
|
|
269
|
-
const levels = levelsRaw
|
|
270
|
-
.split(',')
|
|
271
|
-
.map((item) => String(item || '').trim().toLowerCase())
|
|
272
|
-
.filter(Boolean);
|
|
273
|
-
const clear = args.includes('--clear');
|
|
274
|
-
|
|
275
|
-
const install = await ensureConsoleCollector(profileId, MAX_LIMIT);
|
|
276
|
-
const result = await callAPI('evaluate', {
|
|
277
|
-
profileId,
|
|
278
|
-
script: buildConsoleReadScript({ limit, sinceTs, levels, clear }),
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
console.log(JSON.stringify({
|
|
282
|
-
ok: true,
|
|
283
|
-
command: 'devtools.logs',
|
|
284
|
-
profileId,
|
|
285
|
-
collector: install?.result || install?.data || install || null,
|
|
286
|
-
result: result?.result || result?.data || result || null,
|
|
287
|
-
}, null, 2));
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
async function handleClear(args) {
|
|
291
|
-
const { profileId } = pickProfileAndExpression(args, 'clear');
|
|
292
|
-
if (!profileId) {
|
|
293
|
-
throw new Error('No default profile set. Run: camo profile default <profileId>');
|
|
294
|
-
}
|
|
295
|
-
await ensureConsoleCollector(profileId, MAX_LIMIT);
|
|
296
|
-
const result = await callAPI('evaluate', {
|
|
297
|
-
profileId,
|
|
298
|
-
script: buildConsoleReadScript({ limit: MAX_LIMIT, sinceTs: 0, clear: true }),
|
|
299
|
-
});
|
|
300
|
-
console.log(JSON.stringify({
|
|
301
|
-
ok: true,
|
|
302
|
-
command: 'devtools.clear',
|
|
303
|
-
profileId,
|
|
304
|
-
result: result?.result || result?.data || result || null,
|
|
305
|
-
}, null, 2));
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async function handleEval(args) {
|
|
309
|
-
const { profileId, expression } = pickProfileAndExpression(args, 'eval');
|
|
310
|
-
if (!profileId) {
|
|
311
|
-
throw new Error('No default profile set. Run: camo profile default <profileId>');
|
|
312
|
-
}
|
|
313
|
-
if (!expression) {
|
|
314
|
-
throw new Error('Usage: camo devtools eval [profileId] <expression> [--profile <id>]');
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
await ensureConsoleCollector(profileId, MAX_LIMIT);
|
|
318
|
-
const result = await callAPI('evaluate', {
|
|
319
|
-
profileId,
|
|
320
|
-
script: buildEvalScript(expression),
|
|
321
|
-
});
|
|
322
|
-
console.log(JSON.stringify({
|
|
323
|
-
ok: true,
|
|
324
|
-
command: 'devtools.eval',
|
|
325
|
-
profileId,
|
|
326
|
-
expression,
|
|
327
|
-
result: result?.result || result?.data || result || null,
|
|
328
|
-
}, null, 2));
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
export async function handleDevtoolsCommand(args) {
|
|
332
|
-
const sub = String(args[1] || '').trim().toLowerCase();
|
|
333
|
-
switch (sub) {
|
|
334
|
-
case 'logs':
|
|
335
|
-
return handleLogs(args);
|
|
336
|
-
case 'clear':
|
|
337
|
-
return handleClear(args);
|
|
338
|
-
case 'eval':
|
|
339
|
-
return handleEval(args);
|
|
340
|
-
default:
|
|
341
|
-
console.log(`Usage: camo devtools <logs|eval|clear> [options]
|
|
342
|
-
|
|
343
|
-
Commands:
|
|
344
|
-
logs [profileId] [--limit 120] [--since <unix_ms>] [--levels error,warn] [--clear]
|
|
345
|
-
eval [profileId] <expression> [--profile <id>]
|
|
346
|
-
clear [profileId]
|
|
347
|
-
`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
1
|
+
import { getDefaultProfile, listProfiles } from '../utils/config.mjs';
|
|
2
|
+
import { callAPI } from '../utils/browser-service.mjs';
|
|
3
|
+
|
|
4
|
+
const DEFAULT_LIMIT = 120;
|
|
5
|
+
const MAX_LIMIT = 1000;
|
|
6
|
+
|
|
7
|
+
function parseNumber(value, fallback) {
|
|
8
|
+
const num = Number(value);
|
|
9
|
+
return Number.isFinite(num) ? num : fallback;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function clamp(value, min, max) {
|
|
13
|
+
return Math.min(Math.max(value, min), max);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function readFlagValue(args, names) {
|
|
17
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
18
|
+
if (!names.includes(args[i])) continue;
|
|
19
|
+
const value = args[i + 1];
|
|
20
|
+
if (!value || String(value).startsWith('-')) return null;
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function collectPositionals(args, startIndex = 2) {
|
|
27
|
+
const values = [];
|
|
28
|
+
for (let i = startIndex; i < args.length; i += 1) {
|
|
29
|
+
const token = args[i];
|
|
30
|
+
if (!token || String(token).startsWith('--')) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const prev = args[i - 1];
|
|
34
|
+
if (prev && ['--profile', '-p', '--limit', '-n', '--levels', '--since'].includes(prev)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
values.push(String(token));
|
|
38
|
+
}
|
|
39
|
+
return values;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function pickProfileAndExpression(args, subcommand) {
|
|
43
|
+
const explicitProfile = readFlagValue(args, ['--profile', '-p']);
|
|
44
|
+
const profileSet = new Set(listProfiles());
|
|
45
|
+
const positionals = collectPositionals(args, 2);
|
|
46
|
+
|
|
47
|
+
let profileId = explicitProfile || null;
|
|
48
|
+
let expression = null;
|
|
49
|
+
|
|
50
|
+
if (subcommand === 'eval') {
|
|
51
|
+
if (positionals.length === 0) {
|
|
52
|
+
return { profileId: profileId || getDefaultProfile(), expression: null };
|
|
53
|
+
}
|
|
54
|
+
if (!profileId && positionals.length >= 2) {
|
|
55
|
+
profileId = positionals[0];
|
|
56
|
+
expression = positionals.slice(1).join(' ').trim();
|
|
57
|
+
} else if (!profileId && profileSet.has(positionals[0])) {
|
|
58
|
+
profileId = positionals[0];
|
|
59
|
+
expression = positionals.slice(1).join(' ').trim();
|
|
60
|
+
} else {
|
|
61
|
+
expression = positionals.join(' ').trim();
|
|
62
|
+
}
|
|
63
|
+
return { profileId: profileId || getDefaultProfile(), expression };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (positionals.length > 0) {
|
|
67
|
+
if (!profileId && (profileSet.has(positionals[0]) || subcommand === 'logs' || subcommand === 'clear')) {
|
|
68
|
+
profileId = positionals[0];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { profileId: profileId || getDefaultProfile(), expression: null };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function buildConsoleInstallScript(maxEntries) {
|
|
75
|
+
return `(function installCamoDevtoolsConsoleCollector() {
|
|
76
|
+
const KEY = '__camo_console_collector_v1__';
|
|
77
|
+
const BUFFER_KEY = '__camo_console_buffer_v1__';
|
|
78
|
+
const MAX = ${Math.max(100, Math.floor(maxEntries || MAX_LIMIT))};
|
|
79
|
+
const now = () => Date.now();
|
|
80
|
+
|
|
81
|
+
const stringify = (value) => {
|
|
82
|
+
if (typeof value === 'string') return value;
|
|
83
|
+
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') return String(value);
|
|
84
|
+
if (value === null) return 'null';
|
|
85
|
+
if (typeof value === 'undefined') return 'undefined';
|
|
86
|
+
if (typeof value === 'function') return '[function]';
|
|
87
|
+
if (typeof value === 'symbol') return String(value);
|
|
88
|
+
if (value instanceof Error) return value.stack || value.message || String(value);
|
|
89
|
+
try {
|
|
90
|
+
return JSON.stringify(value);
|
|
91
|
+
} catch {
|
|
92
|
+
return Object.prototype.toString.call(value);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const pushEntry = (level, args) => {
|
|
97
|
+
const target = window[BUFFER_KEY];
|
|
98
|
+
if (!Array.isArray(target)) return;
|
|
99
|
+
const text = Array.from(args || []).map(stringify).join(' ');
|
|
100
|
+
target.push({
|
|
101
|
+
ts: now(),
|
|
102
|
+
level,
|
|
103
|
+
text,
|
|
104
|
+
href: String(window.location?.href || ''),
|
|
105
|
+
});
|
|
106
|
+
if (target.length > MAX) {
|
|
107
|
+
target.splice(0, target.length - MAX);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
if (!Array.isArray(window[BUFFER_KEY])) {
|
|
112
|
+
window[BUFFER_KEY] = [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!window[KEY]) {
|
|
116
|
+
const levels = ['log', 'info', 'warn', 'error', 'debug'];
|
|
117
|
+
const originals = {};
|
|
118
|
+
for (const level of levels) {
|
|
119
|
+
const raw = typeof console[level] === 'function' ? console[level] : console.log;
|
|
120
|
+
originals[level] = raw.bind(console);
|
|
121
|
+
console[level] = (...args) => {
|
|
122
|
+
try {
|
|
123
|
+
pushEntry(level, args);
|
|
124
|
+
} catch {}
|
|
125
|
+
return originals[level](...args);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
window.addEventListener('error', (event) => {
|
|
130
|
+
try {
|
|
131
|
+
const message = event?.message || 'window.error';
|
|
132
|
+
pushEntry('error', [message]);
|
|
133
|
+
} catch {}
|
|
134
|
+
});
|
|
135
|
+
window.addEventListener('unhandledrejection', (event) => {
|
|
136
|
+
try {
|
|
137
|
+
const reason = event?.reason instanceof Error
|
|
138
|
+
? (event.reason.stack || event.reason.message)
|
|
139
|
+
: stringify(event?.reason);
|
|
140
|
+
pushEntry('error', ['unhandledrejection', reason]);
|
|
141
|
+
} catch {}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
window[KEY] = { installedAt: now(), max: MAX };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
ok: true,
|
|
149
|
+
installed: true,
|
|
150
|
+
entries: Array.isArray(window[BUFFER_KEY]) ? window[BUFFER_KEY].length : 0,
|
|
151
|
+
max: MAX,
|
|
152
|
+
};
|
|
153
|
+
})();`;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function buildConsoleReadScript(options = {}) {
|
|
157
|
+
const limit = clamp(parseNumber(options.limit, DEFAULT_LIMIT), 1, MAX_LIMIT);
|
|
158
|
+
const sinceTs = Math.max(0, parseNumber(options.sinceTs, 0) || 0);
|
|
159
|
+
const clear = options.clear === true;
|
|
160
|
+
const levels = Array.isArray(options.levels)
|
|
161
|
+
? options.levels.map((item) => String(item || '').trim().toLowerCase()).filter(Boolean)
|
|
162
|
+
: [];
|
|
163
|
+
const levelsLiteral = JSON.stringify(levels);
|
|
164
|
+
|
|
165
|
+
return `(function readCamoDevtoolsConsole() {
|
|
166
|
+
const BUFFER_KEY = '__camo_console_buffer_v1__';
|
|
167
|
+
const raw = Array.isArray(window[BUFFER_KEY]) ? window[BUFFER_KEY] : [];
|
|
168
|
+
const levelSet = new Set(${levelsLiteral});
|
|
169
|
+
const list = raw.filter((entry) => {
|
|
170
|
+
if (!entry || typeof entry !== 'object') return false;
|
|
171
|
+
const ts = Number(entry.ts || 0);
|
|
172
|
+
if (ts < ${sinceTs}) return false;
|
|
173
|
+
if (levelSet.size === 0) return true;
|
|
174
|
+
return levelSet.has(String(entry.level || '').toLowerCase());
|
|
175
|
+
});
|
|
176
|
+
const entries = list.slice(Math.max(0, list.length - ${limit}));
|
|
177
|
+
if (${clear ? 'true' : 'false'}) {
|
|
178
|
+
window[BUFFER_KEY] = [];
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
ok: true,
|
|
182
|
+
total: raw.length,
|
|
183
|
+
returned: entries.length,
|
|
184
|
+
sinceTs: ${sinceTs},
|
|
185
|
+
levels: Array.from(levelSet),
|
|
186
|
+
cleared: ${clear ? 'true' : 'false'},
|
|
187
|
+
entries,
|
|
188
|
+
};
|
|
189
|
+
})();`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function buildEvalScript(expression) {
|
|
193
|
+
return `(async function runCamoDevtoolsEval() {
|
|
194
|
+
const expr = ${JSON.stringify(expression || '')};
|
|
195
|
+
const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;
|
|
196
|
+
const resultPayload = { ok: true, mode: 'expression', value: null, valueType: null };
|
|
197
|
+
|
|
198
|
+
const toSerializable = (value, depth = 0, seen = new WeakSet()) => {
|
|
199
|
+
if (value === null) return null;
|
|
200
|
+
if (typeof value === 'undefined') return '[undefined]';
|
|
201
|
+
if (typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean') return value;
|
|
202
|
+
if (typeof value === 'bigint') return value.toString();
|
|
203
|
+
if (typeof value === 'function') return '[function]';
|
|
204
|
+
if (typeof value === 'symbol') return value.toString();
|
|
205
|
+
if (value instanceof Error) return { name: value.name, message: value.message, stack: value.stack || null };
|
|
206
|
+
if (depth >= 3) return '[max-depth]';
|
|
207
|
+
if (Array.isArray(value)) return value.slice(0, 30).map((item) => toSerializable(item, depth + 1, seen));
|
|
208
|
+
if (typeof value === 'object') {
|
|
209
|
+
if (seen.has(value)) return '[circular]';
|
|
210
|
+
seen.add(value);
|
|
211
|
+
const out = {};
|
|
212
|
+
const keys = Object.keys(value).slice(0, 30);
|
|
213
|
+
for (const key of keys) {
|
|
214
|
+
out[key] = toSerializable(value[key], depth + 1, seen);
|
|
215
|
+
}
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
try {
|
|
219
|
+
return JSON.parse(JSON.stringify(value));
|
|
220
|
+
} catch {
|
|
221
|
+
return String(value);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
const fn = new AsyncFunction('return (' + expr + ')');
|
|
227
|
+
const value = await fn();
|
|
228
|
+
resultPayload.value = toSerializable(value);
|
|
229
|
+
resultPayload.valueType = typeof value;
|
|
230
|
+
return resultPayload;
|
|
231
|
+
} catch (exprError) {
|
|
232
|
+
try {
|
|
233
|
+
const fn = new AsyncFunction(expr);
|
|
234
|
+
const value = await fn();
|
|
235
|
+
resultPayload.mode = 'statement';
|
|
236
|
+
resultPayload.value = toSerializable(value);
|
|
237
|
+
resultPayload.valueType = typeof value;
|
|
238
|
+
return resultPayload;
|
|
239
|
+
} catch (statementError) {
|
|
240
|
+
return {
|
|
241
|
+
ok: false,
|
|
242
|
+
mode: 'statement',
|
|
243
|
+
error: {
|
|
244
|
+
message: statementError?.message || String(statementError),
|
|
245
|
+
stack: statementError?.stack || null,
|
|
246
|
+
expressionError: exprError?.message || String(exprError),
|
|
247
|
+
},
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
})();`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function ensureConsoleCollector(profileId, maxEntries = MAX_LIMIT) {
|
|
255
|
+
return callAPI('evaluate', {
|
|
256
|
+
profileId,
|
|
257
|
+
script: buildConsoleInstallScript(maxEntries),
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function handleLogs(args) {
|
|
262
|
+
const { profileId } = pickProfileAndExpression(args, 'logs');
|
|
263
|
+
if (!profileId) {
|
|
264
|
+
throw new Error('No default profile set. Run: camo profile default <profileId>');
|
|
265
|
+
}
|
|
266
|
+
const limit = clamp(parseNumber(readFlagValue(args, ['--limit', '-n']), DEFAULT_LIMIT), 1, MAX_LIMIT);
|
|
267
|
+
const sinceTs = Math.max(0, parseNumber(readFlagValue(args, ['--since']), 0) || 0);
|
|
268
|
+
const levelsRaw = readFlagValue(args, ['--levels', '--level']) || '';
|
|
269
|
+
const levels = levelsRaw
|
|
270
|
+
.split(',')
|
|
271
|
+
.map((item) => String(item || '').trim().toLowerCase())
|
|
272
|
+
.filter(Boolean);
|
|
273
|
+
const clear = args.includes('--clear');
|
|
274
|
+
|
|
275
|
+
const install = await ensureConsoleCollector(profileId, MAX_LIMIT);
|
|
276
|
+
const result = await callAPI('evaluate', {
|
|
277
|
+
profileId,
|
|
278
|
+
script: buildConsoleReadScript({ limit, sinceTs, levels, clear }),
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
console.log(JSON.stringify({
|
|
282
|
+
ok: true,
|
|
283
|
+
command: 'devtools.logs',
|
|
284
|
+
profileId,
|
|
285
|
+
collector: install?.result || install?.data || install || null,
|
|
286
|
+
result: result?.result || result?.data || result || null,
|
|
287
|
+
}, null, 2));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function handleClear(args) {
|
|
291
|
+
const { profileId } = pickProfileAndExpression(args, 'clear');
|
|
292
|
+
if (!profileId) {
|
|
293
|
+
throw new Error('No default profile set. Run: camo profile default <profileId>');
|
|
294
|
+
}
|
|
295
|
+
await ensureConsoleCollector(profileId, MAX_LIMIT);
|
|
296
|
+
const result = await callAPI('evaluate', {
|
|
297
|
+
profileId,
|
|
298
|
+
script: buildConsoleReadScript({ limit: MAX_LIMIT, sinceTs: 0, clear: true }),
|
|
299
|
+
});
|
|
300
|
+
console.log(JSON.stringify({
|
|
301
|
+
ok: true,
|
|
302
|
+
command: 'devtools.clear',
|
|
303
|
+
profileId,
|
|
304
|
+
result: result?.result || result?.data || result || null,
|
|
305
|
+
}, null, 2));
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function handleEval(args) {
|
|
309
|
+
const { profileId, expression } = pickProfileAndExpression(args, 'eval');
|
|
310
|
+
if (!profileId) {
|
|
311
|
+
throw new Error('No default profile set. Run: camo profile default <profileId>');
|
|
312
|
+
}
|
|
313
|
+
if (!expression) {
|
|
314
|
+
throw new Error('Usage: camo devtools eval [profileId] <expression> [--profile <id>]');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
await ensureConsoleCollector(profileId, MAX_LIMIT);
|
|
318
|
+
const result = await callAPI('evaluate', {
|
|
319
|
+
profileId,
|
|
320
|
+
script: buildEvalScript(expression),
|
|
321
|
+
});
|
|
322
|
+
console.log(JSON.stringify({
|
|
323
|
+
ok: true,
|
|
324
|
+
command: 'devtools.eval',
|
|
325
|
+
profileId,
|
|
326
|
+
expression,
|
|
327
|
+
result: result?.result || result?.data || result || null,
|
|
328
|
+
}, null, 2));
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export async function handleDevtoolsCommand(args) {
|
|
332
|
+
const sub = String(args[1] || '').trim().toLowerCase();
|
|
333
|
+
switch (sub) {
|
|
334
|
+
case 'logs':
|
|
335
|
+
return handleLogs(args);
|
|
336
|
+
case 'clear':
|
|
337
|
+
return handleClear(args);
|
|
338
|
+
case 'eval':
|
|
339
|
+
return handleEval(args);
|
|
340
|
+
default:
|
|
341
|
+
console.log(`Usage: camo devtools <logs|eval|clear> [options]
|
|
342
|
+
|
|
343
|
+
Commands:
|
|
344
|
+
logs [profileId] [--limit 120] [--since <unix_ms>] [--levels error,warn] [--clear]
|
|
345
|
+
eval [profileId] <expression> [--profile <id>]
|
|
346
|
+
clear [profileId]
|
|
347
|
+
`);
|
|
348
|
+
}
|
|
349
|
+
}
|