libretto 0.1.4 → 0.2.0
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 +213 -17
- package/bin/libretto.mjs +18 -0
- package/dist/cli/cli.js +201 -0
- package/dist/cli/commands/ai.js +21 -0
- package/dist/cli/commands/browser.js +56 -0
- package/dist/cli/commands/execution.js +407 -0
- package/dist/cli/commands/logs.js +65 -0
- package/dist/cli/commands/snapshot.js +99 -0
- package/dist/cli/core/ai-config.js +149 -0
- package/dist/cli/core/browser.js +687 -0
- package/dist/cli/core/context.js +113 -0
- package/dist/cli/core/pause-signals.js +29 -0
- package/dist/cli/core/session.js +183 -0
- package/dist/cli/core/snapshot-analyzer.js +492 -0
- package/dist/cli/core/telemetry.js +350 -0
- package/dist/cli/index.js +13 -0
- package/dist/cli/workers/run-integration-runtime.js +204 -0
- package/dist/cli/workers/run-integration-worker-protocol.js +0 -0
- package/dist/cli/workers/run-integration-worker.js +83 -0
- package/dist/index.cjs +127 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +110 -0
- package/dist/runtime/download/download.cjs +70 -0
- package/dist/runtime/download/download.d.cts +35 -0
- package/dist/runtime/download/download.d.ts +35 -0
- package/dist/runtime/download/download.js +45 -0
- package/dist/runtime/download/index.cjs +30 -0
- package/dist/runtime/download/index.d.cts +3 -0
- package/dist/runtime/download/index.d.ts +3 -0
- package/dist/runtime/download/index.js +8 -0
- package/dist/runtime/extract/extract.cjs +87 -0
- package/dist/runtime/extract/extract.d.cts +23 -0
- package/dist/runtime/extract/extract.d.ts +23 -0
- package/dist/runtime/extract/extract.js +63 -0
- package/dist/runtime/extract/index.cjs +28 -0
- package/dist/runtime/extract/index.d.cts +5 -0
- package/dist/runtime/extract/index.d.ts +5 -0
- package/dist/runtime/extract/index.js +4 -0
- package/dist/runtime/network/index.cjs +28 -0
- package/dist/runtime/network/index.d.cts +4 -0
- package/dist/runtime/network/index.d.ts +4 -0
- package/dist/runtime/network/index.js +6 -0
- package/dist/runtime/network/network.cjs +91 -0
- package/dist/runtime/network/network.d.cts +28 -0
- package/dist/runtime/network/network.d.ts +28 -0
- package/dist/runtime/network/network.js +67 -0
- package/dist/runtime/recovery/agent.cjs +218 -0
- package/dist/runtime/recovery/agent.d.cts +13 -0
- package/dist/runtime/recovery/agent.d.ts +13 -0
- package/dist/runtime/recovery/agent.js +194 -0
- package/dist/runtime/recovery/errors.cjs +122 -0
- package/dist/runtime/recovery/errors.d.cts +31 -0
- package/dist/runtime/recovery/errors.d.ts +31 -0
- package/dist/runtime/recovery/errors.js +98 -0
- package/dist/runtime/recovery/index.cjs +34 -0
- package/dist/runtime/recovery/index.d.cts +7 -0
- package/dist/runtime/recovery/index.d.ts +7 -0
- package/dist/runtime/recovery/index.js +10 -0
- package/dist/runtime/recovery/recovery.cjs +53 -0
- package/dist/runtime/recovery/recovery.d.cts +12 -0
- package/dist/runtime/recovery/recovery.d.ts +12 -0
- package/dist/runtime/recovery/recovery.js +29 -0
- package/dist/runtime/step/index.cjs +31 -0
- package/dist/runtime/step/index.d.cts +7 -0
- package/dist/runtime/step/index.d.ts +7 -0
- package/dist/runtime/step/index.js +6 -0
- package/dist/runtime/step/runner.cjs +208 -0
- package/dist/runtime/step/runner.d.cts +16 -0
- package/dist/runtime/step/runner.d.ts +16 -0
- package/dist/runtime/step/runner.js +187 -0
- package/dist/runtime/step/step.cjs +67 -0
- package/dist/runtime/step/step.d.cts +23 -0
- package/dist/runtime/step/step.d.ts +23 -0
- package/dist/runtime/step/step.js +43 -0
- package/dist/runtime/step/types.cjs +16 -0
- package/dist/runtime/step/types.d.cts +72 -0
- package/dist/runtime/step/types.d.ts +72 -0
- package/dist/runtime/step/types.js +0 -0
- package/dist/shared/config/config.cjs +44 -0
- package/dist/shared/config/config.d.cts +10 -0
- package/dist/shared/config/config.d.ts +10 -0
- package/dist/shared/config/config.js +18 -0
- package/dist/shared/config/index.cjs +32 -0
- package/dist/shared/config/index.d.cts +1 -0
- package/dist/shared/config/index.d.ts +1 -0
- package/dist/shared/config/index.js +10 -0
- package/dist/shared/debug/index.cjs +32 -0
- package/dist/shared/debug/index.d.cts +2 -0
- package/dist/shared/debug/index.d.ts +2 -0
- package/dist/shared/debug/index.js +10 -0
- package/dist/shared/debug/pause.cjs +56 -0
- package/dist/shared/debug/pause.d.cts +23 -0
- package/dist/shared/debug/pause.d.ts +23 -0
- package/dist/shared/debug/pause.js +30 -0
- package/dist/shared/instrumentation/errors.cjs +81 -0
- package/dist/shared/instrumentation/errors.d.cts +12 -0
- package/dist/shared/instrumentation/errors.d.ts +12 -0
- package/dist/shared/instrumentation/errors.js +57 -0
- package/dist/shared/instrumentation/index.cjs +35 -0
- package/dist/shared/instrumentation/index.d.cts +6 -0
- package/dist/shared/instrumentation/index.d.ts +6 -0
- package/dist/shared/instrumentation/index.js +12 -0
- package/dist/shared/instrumentation/instrument.cjs +206 -0
- package/dist/shared/instrumentation/instrument.d.cts +32 -0
- package/dist/shared/instrumentation/instrument.d.ts +32 -0
- package/dist/shared/instrumentation/instrument.js +190 -0
- package/dist/shared/llm/client.cjs +139 -0
- package/dist/shared/llm/client.d.cts +6 -0
- package/dist/shared/llm/client.d.ts +6 -0
- package/dist/shared/llm/client.js +115 -0
- package/dist/shared/llm/index.cjs +28 -0
- package/dist/shared/llm/index.d.cts +3 -0
- package/dist/shared/llm/index.d.ts +3 -0
- package/dist/shared/llm/index.js +4 -0
- package/dist/shared/llm/types.cjs +16 -0
- package/dist/shared/llm/types.d.cts +34 -0
- package/dist/shared/llm/types.d.ts +34 -0
- package/dist/shared/llm/types.js +0 -0
- package/dist/shared/logger/index.cjs +35 -0
- package/dist/shared/logger/index.d.cts +2 -0
- package/dist/shared/logger/index.d.ts +2 -0
- package/dist/shared/logger/index.js +12 -0
- package/dist/shared/logger/logger.cjs +200 -0
- package/dist/shared/logger/logger.d.cts +70 -0
- package/dist/shared/logger/logger.d.ts +70 -0
- package/dist/shared/logger/logger.js +176 -0
- package/dist/shared/logger/sinks.cjs +160 -0
- package/dist/shared/logger/sinks.d.cts +9 -0
- package/dist/shared/logger/sinks.d.ts +9 -0
- package/dist/shared/logger/sinks.js +124 -0
- package/dist/shared/paths/paths.cjs +104 -0
- package/dist/shared/paths/paths.d.cts +10 -0
- package/dist/shared/paths/paths.d.ts +10 -0
- package/dist/shared/paths/paths.js +73 -0
- package/dist/shared/run/api.cjs +35 -0
- package/dist/shared/run/api.d.cts +3 -0
- package/dist/shared/run/api.d.ts +3 -0
- package/dist/shared/run/api.js +12 -0
- package/dist/shared/run/browser.cjs +98 -0
- package/dist/shared/run/browser.d.cts +22 -0
- package/dist/shared/run/browser.d.ts +22 -0
- package/dist/shared/run/browser.js +74 -0
- package/dist/shared/state/index.cjs +38 -0
- package/dist/shared/state/index.d.cts +2 -0
- package/dist/shared/state/index.d.ts +2 -0
- package/dist/shared/state/index.js +16 -0
- package/dist/shared/state/session-state.cjs +85 -0
- package/dist/shared/state/session-state.d.cts +34 -0
- package/dist/shared/state/session-state.d.ts +34 -0
- package/dist/shared/state/session-state.js +56 -0
- package/dist/shared/visualization/ghost-cursor.cjs +174 -0
- package/dist/shared/visualization/ghost-cursor.d.cts +37 -0
- package/dist/shared/visualization/ghost-cursor.d.ts +37 -0
- package/dist/shared/visualization/ghost-cursor.js +145 -0
- package/dist/shared/visualization/highlight.cjs +134 -0
- package/dist/shared/visualization/highlight.d.cts +22 -0
- package/dist/shared/visualization/highlight.d.ts +22 -0
- package/dist/shared/visualization/highlight.js +108 -0
- package/dist/shared/visualization/index.cjs +45 -0
- package/dist/shared/visualization/index.d.cts +3 -0
- package/dist/shared/visualization/index.d.ts +3 -0
- package/dist/shared/visualization/index.js +24 -0
- package/dist/shared/workflow/workflow.cjs +47 -0
- package/dist/shared/workflow/workflow.d.cts +33 -0
- package/dist/shared/workflow/workflow.d.ts +33 -0
- package/dist/shared/workflow/workflow.js +21 -0
- package/package.json +123 -26
- package/.npmignore +0 -2
- package/bin/libretto +0 -31
- package/lib/connect.js +0 -34
- package/lib/export.js +0 -224
- package/lib/import.js +0 -168
- package/lib/index.js +0 -8
- package/lib/log.js +0 -9
- package/lib/validate.js +0 -20
- package/makefile +0 -8
- package/src/connect.coffee +0 -25
- package/src/export.coffee +0 -222
- package/src/import.coffee +0 -171
- package/src/index.coffee +0 -3
- package/src/log.coffee +0 -3
- package/src/validate.coffee +0 -10
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { InstrumentationOptions, InstrumentedPage, installInstrumentation, instrumentContext, instrumentPage } from './instrument.cjs';
|
|
2
|
+
export { enrichTimeoutError } from './errors.cjs';
|
|
3
|
+
import 'playwright';
|
|
4
|
+
import '../logger/logger.cjs';
|
|
5
|
+
import '../visualization/ghost-cursor.cjs';
|
|
6
|
+
import '../visualization/highlight.cjs';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { InstrumentationOptions, InstrumentedPage, installInstrumentation, instrumentContext, instrumentPage } from './instrument.js';
|
|
2
|
+
export { enrichTimeoutError } from './errors.js';
|
|
3
|
+
import 'playwright';
|
|
4
|
+
import '../logger/logger.js';
|
|
5
|
+
import '../visualization/ghost-cursor.js';
|
|
6
|
+
import '../visualization/highlight.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
instrumentPage,
|
|
3
|
+
installInstrumentation,
|
|
4
|
+
instrumentContext
|
|
5
|
+
} from "./instrument.js";
|
|
6
|
+
import { enrichTimeoutError } from "./errors.js";
|
|
7
|
+
export {
|
|
8
|
+
enrichTimeoutError,
|
|
9
|
+
installInstrumentation,
|
|
10
|
+
instrumentContext,
|
|
11
|
+
instrumentPage
|
|
12
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var instrument_exports = {};
|
|
20
|
+
__export(instrument_exports, {
|
|
21
|
+
installInstrumentation: () => installInstrumentation,
|
|
22
|
+
instrumentContext: () => instrumentContext,
|
|
23
|
+
instrumentPage: () => instrumentPage
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(instrument_exports);
|
|
26
|
+
var import_ghost_cursor = require("../visualization/ghost-cursor.js");
|
|
27
|
+
var import_highlight = require("../visualization/highlight.js");
|
|
28
|
+
var import_errors = require("./errors.js");
|
|
29
|
+
const LOCATOR_ACTIONS = [
|
|
30
|
+
"click",
|
|
31
|
+
"dblclick",
|
|
32
|
+
"hover",
|
|
33
|
+
"fill",
|
|
34
|
+
"type",
|
|
35
|
+
"press",
|
|
36
|
+
"check",
|
|
37
|
+
"uncheck",
|
|
38
|
+
"selectOption",
|
|
39
|
+
"focus"
|
|
40
|
+
];
|
|
41
|
+
const NAV_ACTIONS = ["goto", "reload", "goBack", "goForward"];
|
|
42
|
+
const POINTER_ACTIONS = /* @__PURE__ */ new Set(["click", "dblclick", "hover"]);
|
|
43
|
+
const pageQueues = /* @__PURE__ */ new WeakMap();
|
|
44
|
+
function enqueue(page, fn) {
|
|
45
|
+
const prev = pageQueues.get(page) ?? Promise.resolve();
|
|
46
|
+
const next = prev.then(fn, fn);
|
|
47
|
+
pageQueues.set(page, next);
|
|
48
|
+
return next;
|
|
49
|
+
}
|
|
50
|
+
async function visualizeBeforeAction(page, box, actionName, highlightMs) {
|
|
51
|
+
if (!box) return;
|
|
52
|
+
await (0, import_ghost_cursor.ensureGhostCursor)(page);
|
|
53
|
+
await (0, import_highlight.ensureHighlightLayer)(page);
|
|
54
|
+
const centerX = box.x + box.width / 2;
|
|
55
|
+
const centerY = box.y + box.height / 2;
|
|
56
|
+
await (0, import_highlight.showHighlight)(page, {
|
|
57
|
+
box,
|
|
58
|
+
durationMs: highlightMs + 200
|
|
59
|
+
// keep visible a bit past the cursor arrival
|
|
60
|
+
});
|
|
61
|
+
const cursorPos = await (0, import_ghost_cursor.getGhostCursorPosition)(page);
|
|
62
|
+
if (cursorPos) {
|
|
63
|
+
await (0, import_ghost_cursor.moveGhostCursorWithDistance)(page, cursorPos, {
|
|
64
|
+
x: centerX,
|
|
65
|
+
y: centerY
|
|
66
|
+
});
|
|
67
|
+
} else {
|
|
68
|
+
await (0, import_ghost_cursor.moveGhostCursor)(page, { x: centerX, y: centerY, durationMs: 200 });
|
|
69
|
+
}
|
|
70
|
+
if (actionName === "click" || actionName === "dblclick") {
|
|
71
|
+
await (0, import_ghost_cursor.ghostClick)(page, { x: centerX, y: centerY });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function visualizeAfterAction(page) {
|
|
75
|
+
await (0, import_highlight.clearHighlights)(page);
|
|
76
|
+
}
|
|
77
|
+
function wrapLocatorActions(locator, page, opts) {
|
|
78
|
+
for (const method of LOCATOR_ACTIONS) {
|
|
79
|
+
if (typeof locator[method] !== "function") continue;
|
|
80
|
+
const orig = locator[method].bind(locator);
|
|
81
|
+
locator[method] = async (...args) => {
|
|
82
|
+
if (opts.visualize) {
|
|
83
|
+
await enqueue(page, async () => {
|
|
84
|
+
try {
|
|
85
|
+
const box = await locator.boundingBox();
|
|
86
|
+
await visualizeBeforeAction(
|
|
87
|
+
page,
|
|
88
|
+
box,
|
|
89
|
+
method,
|
|
90
|
+
opts.highlightBeforeActionMs
|
|
91
|
+
);
|
|
92
|
+
} catch {
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const result = await orig(...args);
|
|
98
|
+
if (opts.visualize) {
|
|
99
|
+
enqueue(page, () => visualizeAfterAction(page));
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if (opts.visualize) {
|
|
104
|
+
enqueue(page, () => visualizeAfterAction(page));
|
|
105
|
+
}
|
|
106
|
+
if (POINTER_ACTIONS.has(method) && isTimeoutError(err)) {
|
|
107
|
+
await (0, import_errors.enrichTimeoutError)(err, locator, page);
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function isTimeoutError(err) {
|
|
115
|
+
if (!err || typeof err.message !== "string") return false;
|
|
116
|
+
return err.message.includes("Timeout") || err.message.includes("timeout") || err.name === "TimeoutError";
|
|
117
|
+
}
|
|
118
|
+
const LOCATOR_FACTORIES = [
|
|
119
|
+
"locator",
|
|
120
|
+
"getByRole",
|
|
121
|
+
"getByText",
|
|
122
|
+
"getByLabel",
|
|
123
|
+
"getByPlaceholder",
|
|
124
|
+
"getByAltText",
|
|
125
|
+
"getByTitle",
|
|
126
|
+
"getByTestId"
|
|
127
|
+
];
|
|
128
|
+
async function installInstrumentation(page, options) {
|
|
129
|
+
if (page.__librettoInstrumented) return;
|
|
130
|
+
page.__librettoInstrumented = true;
|
|
131
|
+
const visualize = options?.visualize ?? false;
|
|
132
|
+
const highlightBeforeActionMs = options?.highlightBeforeActionMs ?? 350;
|
|
133
|
+
const mergedOpts = { ...options, visualize, highlightBeforeActionMs };
|
|
134
|
+
if (visualize) {
|
|
135
|
+
await (0, import_ghost_cursor.ensureGhostCursor)(page, options?.ghostCursor);
|
|
136
|
+
await (0, import_highlight.ensureHighlightLayer)(page, options?.highlight);
|
|
137
|
+
}
|
|
138
|
+
for (const method of LOCATOR_ACTIONS) {
|
|
139
|
+
if (typeof page[method] !== "function") continue;
|
|
140
|
+
const orig = page[method].bind(page);
|
|
141
|
+
page[method] = async (...args) => {
|
|
142
|
+
if (visualize && typeof args[0] === "string") {
|
|
143
|
+
await enqueue(page, async () => {
|
|
144
|
+
try {
|
|
145
|
+
const loc = page.locator(args[0]);
|
|
146
|
+
const box = await loc.boundingBox();
|
|
147
|
+
await visualizeBeforeAction(page, box, method, highlightBeforeActionMs);
|
|
148
|
+
} catch {
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const result = await orig(...args);
|
|
154
|
+
if (visualize) {
|
|
155
|
+
enqueue(page, () => visualizeAfterAction(page));
|
|
156
|
+
}
|
|
157
|
+
return result;
|
|
158
|
+
} catch (err) {
|
|
159
|
+
if (visualize) {
|
|
160
|
+
enqueue(page, () => visualizeAfterAction(page));
|
|
161
|
+
}
|
|
162
|
+
if (POINTER_ACTIONS.has(method) && isTimeoutError(err) && typeof args[0] === "string") {
|
|
163
|
+
await (0, import_errors.enrichTimeoutError)(err, page.locator(args[0]), page);
|
|
164
|
+
}
|
|
165
|
+
throw err;
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
for (const method of NAV_ACTIONS) {
|
|
170
|
+
if (typeof page[method] !== "function") continue;
|
|
171
|
+
const orig = page[method].bind(page);
|
|
172
|
+
page[method] = async (...args) => {
|
|
173
|
+
options?.logger?.info(`instrumentation:${method}`, {
|
|
174
|
+
url: typeof args[0] === "string" ? args[0] : page.url()
|
|
175
|
+
});
|
|
176
|
+
return orig(...args);
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
for (const factory of LOCATOR_FACTORIES) {
|
|
180
|
+
if (typeof page[factory] !== "function") continue;
|
|
181
|
+
const origFactory = page[factory].bind(page);
|
|
182
|
+
page[factory] = (...factoryArgs) => {
|
|
183
|
+
const locator = origFactory(...factoryArgs);
|
|
184
|
+
wrapLocatorActions(locator, page, mergedOpts);
|
|
185
|
+
return locator;
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async function instrumentPage(page, options) {
|
|
190
|
+
await installInstrumentation(page, options);
|
|
191
|
+
return page;
|
|
192
|
+
}
|
|
193
|
+
async function instrumentContext(context, options) {
|
|
194
|
+
for (const page of context.pages()) {
|
|
195
|
+
await installInstrumentation(page, options);
|
|
196
|
+
}
|
|
197
|
+
context.on("page", async (page) => {
|
|
198
|
+
await installInstrumentation(page, options);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
202
|
+
0 && (module.exports = {
|
|
203
|
+
installInstrumentation,
|
|
204
|
+
instrumentContext,
|
|
205
|
+
instrumentPage
|
|
206
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Page, BrowserContext } from 'playwright';
|
|
2
|
+
import { LoggerApi } from '../logger/logger.cjs';
|
|
3
|
+
import { GhostCursorOptions } from '../visualization/ghost-cursor.cjs';
|
|
4
|
+
import { HighlightOptions } from '../visualization/highlight.cjs';
|
|
5
|
+
|
|
6
|
+
type InstrumentationOptions = {
|
|
7
|
+
visualize?: boolean;
|
|
8
|
+
logger?: LoggerApi;
|
|
9
|
+
highlightBeforeActionMs?: number;
|
|
10
|
+
ghostCursor?: GhostCursorOptions;
|
|
11
|
+
highlight?: HighlightOptions;
|
|
12
|
+
};
|
|
13
|
+
type InstrumentedPage = Page & {
|
|
14
|
+
__librettoInstrumented: true;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* In-place patching of a Page object to add visualization and error enrichment.
|
|
18
|
+
* Modifies the page directly (does not return a new object).
|
|
19
|
+
*/
|
|
20
|
+
declare function installInstrumentation(page: Page, options?: InstrumentationOptions): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Returns a new object that proxies to the page with instrumentation applied.
|
|
23
|
+
* The original page is not modified.
|
|
24
|
+
*/
|
|
25
|
+
declare function instrumentPage(page: Page, options?: InstrumentationOptions): Promise<InstrumentedPage>;
|
|
26
|
+
/**
|
|
27
|
+
* Install overlays on a page and auto-install on all future pages in the context.
|
|
28
|
+
* Useful when connecting to an existing browser via CDP.
|
|
29
|
+
*/
|
|
30
|
+
declare function instrumentContext(context: BrowserContext, options?: InstrumentationOptions): Promise<void>;
|
|
31
|
+
|
|
32
|
+
export { type InstrumentationOptions, type InstrumentedPage, installInstrumentation, instrumentContext, instrumentPage };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Page, BrowserContext } from 'playwright';
|
|
2
|
+
import { LoggerApi } from '../logger/logger.js';
|
|
3
|
+
import { GhostCursorOptions } from '../visualization/ghost-cursor.js';
|
|
4
|
+
import { HighlightOptions } from '../visualization/highlight.js';
|
|
5
|
+
|
|
6
|
+
type InstrumentationOptions = {
|
|
7
|
+
visualize?: boolean;
|
|
8
|
+
logger?: LoggerApi;
|
|
9
|
+
highlightBeforeActionMs?: number;
|
|
10
|
+
ghostCursor?: GhostCursorOptions;
|
|
11
|
+
highlight?: HighlightOptions;
|
|
12
|
+
};
|
|
13
|
+
type InstrumentedPage = Page & {
|
|
14
|
+
__librettoInstrumented: true;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* In-place patching of a Page object to add visualization and error enrichment.
|
|
18
|
+
* Modifies the page directly (does not return a new object).
|
|
19
|
+
*/
|
|
20
|
+
declare function installInstrumentation(page: Page, options?: InstrumentationOptions): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Returns a new object that proxies to the page with instrumentation applied.
|
|
23
|
+
* The original page is not modified.
|
|
24
|
+
*/
|
|
25
|
+
declare function instrumentPage(page: Page, options?: InstrumentationOptions): Promise<InstrumentedPage>;
|
|
26
|
+
/**
|
|
27
|
+
* Install overlays on a page and auto-install on all future pages in the context.
|
|
28
|
+
* Useful when connecting to an existing browser via CDP.
|
|
29
|
+
*/
|
|
30
|
+
declare function instrumentContext(context: BrowserContext, options?: InstrumentationOptions): Promise<void>;
|
|
31
|
+
|
|
32
|
+
export { type InstrumentationOptions, type InstrumentedPage, installInstrumentation, instrumentContext, instrumentPage };
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureGhostCursor,
|
|
3
|
+
moveGhostCursor,
|
|
4
|
+
moveGhostCursorWithDistance,
|
|
5
|
+
ghostClick,
|
|
6
|
+
getGhostCursorPosition
|
|
7
|
+
} from "../visualization/ghost-cursor.js";
|
|
8
|
+
import {
|
|
9
|
+
ensureHighlightLayer,
|
|
10
|
+
showHighlight,
|
|
11
|
+
clearHighlights
|
|
12
|
+
} from "../visualization/highlight.js";
|
|
13
|
+
import { enrichTimeoutError } from "./errors.js";
|
|
14
|
+
const LOCATOR_ACTIONS = [
|
|
15
|
+
"click",
|
|
16
|
+
"dblclick",
|
|
17
|
+
"hover",
|
|
18
|
+
"fill",
|
|
19
|
+
"type",
|
|
20
|
+
"press",
|
|
21
|
+
"check",
|
|
22
|
+
"uncheck",
|
|
23
|
+
"selectOption",
|
|
24
|
+
"focus"
|
|
25
|
+
];
|
|
26
|
+
const NAV_ACTIONS = ["goto", "reload", "goBack", "goForward"];
|
|
27
|
+
const POINTER_ACTIONS = /* @__PURE__ */ new Set(["click", "dblclick", "hover"]);
|
|
28
|
+
const pageQueues = /* @__PURE__ */ new WeakMap();
|
|
29
|
+
function enqueue(page, fn) {
|
|
30
|
+
const prev = pageQueues.get(page) ?? Promise.resolve();
|
|
31
|
+
const next = prev.then(fn, fn);
|
|
32
|
+
pageQueues.set(page, next);
|
|
33
|
+
return next;
|
|
34
|
+
}
|
|
35
|
+
async function visualizeBeforeAction(page, box, actionName, highlightMs) {
|
|
36
|
+
if (!box) return;
|
|
37
|
+
await ensureGhostCursor(page);
|
|
38
|
+
await ensureHighlightLayer(page);
|
|
39
|
+
const centerX = box.x + box.width / 2;
|
|
40
|
+
const centerY = box.y + box.height / 2;
|
|
41
|
+
await showHighlight(page, {
|
|
42
|
+
box,
|
|
43
|
+
durationMs: highlightMs + 200
|
|
44
|
+
// keep visible a bit past the cursor arrival
|
|
45
|
+
});
|
|
46
|
+
const cursorPos = await getGhostCursorPosition(page);
|
|
47
|
+
if (cursorPos) {
|
|
48
|
+
await moveGhostCursorWithDistance(page, cursorPos, {
|
|
49
|
+
x: centerX,
|
|
50
|
+
y: centerY
|
|
51
|
+
});
|
|
52
|
+
} else {
|
|
53
|
+
await moveGhostCursor(page, { x: centerX, y: centerY, durationMs: 200 });
|
|
54
|
+
}
|
|
55
|
+
if (actionName === "click" || actionName === "dblclick") {
|
|
56
|
+
await ghostClick(page, { x: centerX, y: centerY });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function visualizeAfterAction(page) {
|
|
60
|
+
await clearHighlights(page);
|
|
61
|
+
}
|
|
62
|
+
function wrapLocatorActions(locator, page, opts) {
|
|
63
|
+
for (const method of LOCATOR_ACTIONS) {
|
|
64
|
+
if (typeof locator[method] !== "function") continue;
|
|
65
|
+
const orig = locator[method].bind(locator);
|
|
66
|
+
locator[method] = async (...args) => {
|
|
67
|
+
if (opts.visualize) {
|
|
68
|
+
await enqueue(page, async () => {
|
|
69
|
+
try {
|
|
70
|
+
const box = await locator.boundingBox();
|
|
71
|
+
await visualizeBeforeAction(
|
|
72
|
+
page,
|
|
73
|
+
box,
|
|
74
|
+
method,
|
|
75
|
+
opts.highlightBeforeActionMs
|
|
76
|
+
);
|
|
77
|
+
} catch {
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const result = await orig(...args);
|
|
83
|
+
if (opts.visualize) {
|
|
84
|
+
enqueue(page, () => visualizeAfterAction(page));
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
if (opts.visualize) {
|
|
89
|
+
enqueue(page, () => visualizeAfterAction(page));
|
|
90
|
+
}
|
|
91
|
+
if (POINTER_ACTIONS.has(method) && isTimeoutError(err)) {
|
|
92
|
+
await enrichTimeoutError(err, locator, page);
|
|
93
|
+
}
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
function isTimeoutError(err) {
|
|
100
|
+
if (!err || typeof err.message !== "string") return false;
|
|
101
|
+
return err.message.includes("Timeout") || err.message.includes("timeout") || err.name === "TimeoutError";
|
|
102
|
+
}
|
|
103
|
+
const LOCATOR_FACTORIES = [
|
|
104
|
+
"locator",
|
|
105
|
+
"getByRole",
|
|
106
|
+
"getByText",
|
|
107
|
+
"getByLabel",
|
|
108
|
+
"getByPlaceholder",
|
|
109
|
+
"getByAltText",
|
|
110
|
+
"getByTitle",
|
|
111
|
+
"getByTestId"
|
|
112
|
+
];
|
|
113
|
+
async function installInstrumentation(page, options) {
|
|
114
|
+
if (page.__librettoInstrumented) return;
|
|
115
|
+
page.__librettoInstrumented = true;
|
|
116
|
+
const visualize = options?.visualize ?? false;
|
|
117
|
+
const highlightBeforeActionMs = options?.highlightBeforeActionMs ?? 350;
|
|
118
|
+
const mergedOpts = { ...options, visualize, highlightBeforeActionMs };
|
|
119
|
+
if (visualize) {
|
|
120
|
+
await ensureGhostCursor(page, options?.ghostCursor);
|
|
121
|
+
await ensureHighlightLayer(page, options?.highlight);
|
|
122
|
+
}
|
|
123
|
+
for (const method of LOCATOR_ACTIONS) {
|
|
124
|
+
if (typeof page[method] !== "function") continue;
|
|
125
|
+
const orig = page[method].bind(page);
|
|
126
|
+
page[method] = async (...args) => {
|
|
127
|
+
if (visualize && typeof args[0] === "string") {
|
|
128
|
+
await enqueue(page, async () => {
|
|
129
|
+
try {
|
|
130
|
+
const loc = page.locator(args[0]);
|
|
131
|
+
const box = await loc.boundingBox();
|
|
132
|
+
await visualizeBeforeAction(page, box, method, highlightBeforeActionMs);
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const result = await orig(...args);
|
|
139
|
+
if (visualize) {
|
|
140
|
+
enqueue(page, () => visualizeAfterAction(page));
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
} catch (err) {
|
|
144
|
+
if (visualize) {
|
|
145
|
+
enqueue(page, () => visualizeAfterAction(page));
|
|
146
|
+
}
|
|
147
|
+
if (POINTER_ACTIONS.has(method) && isTimeoutError(err) && typeof args[0] === "string") {
|
|
148
|
+
await enrichTimeoutError(err, page.locator(args[0]), page);
|
|
149
|
+
}
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
for (const method of NAV_ACTIONS) {
|
|
155
|
+
if (typeof page[method] !== "function") continue;
|
|
156
|
+
const orig = page[method].bind(page);
|
|
157
|
+
page[method] = async (...args) => {
|
|
158
|
+
options?.logger?.info(`instrumentation:${method}`, {
|
|
159
|
+
url: typeof args[0] === "string" ? args[0] : page.url()
|
|
160
|
+
});
|
|
161
|
+
return orig(...args);
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
for (const factory of LOCATOR_FACTORIES) {
|
|
165
|
+
if (typeof page[factory] !== "function") continue;
|
|
166
|
+
const origFactory = page[factory].bind(page);
|
|
167
|
+
page[factory] = (...factoryArgs) => {
|
|
168
|
+
const locator = origFactory(...factoryArgs);
|
|
169
|
+
wrapLocatorActions(locator, page, mergedOpts);
|
|
170
|
+
return locator;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function instrumentPage(page, options) {
|
|
175
|
+
await installInstrumentation(page, options);
|
|
176
|
+
return page;
|
|
177
|
+
}
|
|
178
|
+
async function instrumentContext(context, options) {
|
|
179
|
+
for (const page of context.pages()) {
|
|
180
|
+
await installInstrumentation(page, options);
|
|
181
|
+
}
|
|
182
|
+
context.on("page", async (page) => {
|
|
183
|
+
await installInstrumentation(page, options);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
export {
|
|
187
|
+
installInstrumentation,
|
|
188
|
+
instrumentContext,
|
|
189
|
+
instrumentPage
|
|
190
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var client_exports = {};
|
|
20
|
+
__export(client_exports, {
|
|
21
|
+
createLLMClient: () => createLLMClient
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(client_exports);
|
|
24
|
+
var import_google_vertex = require("@ai-sdk/google-vertex");
|
|
25
|
+
var import_anthropic = require("@ai-sdk/anthropic");
|
|
26
|
+
var import_openai = require("@ai-sdk/openai");
|
|
27
|
+
var import_ai = require("ai");
|
|
28
|
+
function parseModel(model) {
|
|
29
|
+
const slashIndex = model.indexOf("/");
|
|
30
|
+
if (slashIndex === -1) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
`Invalid model string "${model}". Expected format: "provider/model-id" (e.g. "google/gemini-3-flash-preview").`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const provider = model.slice(0, slashIndex);
|
|
36
|
+
const modelId = model.slice(slashIndex + 1);
|
|
37
|
+
if (!["google", "anthropic", "openai"].includes(provider)) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`Unsupported provider "${provider}". Supported providers: google, anthropic, openai.`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
return { provider, modelId };
|
|
43
|
+
}
|
|
44
|
+
function getProviderModel(provider, modelId) {
|
|
45
|
+
switch (provider) {
|
|
46
|
+
case "google": {
|
|
47
|
+
const project = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT;
|
|
48
|
+
if (!project) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"Missing GCP project for Vertex AI. Set GOOGLE_CLOUD_PROJECT environment variable and ensure application default credentials are configured (gcloud auth application-default login)."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const vertex = (0, import_google_vertex.createVertex)({
|
|
54
|
+
project,
|
|
55
|
+
location: process.env.GOOGLE_CLOUD_LOCATION || "global"
|
|
56
|
+
});
|
|
57
|
+
return vertex(modelId);
|
|
58
|
+
}
|
|
59
|
+
case "anthropic": {
|
|
60
|
+
const apiKey = process.env.ANTHROPIC_API_KEY;
|
|
61
|
+
if (!apiKey) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"Missing API key for Anthropic. Set ANTHROPIC_API_KEY environment variable."
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
const anthropic = (0, import_anthropic.createAnthropic)({ apiKey });
|
|
67
|
+
return anthropic(modelId);
|
|
68
|
+
}
|
|
69
|
+
case "openai": {
|
|
70
|
+
const apiKey = process.env.OPENAI_API_KEY;
|
|
71
|
+
if (!apiKey) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
"Missing API key for OpenAI. Set OPENAI_API_KEY environment variable."
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
const openai = (0, import_openai.createOpenAI)({ apiKey });
|
|
77
|
+
return openai(modelId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function convertUserContentParts(parts) {
|
|
82
|
+
return parts.map((part) => {
|
|
83
|
+
if (part.type === "text") {
|
|
84
|
+
return { type: "text", text: part.text };
|
|
85
|
+
}
|
|
86
|
+
return { type: "image", image: part.image };
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
function convertAssistantContentParts(parts) {
|
|
90
|
+
return parts.filter((part) => part.type === "text").map((part) => ({ type: "text", text: part.text }));
|
|
91
|
+
}
|
|
92
|
+
function convertMessages(messages) {
|
|
93
|
+
return messages.map((msg) => {
|
|
94
|
+
if (msg.role === "user") {
|
|
95
|
+
if (typeof msg.content === "string") {
|
|
96
|
+
return { role: "user", content: msg.content };
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
role: "user",
|
|
100
|
+
content: convertUserContentParts(msg.content)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (typeof msg.content === "string") {
|
|
104
|
+
return { role: "assistant", content: msg.content };
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
role: "assistant",
|
|
108
|
+
content: convertAssistantContentParts(msg.content)
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function createLLMClient(model) {
|
|
113
|
+
const { provider, modelId } = parseModel(model);
|
|
114
|
+
const aiModel = getProviderModel(provider, modelId);
|
|
115
|
+
return {
|
|
116
|
+
async generateObject(opts) {
|
|
117
|
+
const result = await (0, import_ai.generateObject)({
|
|
118
|
+
model: aiModel,
|
|
119
|
+
prompt: opts.prompt,
|
|
120
|
+
schema: opts.schema,
|
|
121
|
+
temperature: opts.temperature ?? 0
|
|
122
|
+
});
|
|
123
|
+
return result.object;
|
|
124
|
+
},
|
|
125
|
+
async generateObjectFromMessages(opts) {
|
|
126
|
+
const result = await (0, import_ai.generateObject)({
|
|
127
|
+
model: aiModel,
|
|
128
|
+
messages: convertMessages(opts.messages),
|
|
129
|
+
schema: opts.schema,
|
|
130
|
+
temperature: opts.temperature ?? 0
|
|
131
|
+
});
|
|
132
|
+
return result.object;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
137
|
+
0 && (module.exports = {
|
|
138
|
+
createLLMClient
|
|
139
|
+
});
|