libretto 0.5.0 → 0.5.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/README.md +106 -36
- package/dist/cli/cli.js +22 -97
- package/dist/cli/commands/browser.js +86 -59
- package/dist/cli/commands/execution.js +199 -86
- package/dist/cli/commands/init.js +30 -8
- package/dist/cli/commands/logs.js +4 -5
- package/dist/cli/commands/shared.js +30 -29
- package/dist/cli/commands/snapshot.js +26 -39
- package/dist/cli/core/ai-config.js +9 -2
- package/dist/cli/core/api-snapshot-analyzer.js +15 -5
- package/dist/cli/core/browser.js +132 -29
- package/dist/cli/core/context.js +4 -1
- package/dist/cli/core/session-telemetry.js +5 -2
- package/dist/cli/core/session.js +21 -8
- package/dist/cli/core/snapshot-analyzer.js +14 -31
- package/dist/cli/core/snapshot-api-config.js +2 -6
- package/dist/cli/core/telemetry.js +10 -2
- package/dist/cli/framework/simple-cli.js +45 -25
- package/dist/cli/router.js +14 -21
- package/dist/cli/workers/run-integration-runtime.js +24 -5
- package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
- package/dist/cli/workers/run-integration-worker.js +1 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.js +7 -10
- package/dist/runtime/download/download.js +5 -1
- package/dist/runtime/extract/extract.js +11 -2
- package/dist/runtime/network/network.js +8 -1
- package/dist/runtime/recovery/agent.js +6 -2
- package/dist/runtime/recovery/errors.js +3 -1
- package/dist/runtime/recovery/recovery.js +3 -1
- package/dist/shared/condense-dom/condense-dom.js +6 -13
- package/dist/shared/config/config.d.ts +1 -9
- package/dist/shared/config/config.js +0 -18
- package/dist/shared/config/index.d.ts +2 -1
- package/dist/shared/config/index.js +0 -10
- package/dist/shared/debug/pause.js +9 -3
- package/dist/shared/instrumentation/instrument.js +101 -5
- package/dist/shared/llm/ai-sdk-adapter.js +3 -1
- package/dist/shared/llm/client.js +3 -1
- package/dist/shared/logger/index.js +4 -1
- package/dist/shared/run/api.js +3 -1
- package/dist/shared/run/browser.js +7 -2
- package/dist/shared/state/session-state.d.ts +2 -1
- package/dist/shared/state/session-state.js +5 -2
- package/dist/shared/visualization/ghost-cursor.js +19 -10
- package/dist/shared/visualization/highlight.js +9 -6
- package/dist/shared/workflow/workflow.d.ts +4 -5
- package/dist/shared/workflow/workflow.js +3 -5
- package/package.json +6 -2
- package/scripts/check-skills-sync.mjs +25 -0
- package/scripts/compare-eval-summary.mjs +47 -0
- package/scripts/postinstall.mjs +15 -15
- package/scripts/prepare-release.sh +97 -0
- package/scripts/skills-libretto.mjs +103 -0
- package/scripts/summarize-evals.mjs +135 -0
- package/scripts/sync-skills.mjs +12 -0
- package/skills/libretto/SKILL.md +113 -49
- package/skills/libretto/references/code-generation-rules.md +208 -0
- package/skills/libretto/references/configuration-file-reference.md +53 -0
- package/skills/libretto/references/site-security-review.md +143 -0
- package/src/cli/cli.ts +23 -110
- package/src/cli/commands/browser.ts +94 -70
- package/src/cli/commands/execution.ts +233 -102
- package/src/cli/commands/init.ts +32 -9
- package/src/cli/commands/logs.ts +7 -7
- package/src/cli/commands/shared.ts +36 -37
- package/src/cli/commands/snapshot.ts +44 -59
- package/src/cli/core/ai-config.ts +12 -3
- package/src/cli/core/api-snapshot-analyzer.ts +17 -6
- package/src/cli/core/browser.ts +178 -41
- package/src/cli/core/context.ts +7 -2
- package/src/cli/core/session-telemetry.ts +19 -8
- package/src/cli/core/session.ts +21 -7
- package/src/cli/core/snapshot-analyzer.ts +26 -46
- package/src/cli/core/snapshot-api-config.ts +170 -175
- package/src/cli/core/telemetry.ts +16 -3
- package/src/cli/framework/simple-cli.ts +144 -77
- package/src/cli/router.ts +13 -21
- package/src/cli/workers/run-integration-runtime.ts +36 -9
- package/src/cli/workers/run-integration-worker-protocol.ts +2 -0
- package/src/cli/workers/run-integration-worker.ts +1 -4
- package/src/index.ts +73 -66
- package/src/runtime/download/download.ts +62 -58
- package/src/runtime/download/index.ts +5 -5
- package/src/runtime/extract/extract.ts +71 -61
- package/src/runtime/network/index.ts +3 -3
- package/src/runtime/network/network.ts +99 -93
- package/src/runtime/recovery/agent.ts +217 -212
- package/src/runtime/recovery/errors.ts +107 -104
- package/src/runtime/recovery/index.ts +3 -3
- package/src/runtime/recovery/recovery.ts +38 -35
- package/src/shared/condense-dom/condense-dom.ts +15 -18
- package/src/shared/config/config.ts +0 -19
- package/src/shared/config/index.ts +0 -5
- package/src/shared/debug/pause.ts +57 -51
- package/src/shared/instrumentation/errors.ts +64 -62
- package/src/shared/instrumentation/index.ts +5 -5
- package/src/shared/instrumentation/instrument.ts +339 -209
- package/src/shared/llm/ai-sdk-adapter.ts +58 -55
- package/src/shared/llm/client.ts +181 -174
- package/src/shared/llm/types.ts +39 -39
- package/src/shared/logger/index.ts +11 -4
- package/src/shared/logger/logger.ts +312 -306
- package/src/shared/logger/sinks.ts +118 -114
- package/src/shared/paths/paths.ts +50 -49
- package/src/shared/paths/repo-root.ts +17 -17
- package/src/shared/run/api.ts +5 -1
- package/src/shared/run/browser.ts +12 -3
- package/src/shared/state/index.ts +9 -9
- package/src/shared/state/session-state.ts +46 -43
- package/src/shared/visualization/ghost-cursor.ts +161 -148
- package/src/shared/visualization/highlight.ts +89 -86
- package/src/shared/visualization/index.ts +13 -13
- package/src/shared/workflow/workflow.ts +19 -25
- package/skills/libretto/references/reverse-engineering-network-requests.md +0 -39
- package/skills/libretto/references/user-action-log.md +0 -31
|
@@ -3,45 +3,49 @@ import type { Page } from "playwright";
|
|
|
3
3
|
export type GhostCursorStyle = "minimal" | "dot" | "screenstudio";
|
|
4
4
|
|
|
5
5
|
export type GhostCursorOptions = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
style?: GhostCursorStyle;
|
|
7
|
+
color?: string;
|
|
8
|
+
size?: number;
|
|
9
|
+
zIndex?: number;
|
|
10
|
+
easing?: string;
|
|
11
|
+
minDurationMs?: number;
|
|
12
|
+
maxDurationMs?: number;
|
|
13
|
+
speedPxPerMs?: number;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
const DEFAULTS: Required<GhostCursorOptions> = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
style: "minimal",
|
|
18
|
+
color: "rgba(255, 70, 70, 0.9)",
|
|
19
|
+
size: 20,
|
|
20
|
+
zIndex: 2147483646,
|
|
21
|
+
easing: "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
22
|
+
minDurationMs: 100,
|
|
23
|
+
maxDurationMs: 600,
|
|
24
|
+
speedPxPerMs: 1.5,
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
const CURSOR_ID = "__libretto_ghost_cursor__";
|
|
28
28
|
|
|
29
|
-
function buildCursorSvg(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
29
|
+
function buildCursorSvg(
|
|
30
|
+
style: GhostCursorStyle,
|
|
31
|
+
color: string,
|
|
32
|
+
size: number,
|
|
33
|
+
): string {
|
|
34
|
+
if (style === "dot") {
|
|
35
|
+
return `<div style="width:${size}px;height:${size}px;border-radius:50%;background:${color};"></div>`;
|
|
36
|
+
}
|
|
37
|
+
if (style === "screenstudio") {
|
|
38
|
+
return `<div style="width:${size * 1.4}px;height:${size * 1.4}px;border-radius:50%;background:${color};box-shadow:0 0 ${size * 0.6}px ${color};opacity:0.7;"></div>`;
|
|
39
|
+
}
|
|
40
|
+
// minimal: default arrow-like SVG cursor
|
|
41
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
38
42
|
<path d="M5 3L19 12L12 13L9 20L5 3Z" fill="${color}" stroke="rgba(0,0,0,0.3)" stroke-width="1"/>
|
|
39
43
|
</svg>`;
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
function buildInitScript(opts: Required<GhostCursorOptions>): string {
|
|
43
|
-
|
|
44
|
-
|
|
47
|
+
const svg = buildCursorSvg(opts.style, opts.color, opts.size);
|
|
48
|
+
return `
|
|
45
49
|
(function() {
|
|
46
50
|
if (document.getElementById("${CURSOR_ID}")) return;
|
|
47
51
|
var el = document.createElement("div");
|
|
@@ -56,145 +60,154 @@ function buildInitScript(opts: Required<GhostCursorOptions>): string {
|
|
|
56
60
|
const installedPages = new WeakSet<Page>();
|
|
57
61
|
|
|
58
62
|
export async function ensureGhostCursor(
|
|
59
|
-
|
|
60
|
-
|
|
63
|
+
page: Page,
|
|
64
|
+
options?: GhostCursorOptions,
|
|
61
65
|
): Promise<void> {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
66
|
+
const existingOpts = (page as any).__librettoGhostCursorOpts as
|
|
67
|
+
| Required<GhostCursorOptions>
|
|
68
|
+
| undefined;
|
|
69
|
+
const opts = { ...DEFAULTS, ...(existingOpts ?? {}), ...options };
|
|
70
|
+
const initScript = buildInitScript(opts);
|
|
71
|
+
|
|
72
|
+
if (!installedPages.has(page)) {
|
|
73
|
+
installedPages.add(page);
|
|
74
|
+
await page.addInitScript({ content: initScript });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Store options on the page for later use by move/click
|
|
78
|
+
(page as any).__librettoGhostCursorOpts = opts;
|
|
79
|
+
|
|
80
|
+
// Re-run in-page installer so cursor recovers after page.setContent() or DOM resets.
|
|
81
|
+
try {
|
|
82
|
+
await page.evaluate(new Function(initScript) as () => void);
|
|
83
|
+
} catch {
|
|
84
|
+
// Page might not be ready yet; addInitScript will handle on next navigation
|
|
85
|
+
}
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
export async function moveGhostCursor(
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
page: Page,
|
|
90
|
+
target: { x: number; y: number; durationMs?: number },
|
|
87
91
|
): Promise<void> {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
92
|
+
const opts: Required<GhostCursorOptions> =
|
|
93
|
+
(page as any).__librettoGhostCursorOpts ?? DEFAULTS;
|
|
94
|
+
|
|
95
|
+
const durationMs =
|
|
96
|
+
target.durationMs ??
|
|
97
|
+
Math.min(
|
|
98
|
+
opts.maxDurationMs,
|
|
99
|
+
Math.max(opts.minDurationMs, 200), // default ~200ms if no distance info
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
await page.evaluate(
|
|
104
|
+
({ id, x, y, duration, easing }) => {
|
|
105
|
+
const el = document.getElementById(id);
|
|
106
|
+
if (!el) return;
|
|
107
|
+
el.style.opacity = "1";
|
|
108
|
+
el.style.transition = `transform ${duration}ms ${easing}`;
|
|
109
|
+
el.style.transform = `translate3d(${x}px, ${y}px, 0)`;
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: CURSOR_ID,
|
|
113
|
+
x: target.x,
|
|
114
|
+
y: target.y,
|
|
115
|
+
duration: durationMs,
|
|
116
|
+
easing: opts.easing,
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
await page.waitForTimeout(durationMs);
|
|
121
|
+
} catch {
|
|
122
|
+
// Best-effort: page may have navigated
|
|
123
|
+
}
|
|
114
124
|
}
|
|
115
125
|
|
|
116
126
|
export async function moveGhostCursorWithDistance(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
127
|
+
page: Page,
|
|
128
|
+
from: { x: number; y: number },
|
|
129
|
+
to: { x: number; y: number },
|
|
120
130
|
): Promise<void> {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
const opts: Required<GhostCursorOptions> =
|
|
132
|
+
(page as any).__librettoGhostCursorOpts ?? DEFAULTS;
|
|
133
|
+
|
|
134
|
+
const dx = to.x - from.x;
|
|
135
|
+
const dy = to.y - from.y;
|
|
136
|
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
|
137
|
+
const durationMs = Math.min(
|
|
138
|
+
opts.maxDurationMs,
|
|
139
|
+
Math.max(opts.minDurationMs, distance / opts.speedPxPerMs),
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
await moveGhostCursor(page, { x: to.x, y: to.y, durationMs });
|
|
133
143
|
}
|
|
134
144
|
|
|
135
145
|
export async function ghostClick(
|
|
136
|
-
|
|
137
|
-
|
|
146
|
+
page: Page,
|
|
147
|
+
target: { x: number; y: number },
|
|
138
148
|
): Promise<void> {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
149
|
+
try {
|
|
150
|
+
// Click feedback: scale down on "press"
|
|
151
|
+
await page.evaluate(
|
|
152
|
+
({ id, x, y }) => {
|
|
153
|
+
const el = document.getElementById(id);
|
|
154
|
+
if (!el) return;
|
|
155
|
+
el.style.transform = `translate3d(${x}px, ${y}px, 0) scale(0.93)`;
|
|
156
|
+
el.style.transition = "transform 80ms ease-out";
|
|
157
|
+
},
|
|
158
|
+
{ id: CURSOR_ID, x: target.x, y: target.y },
|
|
159
|
+
);
|
|
160
|
+
await page.waitForTimeout(100);
|
|
161
|
+
|
|
162
|
+
// Release: scale back up
|
|
163
|
+
await page.evaluate(
|
|
164
|
+
({ id, x, y }) => {
|
|
165
|
+
const el = document.getElementById(id);
|
|
166
|
+
if (!el) return;
|
|
167
|
+
el.style.transform = `translate3d(${x}px, ${y}px, 0) scale(1)`;
|
|
168
|
+
el.style.transition = "transform 120ms ease-out";
|
|
169
|
+
},
|
|
170
|
+
{ id: CURSOR_ID, x: target.x, y: target.y },
|
|
171
|
+
);
|
|
172
|
+
await page.waitForTimeout(130);
|
|
173
|
+
} catch {
|
|
174
|
+
// Best-effort
|
|
175
|
+
}
|
|
166
176
|
}
|
|
167
177
|
|
|
168
178
|
export async function hideGhostCursor(page: Page): Promise<void> {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
try {
|
|
180
|
+
await page.evaluate(
|
|
181
|
+
({ id }) => {
|
|
182
|
+
const el = document.getElementById(id);
|
|
183
|
+
if (!el) return;
|
|
184
|
+
el.style.transition = "opacity 300ms ease-out";
|
|
185
|
+
el.style.opacity = "0";
|
|
186
|
+
},
|
|
187
|
+
{ id: CURSOR_ID },
|
|
188
|
+
);
|
|
189
|
+
} catch {
|
|
190
|
+
// Best-effort
|
|
191
|
+
}
|
|
182
192
|
}
|
|
183
193
|
|
|
184
194
|
export async function getGhostCursorPosition(
|
|
185
|
-
|
|
195
|
+
page: Page,
|
|
186
196
|
): Promise<{ x: number; y: number } | null> {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
try {
|
|
198
|
+
return await page.evaluate(
|
|
199
|
+
({ id }) => {
|
|
200
|
+
const el = document.getElementById(id);
|
|
201
|
+
if (!el) return null;
|
|
202
|
+
const match = el.style.transform.match(
|
|
203
|
+
/translate3d\(\s*([\d.-]+)px\s*,\s*([\d.-]+)px/,
|
|
204
|
+
);
|
|
205
|
+
if (!match) return null;
|
|
206
|
+
return { x: parseFloat(match[1]!), y: parseFloat(match[2]!) };
|
|
207
|
+
},
|
|
208
|
+
{ id: CURSOR_ID },
|
|
209
|
+
);
|
|
210
|
+
} catch {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
200
213
|
}
|
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import type { Page } from "playwright";
|
|
2
2
|
|
|
3
3
|
export type HighlightOptions = {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
color?: string;
|
|
5
|
+
zIndex?: number;
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
const HIGHLIGHT_DEFAULTS = {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
color: "rgba(59, 130, 246, 0.25)",
|
|
10
|
+
zIndex: 2147483645,
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
const LAYER_ID = "__libretto_highlight_layer__";
|
|
14
14
|
|
|
15
15
|
function buildHighlightInitScript(opts: { zIndex: number }): string {
|
|
16
|
-
|
|
16
|
+
return `
|
|
17
17
|
(function() {
|
|
18
18
|
if (document.getElementById("${LAYER_ID}")) return;
|
|
19
19
|
var el = document.createElement("div");
|
|
@@ -27,59 +27,59 @@ function buildHighlightInitScript(opts: { zIndex: number }): string {
|
|
|
27
27
|
const installedPages = new WeakSet<Page>();
|
|
28
28
|
|
|
29
29
|
export async function ensureHighlightLayer(
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
page: Page,
|
|
31
|
+
options?: HighlightOptions,
|
|
32
32
|
): Promise<void> {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
33
|
+
const existingOpts = (page as any).__librettoHighlightOpts as
|
|
34
|
+
| { color: string; zIndex: number }
|
|
35
|
+
| undefined;
|
|
36
|
+
const zIndex =
|
|
37
|
+
options?.zIndex ?? existingOpts?.zIndex ?? HIGHLIGHT_DEFAULTS.zIndex;
|
|
38
|
+
const initScript = buildHighlightInitScript({ zIndex });
|
|
39
|
+
|
|
40
|
+
if (!installedPages.has(page)) {
|
|
41
|
+
installedPages.add(page);
|
|
42
|
+
await page.addInitScript({ content: initScript });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Store/refresh options for later.
|
|
46
|
+
(page as any).__librettoHighlightOpts = {
|
|
47
|
+
color: options?.color ?? existingOpts?.color ?? HIGHLIGHT_DEFAULTS.color,
|
|
48
|
+
zIndex,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Re-run in-page installer so overlays recover after page.setContent() or DOM resets.
|
|
52
|
+
try {
|
|
53
|
+
await page.evaluate(new Function(initScript) as () => void);
|
|
54
|
+
} catch {
|
|
55
|
+
// Page may not be ready
|
|
56
|
+
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export type ShowHighlightParams = {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
box: { x: number; y: number; width: number; height: number };
|
|
61
|
+
label?: string;
|
|
62
|
+
color?: string;
|
|
63
|
+
durationMs?: number;
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
export async function showHighlight(
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
page: Page,
|
|
68
|
+
params: ShowHighlightParams,
|
|
69
69
|
): Promise<void> {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
70
|
+
const opts = (page as any).__librettoHighlightOpts ?? HIGHLIGHT_DEFAULTS;
|
|
71
|
+
const color = params.color ?? opts.color;
|
|
72
|
+
const durationMs = params.durationMs ?? 350;
|
|
73
|
+
|
|
74
|
+
try {
|
|
75
|
+
await page.evaluate(
|
|
76
|
+
({ layerId, box, color, label, durationMs }) => {
|
|
77
|
+
const layer = document.getElementById(layerId);
|
|
78
|
+
if (!layer) return;
|
|
79
|
+
|
|
80
|
+
const rect = document.createElement("div");
|
|
81
|
+
rect.className = "__libretto_highlight_rect__";
|
|
82
|
+
rect.style.cssText = `
|
|
83
83
|
position:absolute;
|
|
84
84
|
left:${box.x}px;
|
|
85
85
|
top:${box.y}px;
|
|
@@ -93,10 +93,10 @@ export async function showHighlight(
|
|
|
93
93
|
opacity:1;
|
|
94
94
|
`;
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
if (label) {
|
|
97
|
+
const labelEl = document.createElement("div");
|
|
98
|
+
labelEl.textContent = label;
|
|
99
|
+
labelEl.style.cssText = `
|
|
100
100
|
position:absolute;
|
|
101
101
|
top:-22px;
|
|
102
102
|
left:0;
|
|
@@ -108,39 +108,42 @@ export async function showHighlight(
|
|
|
108
108
|
white-space:nowrap;
|
|
109
109
|
pointer-events:none;
|
|
110
110
|
`;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
111
|
+
rect.appendChild(labelEl);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
layer.appendChild(rect);
|
|
115
|
+
|
|
116
|
+
// Auto-fade after duration
|
|
117
|
+
setTimeout(() => {
|
|
118
|
+
rect.style.opacity = "0";
|
|
119
|
+
setTimeout(() => rect.remove(), 250);
|
|
120
|
+
}, durationMs);
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
layerId: LAYER_ID,
|
|
124
|
+
box: params.box,
|
|
125
|
+
color,
|
|
126
|
+
label: params.label,
|
|
127
|
+
durationMs,
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
} catch {
|
|
131
|
+
// Best-effort
|
|
132
|
+
}
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
export async function clearHighlights(page: Page): Promise<void> {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
136
|
+
try {
|
|
137
|
+
await page.evaluate(
|
|
138
|
+
({ layerId }) => {
|
|
139
|
+
const layer = document.getElementById(layerId);
|
|
140
|
+
if (!layer) return;
|
|
141
|
+
const rects = layer.querySelectorAll(".__libretto_highlight_rect__");
|
|
142
|
+
rects.forEach((r) => r.remove());
|
|
143
|
+
},
|
|
144
|
+
{ layerId: LAYER_ID },
|
|
145
|
+
);
|
|
146
|
+
} catch {
|
|
147
|
+
// Best-effort
|
|
148
|
+
}
|
|
146
149
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
2
|
+
ensureGhostCursor,
|
|
3
|
+
moveGhostCursor,
|
|
4
|
+
moveGhostCursorWithDistance,
|
|
5
|
+
ghostClick,
|
|
6
|
+
hideGhostCursor,
|
|
7
|
+
getGhostCursorPosition,
|
|
8
|
+
type GhostCursorOptions,
|
|
9
|
+
type GhostCursorStyle,
|
|
10
10
|
} from "./ghost-cursor.js";
|
|
11
11
|
|
|
12
12
|
export {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
ensureHighlightLayer,
|
|
14
|
+
showHighlight,
|
|
15
|
+
clearHighlights,
|
|
16
|
+
type HighlightOptions,
|
|
17
|
+
type ShowHighlightParams,
|
|
18
18
|
} from "./highlight.js";
|
|
@@ -3,40 +3,34 @@ import type { MinimalLogger } from "../logger/logger.js";
|
|
|
3
3
|
|
|
4
4
|
export const LIBRETTO_WORKFLOW_BRAND = Symbol.for("libretto.workflow");
|
|
5
5
|
|
|
6
|
-
export type LibrettoWorkflowMetadata = {};
|
|
7
|
-
|
|
8
6
|
export type LibrettoWorkflowContext<S = {}> = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
session: string;
|
|
8
|
+
page: Page;
|
|
9
|
+
logger: MinimalLogger;
|
|
10
|
+
services: S;
|
|
12
11
|
};
|
|
13
12
|
|
|
14
|
-
export type LibrettoWorkflowHandler<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
export type LibrettoWorkflowHandler<
|
|
14
|
+
Input = unknown,
|
|
15
|
+
Output = unknown,
|
|
16
|
+
S = {},
|
|
17
|
+
> = (ctx: LibrettoWorkflowContext<S>, input: Input) => Promise<Output>;
|
|
18
18
|
|
|
19
19
|
export class LibrettoWorkflow<Input = unknown, Output = unknown, S = {}> {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
private readonly handler: LibrettoWorkflowHandler<Input, Output, S>;
|
|
20
|
+
public readonly [LIBRETTO_WORKFLOW_BRAND] = true;
|
|
21
|
+
private readonly handler: LibrettoWorkflowHandler<Input, Output, S>;
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
) {
|
|
28
|
-
this.metadata = metadata;
|
|
29
|
-
this.handler = handler;
|
|
30
|
-
}
|
|
23
|
+
constructor(handler: LibrettoWorkflowHandler<Input, Output, S>) {
|
|
24
|
+
this.handler = handler;
|
|
25
|
+
}
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
async run(ctx: LibrettoWorkflowContext<S>, input: Input): Promise<Output> {
|
|
28
|
+
return this.handler(ctx, input);
|
|
29
|
+
}
|
|
35
30
|
}
|
|
36
31
|
|
|
37
32
|
export function workflow<Input = unknown, Output = unknown, S = {}>(
|
|
38
|
-
|
|
39
|
-
handler: LibrettoWorkflowHandler<Input, Output, S>,
|
|
33
|
+
handler: LibrettoWorkflowHandler<Input, Output, S>,
|
|
40
34
|
): LibrettoWorkflow<Input, Output, S> {
|
|
41
|
-
|
|
35
|
+
return new LibrettoWorkflow(handler);
|
|
42
36
|
}
|