libretto 0.5.1 → 0.5.2

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.
@@ -1,22 +1,12 @@
1
- const TEST_ATTRS = /* @__PURE__ */ new Set(["data-testid", "data-test", "data-qa", "data-cy"]);
2
- const TRUSTED_ATTRS = /* @__PURE__ */ new Set([
3
- "id",
4
- "name",
5
- "for",
6
- "tabindex",
7
- "contenteditable",
8
- "role",
9
- "title",
10
- "alt",
11
- "type",
12
- "value",
13
- "placeholder",
14
- "autocomplete",
15
- "href",
16
- "action",
17
- "method",
18
- "src"
19
- ]);
1
+ import {
2
+ filterSemanticClasses,
3
+ INTERACTIVE_ROLE_NAMES,
4
+ INTERACTIVE_TAG_NAMES,
5
+ TEST_ATTRIBUTE_NAMES,
6
+ TRUSTED_ATTRIBUTE_NAMES
7
+ } from "../dom-semantics.js";
8
+ const TEST_ATTRS = new Set(TEST_ATTRIBUTE_NAMES);
9
+ const TRUSTED_ATTRS = new Set(TRUSTED_ATTRIBUTE_NAMES);
20
10
  const STATE_ATTRS = /* @__PURE__ */ new Set([
21
11
  "disabled",
22
12
  "hidden",
@@ -55,28 +45,8 @@ const SCRIPT_ATTRS = /* @__PURE__ */ new Set([
55
45
  "referrerpolicy"
56
46
  ]);
57
47
  const STYLE_TAG_ATTRS = /* @__PURE__ */ new Set(["media", "type", "nonce", "title"]);
58
- const INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
59
- "a",
60
- "button",
61
- "input",
62
- "select",
63
- "textarea",
64
- "form",
65
- "details",
66
- "dialog",
67
- "label"
68
- ]);
69
- const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
70
- "button",
71
- "link",
72
- "tab",
73
- "menuitem",
74
- "checkbox",
75
- "radio",
76
- "switch",
77
- "slider",
78
- "combobox"
79
- ]);
48
+ const INTERACTIVE_TAGS = new Set(INTERACTIVE_TAG_NAMES);
49
+ const INTERACTIVE_ROLES = new Set(INTERACTIVE_ROLE_NAMES);
80
50
  const OPEN_TAG_PATTERN = /<([a-zA-Z][\w:-]*)(\s(?:[^"'<>/]|"[^"]*"|'[^']*')*)?\s*(\/?)>/g;
81
51
  function condenseDom(html) {
82
52
  const originalLength = html.length;
@@ -337,21 +307,6 @@ function normalizeUrlValue(value) {
337
307
  return `${value.slice(0, 96)}[omitted]`;
338
308
  }
339
309
  }
340
- function filterSemanticClasses(value) {
341
- const classes = value.split(/\s+/).filter(Boolean);
342
- const kept = classes.filter((cls) => !isObfuscatedClass(cls));
343
- return kept.join(" ");
344
- }
345
- function isObfuscatedClass(cls) {
346
- if (cls.length > 80) return true;
347
- if (/^_?[0-9a-f]{6,}$/i.test(cls)) return true;
348
- if (/^[a-z]+_[0-9a-f]{4,}$/i.test(cls)) return true;
349
- if (/^[a-z]{1,2}[0-9]{2,}$/i.test(cls)) return true;
350
- const digits = (cls.match(/[0-9]/g) || []).length;
351
- const letters = (cls.match(/[a-zA-Z]/g) || []).length;
352
- if (cls.length >= 6 && digits >= letters * 0.5 && digits >= 2) return true;
353
- return false;
354
- }
355
310
  function parseAttributes(rawAttrs) {
356
311
  const attrs = [];
357
312
  const attrPattern = /([^\s"'<>\/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g;
@@ -0,0 +1,8 @@
1
+ declare const TEST_ATTRIBUTE_NAMES: readonly ["data-testid", "data-test", "data-qa", "data-cy"];
2
+ declare const TRUSTED_ATTRIBUTE_NAMES: readonly ["id", "name", "for", "tabindex", "contenteditable", "role", "title", "alt", "type", "value", "placeholder", "autocomplete", "href", "action", "method", "src"];
3
+ declare const INTERACTIVE_TAG_NAMES: readonly ["a", "button", "input", "select", "textarea", "form", "details", "dialog", "label"];
4
+ declare const INTERACTIVE_ROLE_NAMES: readonly ["button", "link", "tab", "menuitem", "checkbox", "radio", "switch", "slider", "combobox"];
5
+ declare function filterSemanticClasses(value: string): string;
6
+ declare function isObfuscatedClass(cls: string): boolean;
7
+
8
+ export { INTERACTIVE_ROLE_NAMES, INTERACTIVE_TAG_NAMES, TEST_ATTRIBUTE_NAMES, TRUSTED_ATTRIBUTE_NAMES, filterSemanticClasses, isObfuscatedClass };
@@ -0,0 +1,69 @@
1
+ const TEST_ATTRIBUTE_NAMES = [
2
+ "data-testid",
3
+ "data-test",
4
+ "data-qa",
5
+ "data-cy"
6
+ ];
7
+ const TRUSTED_ATTRIBUTE_NAMES = [
8
+ "id",
9
+ "name",
10
+ "for",
11
+ "tabindex",
12
+ "contenteditable",
13
+ "role",
14
+ "title",
15
+ "alt",
16
+ "type",
17
+ "value",
18
+ "placeholder",
19
+ "autocomplete",
20
+ "href",
21
+ "action",
22
+ "method",
23
+ "src"
24
+ ];
25
+ const INTERACTIVE_TAG_NAMES = [
26
+ "a",
27
+ "button",
28
+ "input",
29
+ "select",
30
+ "textarea",
31
+ "form",
32
+ "details",
33
+ "dialog",
34
+ "label"
35
+ ];
36
+ const INTERACTIVE_ROLE_NAMES = [
37
+ "button",
38
+ "link",
39
+ "tab",
40
+ "menuitem",
41
+ "checkbox",
42
+ "radio",
43
+ "switch",
44
+ "slider",
45
+ "combobox"
46
+ ];
47
+ function filterSemanticClasses(value) {
48
+ const classes = value.split(/\s+/).filter(Boolean);
49
+ const kept = classes.filter((cls) => !isObfuscatedClass(cls));
50
+ return kept.join(" ");
51
+ }
52
+ function isObfuscatedClass(cls) {
53
+ if (cls.length > 80) return true;
54
+ if (/^_?[0-9a-f]{6,}$/i.test(cls)) return true;
55
+ if (/^[a-z]+_[0-9a-f]{4,}$/i.test(cls)) return true;
56
+ if (/^[a-z]{1,2}[0-9]{2,}$/i.test(cls)) return true;
57
+ const digits = (cls.match(/[0-9]/g) || []).length;
58
+ const letters = (cls.match(/[a-zA-Z]/g) || []).length;
59
+ if (cls.length >= 6 && digits >= letters * 0.5 && digits >= 2) return true;
60
+ return false;
61
+ }
62
+ export {
63
+ INTERACTIVE_ROLE_NAMES,
64
+ INTERACTIVE_TAG_NAMES,
65
+ TEST_ATTRIBUTE_NAMES,
66
+ TRUSTED_ATTRIBUTE_NAMES,
67
+ filterSemanticClasses,
68
+ isObfuscatedClass
69
+ };
@@ -8,6 +8,7 @@ import {
8
8
  SESSION_STATE_VERSION,
9
9
  SessionStateFileSchema
10
10
  } from "../state/session-state.js";
11
+ import { readLibrettoConfig } from "../../cli/core/ai-config.js";
11
12
  async function pickFreePort() {
12
13
  return await new Promise((resolve, reject) => {
13
14
  const server = createServer();
@@ -23,6 +24,41 @@ async function pickFreePort() {
23
24
  });
24
25
  });
25
26
  }
27
+ function resolveWindowPosition() {
28
+ return readLibrettoConfig().windowPosition;
29
+ }
30
+ async function applyWindowPosition(browser, context, page, windowPosition) {
31
+ if (!windowPosition) {
32
+ return;
33
+ }
34
+ const requestedBounds = {
35
+ left: windowPosition.x,
36
+ top: windowPosition.y,
37
+ windowState: "normal"
38
+ };
39
+ const pageCdp = await context.newCDPSession(page);
40
+ let browserCdp;
41
+ try {
42
+ const targetInfo = await pageCdp.send("Target.getTargetInfo");
43
+ const targetId = targetInfo.targetInfo?.targetId;
44
+ browserCdp = await browser.newBrowserCDPSession();
45
+ const windowResult = await browserCdp.send(
46
+ "Browser.getWindowForTarget",
47
+ targetId ? { targetId } : {}
48
+ );
49
+ await browserCdp.send("Browser.setWindowBounds", {
50
+ windowId: windowResult.windowId,
51
+ bounds: requestedBounds
52
+ });
53
+ await new Promise((resolve) => setTimeout(resolve, 250));
54
+ } catch {
55
+ } finally {
56
+ await pageCdp.detach().catch(() => {
57
+ });
58
+ await browserCdp?.detach().catch(() => {
59
+ });
60
+ }
61
+ }
26
62
  async function launchBrowser({
27
63
  sessionName,
28
64
  headless = false,
@@ -30,12 +66,14 @@ async function launchBrowser({
30
66
  storageStatePath
31
67
  }) {
32
68
  const debugPort = await pickFreePort();
69
+ const windowPosition = headless ? void 0 : resolveWindowPosition();
33
70
  const browser = await chromium.launch({
34
71
  headless,
35
72
  args: [
36
73
  "--disable-blink-features=AutomationControlled",
37
74
  `--remote-debugging-port=${debugPort}`,
38
- "--no-focus-on-check"
75
+ "--no-focus-on-check",
76
+ ...windowPosition ? [`--window-position=${windowPosition.x},${windowPosition.y}`] : []
39
77
  ]
40
78
  });
41
79
  const context = await browser.newContext({
@@ -43,6 +81,7 @@ async function launchBrowser({
43
81
  ...storageStatePath ? { storageState: storageStatePath } : {}
44
82
  });
45
83
  const page = await context.newPage();
84
+ await applyWindowPosition(browser, context, page, windowPosition);
46
85
  page.setDefaultTimeout(3e4);
47
86
  page.setDefaultNavigationTimeout(45e3);
48
87
  const metadataPath = ensureLibrettoSessionStatePath(sessionName);
@@ -1,7 +1,7 @@
1
1
  const DEFAULTS = {
2
2
  style: "minimal",
3
3
  color: "rgba(255, 70, 70, 0.9)",
4
- size: 20,
4
+ size: 23,
5
5
  zIndex: 2147483646,
6
6
  easing: "cubic-bezier(0.16, 1, 0.3, 1)",
7
7
  minDurationMs: 100,
@@ -16,19 +16,32 @@ function buildCursorSvg(style, color, size) {
16
16
  if (style === "screenstudio") {
17
17
  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>`;
18
18
  }
19
- return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
19
+ return `<svg width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="display:block;filter:drop-shadow(0 2px 6px rgba(15,23,42,0.22));">
20
20
  <path d="M5 3L19 12L12 13L9 20L5 3Z" fill="${color}" stroke="rgba(0,0,0,0.3)" stroke-width="1"/>
21
21
  </svg>`;
22
22
  }
23
+ function buildCursorMarkup(style, color, size) {
24
+ const cursor = buildCursorSvg(style, color, size);
25
+ const badgeHeight = Math.max(12, Math.round(size * 0.54));
26
+ const fontSize = Math.max(8, Math.round(size * 0.28));
27
+ const minWidth = Math.max(28, Math.round(size * 1.28));
28
+ const paddingX = Math.max(5, Math.round(size * 0.2));
29
+ const left = Math.round(size * 0.84);
30
+ const top = Math.round(size * 0.74);
31
+ const width = Math.round(size * 2.4);
32
+ const height = Math.round(size * 1.95);
33
+ const badge = `<div aria-hidden="true" style="position:absolute;left:${left}px;top:${top}px;display:flex;align-items:center;justify-content:center;min-width:${minWidth}px;height:${badgeHeight}px;padding:0 ${paddingX}px;border-radius:${badgeHeight}px;background:${color};color:rgba(255,255,255,0.96);font:700 ${fontSize}px/1 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;letter-spacing:0.02em;white-space:nowrap;border:1px solid rgba(0,0,0,0.16);box-shadow:0 4px 12px rgba(0,0,0,0.14);transform-origin:left center;">Agent</div>`;
34
+ return `<div style="position:relative;width:${width}px;height:${height}px;overflow:visible;">${cursor}${badge}</div>`;
35
+ }
23
36
  function buildInitScript(opts) {
24
- const svg = buildCursorSvg(opts.style, opts.color, opts.size);
37
+ const markup = buildCursorMarkup(opts.style, opts.color, opts.size);
25
38
  return `
26
39
  (function() {
27
40
  if (document.getElementById("${CURSOR_ID}")) return;
28
41
  var el = document.createElement("div");
29
42
  el.id = "${CURSOR_ID}";
30
43
  el.style.cssText = "position:fixed;top:0;left:0;z-index:${opts.zIndex};pointer-events:none;transform:translate3d(-100px,-100px,0);transition:none;will-change:transform,opacity;opacity:0;";
31
- el.innerHTML = ${JSON.stringify(svg)};
44
+ el.innerHTML = ${JSON.stringify(markup)};
32
45
  document.documentElement.appendChild(el);
33
46
  })();
34
47
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libretto",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "AI-powered browser automation library and CLI built on Playwright",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -10,30 +10,31 @@ metadata:
10
10
  ## How Libretto Works
11
11
 
12
12
  - Libretto is a CLI for exploring live websites and building or debugging reusable browser automation scripts.
13
- - Use Libretto to inspect the real site first: open pages, observe state, inspect requests, and prototype interactions before writing code.
13
+ - Use Libretto commands to inspect the site and open pages, observe state, inspect requests, and prototype interactions.
14
14
  - Libretto work must end in script changes. Create or edit the workflow file instead of stopping at interactive exploration.
15
15
 
16
16
  ## Default Integration Approach
17
17
 
18
- - Prefer network requests first for new integrations.
18
+ - Prefer network requests first for new integrations unless the user explicitly asks for Playwright or UI automation, then do not use the site's internal API.
19
19
  - Read `references/site-security-review.md` before committing to a network-first approach on a new site.
20
20
  - Fall back to passive interception or Playwright-driven UI automation when the security review rules network requests out, the request path is not workable, or the user explicitly asks for Playwright.
21
21
 
22
22
  ## Setup
23
23
 
24
- - Ask the user to set up snapshot analysis before relying on `snapshot` for page understanding.
25
- - Use `npx libretto init` for first-time workspace setup.
24
+ - Use `npx libretto init` for first-time workspace setup (sets up config file and snapshot command).
26
25
  - If credentials are already available, `npx libretto ai configure openai|anthropic|gemini|vertex` is usually enough.
27
26
 
28
27
  ## Working Rules
29
28
 
30
29
  - Announce which session you are using and what page you are on.
31
30
  - Ask instead of guessing when it is unclear what to click, type, or submit.
31
+ - Do not treat visibility as interactivity. If an element will not act, inspect blockers before retrying.
32
+ - Defer repo/code review until you begin generating code, unless the user explicitly asks for it earlier.
32
33
  - Read and follow guidelines in `references/code-generation-rules.md` before generating or editing production workflow code.
33
- - After interactive exploration and code generation, test key logic with `exec`, then verify the workflow file with `run --headless`.
34
+ - Validation requires a successful clean `run --headless` with confirmation of the actual returned output, not just process success. If the user wants to watch the finished workflow, do a final headed `run` after headless validation succeeds.
35
+ - Treat exploration sessions as disposable unless the user explicitly wants one kept open.
34
36
  - Get explicit user confirmation before mutating actions or replaying network requests that may have side effects.
35
37
  - Never run multiple `exec` commands at the same time.
36
- - Keep the browser session open until the user says the session is done.
37
38
 
38
39
  ## Commands
39
40
 
@@ -63,6 +64,7 @@ npx libretto connect http://127.0.0.1:9223 --session another-session
63
64
 
64
65
  - Use `snapshot` as the primary page observation tool.
65
66
  - Always provide both `--objective` and `--context`.
67
+ - A single snapshot objective can include multiple questions or analysis tasks.
66
68
  - Use it before guessing at selectors, after workflow failures, and whenever the visible page state is unclear.
67
69
  - When analysis is involved, expect it to take time. Use a timeout of at least 2 minutes for shell-wrapped calls.
68
70
 
@@ -81,13 +83,14 @@ npx libretto snapshot \
81
83
 
82
84
  - Use `exec` for focused inspection and short-lived interaction experiments.
83
85
  - Use `exec` to validate selectors, inspect data, or prototype a step before you encode it in the workflow file.
86
+ - Available globals: `page`, `context`, `browser`, `state`, `fetch`, `Buffer`.
84
87
  - Let failures throw. Do not hide `exec` failures with `try/catch` or `.catch()`.
85
88
  - Do not run multiple `exec` commands in parallel.
86
89
 
87
90
  ```bash
88
91
  npx libretto exec "return await page.url()"
89
92
  npx libretto exec "return await page.locator('button').count()"
90
- npx libretto exec --visualize "await page.locator('button:has-text(\"Continue\")').click()"
93
+ npx libretto exec "await page.locator('button:has-text(\"Continue\")').click()"
91
94
  ```
92
95
 
93
96
  ### `pages`
@@ -100,45 +103,25 @@ npx libretto pages --session debug-example
100
103
  npx libretto exec --session debug-example --page <page-id> "return await page.url()"
101
104
  ```
102
105
 
103
- ### `network`
104
-
105
- - Use `network` to inspect the requests the page already made.
106
- - Prefer this when discovering how a site loads data or when validating whether a network-first approach is workable.
107
- - Filter aggressively by method, URL pattern, and page when the log is noisy.
108
-
109
- ```bash
110
- npx libretto network --session debug-example --last 20
111
- npx libretto network --session debug-example --method POST --filter 'referral|patient'
112
- npx libretto network --session debug-example --page <page-id>
113
- ```
114
-
115
- ### `actions`
116
-
117
- - Use `actions` when you need a quick record of recent user or agent interactions in the current session.
118
- - Keep it lightweight. It is a helper for orientation, not the main integration-building workflow.
119
-
120
- ```bash
121
- npx libretto actions --session debug-example --last 20
122
- npx libretto actions --session debug-example --action click --source user
123
- ```
124
-
125
106
  ### `run`
126
107
 
127
- - Use `run` to verify a workflow file after creating it or editing it.
108
+ - Use `run` to verify a workflow file after creating it or editing it, preferring `run --headless` for the normal fix/verify loop.
109
+ - Plain `run` defaults to headed mode.
128
110
  - If the workflow fails, Libretto keeps the browser open. Inspect the failed state with `snapshot` and `exec` before editing code.
111
+ - Insert `await pause(session)` statements in the workflow file when you need to stop at specific states for interactive debugging, like breakpoints in the browser flow.
129
112
  - If the workflow pauses, resume it with `npx libretto resume --session <name>`.
130
113
  - Re-run the same workflow after each fix to verify the browser behavior end to end.
131
114
 
132
115
  ```bash
133
- npx libretto run ./integration.ts main
134
- npx libretto run ./integration.ts main --params '{"status":"open"}'
135
- npx libretto run ./integration.ts main --auth-profile app.example.com --headed
116
+ npx libretto run ./integration.ts main --headless --params '{"status":"open"}'
117
+ npx libretto run ./integration.ts main --auth-profile app.example.com
136
118
  ```
137
119
 
138
120
  ### `resume`
139
121
 
140
- - Workflows pause by calling `await pause()` in the workflow file.
141
- - Use `resume` when a workflow hit `await pause()`.
122
+ - Workflows pause by calling `await pause("session-name")` in the workflow file. Import `pause` from `"libretto"`.
123
+ - `pause(session)` is a no-op when `NODE_ENV === "production"`.
124
+ - Use `resume` when a workflow hit a `pause()` call.
142
125
  - Keep resuming the same session until the workflow completes or pauses again.
143
126
 
144
127
  ```bash
@@ -147,7 +130,7 @@ npx libretto resume --session debug-example
147
130
 
148
131
  ### `save`
149
132
 
150
- - Use `save` when the user logs in manually and wants to reuse that authenticated browser state later.
133
+ - Use `save` only when the user explicitly asks to save or reuse authenticated browser state.
151
134
 
152
135
  ```bash
153
136
  npx libretto save app.example.com
@@ -155,7 +138,7 @@ npx libretto save app.example.com
155
138
 
156
139
  ### `close`
157
140
 
158
- - Use `close` only when the user is done with the session.
141
+ - Use `close` when the user is done with the session or an exploration session is no longer helping progress (unless the user asked to keep watching that browser).
159
142
  - `close --all` is available for workspace cleanup.
160
143
 
161
144
  ```bash
@@ -163,6 +146,36 @@ npx libretto close --session debug-example
163
146
  npx libretto close --all
164
147
  ```
165
148
 
149
+ ## Session Logs
150
+
151
+ Session state is stored in `.libretto/sessions/<session>/state.json`.
152
+
153
+ Session logs are JSONL files at `.libretto/sessions/<session>/`:
154
+
155
+ - CLI logs are in `.libretto/sessions/<session>/logs.jsonl`.
156
+ - Action logs are in `.libretto/sessions/<session>/actions.jsonl`.
157
+ - Network logs are in `.libretto/sessions/<session>/network.jsonl`.
158
+
159
+ Use `jq` to query jsonl logs directly — for any filtering, slicing, or inspection task.
160
+
161
+ ```bash
162
+ # Last 20 action entries
163
+ tail -n 20 .libretto/sessions/<session>/actions.jsonl | jq .
164
+
165
+ # POST requests only
166
+ jq 'select(.method == "POST")' .libretto/sessions/<session>/network.jsonl
167
+ ```
168
+
169
+ ### Action log (`actions.jsonl`)
170
+
171
+ Key fields: `ts` (ISO timestamp), `source` (`user` or `agent`), `action` (`click`, `fill`, `goto`, etc.), `selector` (locator used by the agent), `bestSemanticSelector` (canonical selector for user DOM events), `success` (boolean), `url` (navigation target), `value` (typed or submitted value), `error` (message on failure).
172
+
173
+ Read `references/action-logs.md` for full field descriptions and user-vs-agent entry semantics.
174
+
175
+ ### Network log (`network.jsonl`)
176
+
177
+ Key fields: `ts` (ISO timestamp), `method` (HTTP method, e.g. `GET`, `POST`), `url` (request URL), `status` (HTTP status code), `contentType` (response content type), `responseBody` (response body string, may be null).
178
+
166
179
  ## Examples
167
180
 
168
181
  ### Building new browser automation workflows
@@ -202,5 +215,6 @@ Assistant: I found the issue. I'll patch the workflow code, then rerun `npx libr
202
215
  - Read `references/configuration-file-reference.md` when you need to inspect or change `.libretto/config.json` for snapshot model selection or viewport defaults.
203
216
  - Read `references/site-security-review.md` before reviewing the site's security posture and deciding whether to lead with network requests, passive interception, or Playwright DOM automation on a new site.
204
217
  - Read `references/code-generation-rules.md` before writing or editing production workflow files.
205
- - Read `references/auth-profiles.md` when the site requires login and the simplest path is to save local browser state.
218
+ - Read `references/auth-profiles.md` when auth-profile behavior is relevant.
206
219
  - Read `references/pages-and-page-targeting.md` when a session has multiple open pages or you need `--page`.
220
+ - Read `references/action-logs.md` for full action log field descriptions and user-vs-agent event semantics.
@@ -0,0 +1,101 @@
1
+ # Action Logs
2
+
3
+ - Stored at `.libretto/sessions/<session>/actions.jsonl`.
4
+ - One JSON object per line.
5
+ - Query the file directly with `jq`, for example `tail -n 20 .libretto/sessions/<session>/actions.jsonl | jq .`.
6
+ - This is an orientation log, not a replay trace.
7
+
8
+ ## User vs Agent
9
+
10
+ - `agent` entries log the Playwright action Libretto observed, usually as `action` plus `selector`, and sometimes `value`, `url`, `duration`, or `error`.
11
+ - `user` entries log the browser DOM event Libretto captured, so they can include `bestSemanticSelector`, `targetSelector`, `ancestorSelectors`, `nearbyText`, `composedPath`, and `coordinates`.
12
+ - This is why agent entries usually describe what Playwright tried to do, while user entries can describe what element was actually interacted with in the page.
13
+
14
+ ## Fields
15
+
16
+ - `ts`
17
+ ISO timestamp.
18
+
19
+ - `pageId`
20
+ Playwright target id for the page that produced the entry.
21
+
22
+ - `action`
23
+ Logged action name, such as `click`, `dblclick`, `fill`, `goto`, or `reload`.
24
+
25
+ - `source`
26
+ `user` for captured DOM events, `agent` for logged Playwright calls.
27
+
28
+ - `success`
29
+ `true` if the action completed, `false` if Libretto logged a failure.
30
+
31
+ - `selector`
32
+ Selector or locator hint for agent entries.
33
+
34
+ - `bestSemanticSelector`
35
+ Canonical selector for user DOM events.
36
+
37
+ - `targetSelector`
38
+ Selector for the raw DOM event target. Usually only present for user DOM events.
39
+
40
+ - `ancestorSelectors`
41
+ Meaningful ancestor selector candidates for a user DOM event. Ordered from closest meaningful ancestor to farthest meaningful ancestor.
42
+
43
+ - `nearbyText`
44
+ Short visible text near the event target, used as human context.
45
+
46
+ - `composedPath`
47
+ Compact event-path summaries. Ordered from the raw event target to farthest ancestor.
48
+
49
+ - `coordinates`
50
+ Rounded `clientX` and `clientY` for pointer-style events:
51
+
52
+ ```json
53
+ { "x": 42, "y": 24 }
54
+ ```
55
+
56
+ - `value`
57
+ Typed, selected, or submitted value when the action had one.
58
+
59
+ - `url`
60
+ Navigation target or recorded page URL for navigation-style actions.
61
+
62
+ - `duration`
63
+ Elapsed time in milliseconds when Libretto recorded it.
64
+
65
+ - `error`
66
+ Error message when the action failed.
67
+
68
+ ## User Example
69
+
70
+ ```json
71
+ {
72
+ "ts": "2026-03-20T22:34:56.123Z",
73
+ "pageId": "A1B2C3D4E5F6",
74
+ "action": "dblclick",
75
+ "source": "user",
76
+ "bestSemanticSelector": "button#saveBtn",
77
+ "targetSelector": "span",
78
+ "ancestorSelectors": ["button#saveBtn", "form[action=\"/save\"]"],
79
+ "nearbyText": "Save",
80
+ "composedPath": ["span [text=\"Save\"]", "button#saveBtn [text=\"Save\"]"],
81
+ "coordinates": {
82
+ "x": 42,
83
+ "y": 24
84
+ },
85
+ "success": true
86
+ }
87
+ ```
88
+
89
+ ## Agent Example
90
+
91
+ ```json
92
+ {
93
+ "ts": "2026-03-20T22:35:10.456Z",
94
+ "pageId": "A1B2C3D4E5F6",
95
+ "action": "click",
96
+ "source": "agent",
97
+ "selector": "page.getByRole(\"button\", {\"name\":\"Save\"})",
98
+ "duration": 187,
99
+ "success": true
100
+ }
101
+ ```
@@ -1,12 +1,11 @@
1
1
  # Auth Profiles
2
2
 
3
- Use this reference when the target site requires login and the user wants to reuse local authenticated browser state.
3
+ Use this reference only when the user explicitly asks to save or reuse local authenticated browser state.
4
4
 
5
5
  ## When to Use This
6
6
 
7
7
  - The site requires manual login.
8
8
  - The user is running workflows locally.
9
- - Reusing a saved session is simpler than building credential-handling logic into the workflow.
10
9
 
11
10
  ## Workflow
12
11
 
@@ -41,8 +41,8 @@ Key points:
41
41
  - `workflow(handler)` returns a branded workflow object with a `.run(ctx, input)` method. The CLI expects that contract.
42
42
  - `ctx` provides `session`, `page`, `logger`, and `services` (generic, default `{}`)
43
43
  - `input` comes from `--params '{"query":"foo"}'` or `--params-file params.json` on the CLI
44
- - If the site requires a saved login session, pass `--auth-profile <domain>` to the CLI (created via `npx libretto save <domain>`)
45
44
  - Use `await pause(ctx.session)` (or `await pause(session)`) to pause the workflow for debugging. It is a no-op in production.
45
+ - After validation is complete and the workflow is confirmed working end to end, remove all `pause()` calls and pause-only workflow params unless the user explicitly says to keep them.
46
46
  - The browser is launched and closed automatically by the CLI. Do not launch or close it in the handler.
47
47
 
48
48
  ## Passing Application Dependencies via Services
@@ -80,12 +80,14 @@ await myWorkflow.run(
80
80
  When running standalone via `npx libretto run`, services defaults to `{}`,
81
81
  so mark fields optional for anything unavailable in that context.
82
82
 
83
- ## Playwright Locators for DOM Interaction
83
+ ## Playwright DOM Interaction Rules
84
84
 
85
85
  Generated code must use Playwright locator APIs for all DOM interactions. Do not use `page.evaluate()` with `document.querySelector`, `querySelectorAll`, `textContent`, `click()`, or other DOM APIs when a Playwright locator can do the same thing.
86
86
 
87
87
  During the interactive `exec` phase, `page.evaluate` is fine for quick prototyping. In generated production code, translate those patterns into Playwright locators.
88
88
 
89
+ Before extracting data (for example text, rows, or field values), wait for the target content itself to be ready, not just its container.
90
+
89
91
  ### Anti-Patterns
90
92
 
91
93
  These patterns come up frequently during interactive sessions and should not carry over into production code:
@@ -26,4 +26,4 @@ npx libretto snapshot --session debug-flow --page <page-id> --objective "Find th
26
26
 
27
27
  - A session can contain more than one page.
28
28
  - When multiple pages are open, think about page targeting first before debugging selectors.
29
- - Use `pages` to resolve the correct page id, then pass `--page` to `exec`, `snapshot`, `network`, or `actions` when needed.
29
+ - Use `pages` to resolve the correct page id, then pass `--page` to `exec` or `snapshot` when needed.