libretto 0.5.1 → 0.5.3-experimental.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 +10 -5
- package/dist/cli/commands/execution.js +38 -12
- package/dist/cli/commands/init.js +4 -21
- package/dist/cli/core/ai-config.js +12 -2
- package/dist/cli/core/browser.js +75 -8
- package/dist/cli/core/session-telemetry.js +429 -172
- package/dist/cli/core/telemetry.js +10 -2
- package/dist/cli/framework/simple-cli.js +4 -0
- package/dist/cli/workers/run-integration-runtime.js +18 -41
- package/dist/cli/workers/run-integration-worker-protocol.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -0
- package/dist/shared/condense-dom/condense-dom.js +11 -56
- package/dist/shared/dom-semantics.d.ts +8 -0
- package/dist/shared/dom-semantics.js +69 -0
- package/dist/shared/run/browser.js +40 -1
- package/dist/shared/visualization/ghost-cursor.js +17 -4
- package/dist/shared/workflow/workflow.d.ts +14 -3
- package/dist/shared/workflow/workflow.js +50 -3
- package/package.json +7 -4
- package/scripts/check-skills-sync.mjs +1 -1
- package/scripts/generate-changelog.ts +132 -0
- package/scripts/skills-libretto.mjs +1 -1
- package/scripts/sync-skills.mjs +1 -1
- package/skills/libretto/SKILL.md +54 -38
- package/skills/libretto/references/action-logs.md +101 -0
- package/skills/libretto/references/auth-profiles.md +1 -2
- package/skills/libretto/references/code-generation-rules.md +10 -6
- package/skills/libretto/references/pages-and-page-targeting.md +1 -1
- package/src/cli/commands/execution.ts +39 -11
- package/src/cli/commands/init.ts +5 -24
- package/src/cli/core/ai-config.ts +12 -1
- package/src/cli/core/browser.ts +82 -8
- package/src/cli/core/session-telemetry.ts +431 -190
- package/src/cli/core/telemetry.ts +23 -1
- package/src/cli/framework/simple-cli.ts +5 -0
- package/src/cli/workers/run-integration-runtime.ts +24 -52
- package/src/cli/workers/run-integration-worker-protocol.ts +2 -1
- package/src/index.ts +4 -0
- package/src/shared/condense-dom/condense-dom.ts +12 -64
- package/src/shared/dom-semantics.ts +68 -0
- package/src/shared/run/browser.ts +53 -0
- package/src/shared/visualization/ghost-cursor.ts +22 -4
- package/src/shared/workflow/workflow.ts +88 -2
- package/scripts/prepare-release.sh +0 -97
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { Agent, type AgentTool, type AgentEvent } from "@mariozechner/pi-agent-core";
|
|
3
|
+
import { getModel } from "@mariozechner/pi-ai";
|
|
4
|
+
import { Type } from "@sinclair/typebox";
|
|
5
|
+
|
|
6
|
+
const tag = process.argv[2];
|
|
7
|
+
if (!tag) {
|
|
8
|
+
console.error("Usage: generate-changelog.ts <tag>");
|
|
9
|
+
console.error("Example: generate-changelog.ts v0.5.2");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
14
|
+
console.error("ANTHROPIC_API_KEY is required.");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ALLOWED_GH_SUBCOMMANDS = new Set(["pr", "release", "repo", "issue"]);
|
|
19
|
+
const ALLOWED_ACTIONS = new Set(["list", "view", "diff", "status", "checks"]);
|
|
20
|
+
|
|
21
|
+
const ghTool: AgentTool = {
|
|
22
|
+
name: "gh",
|
|
23
|
+
label: "GitHub CLI",
|
|
24
|
+
description: [
|
|
25
|
+
"Run a read-only GitHub CLI command. The arguments are passed directly to `gh`.",
|
|
26
|
+
"Examples: 'release list --limit 5', 'pr list --state merged --json number,title',",
|
|
27
|
+
"'pr view 128 --json title,body,files', 'pr diff 128'.",
|
|
28
|
+
"Only read operations are allowed (list, view, diff, etc.). Mutating commands will be rejected.",
|
|
29
|
+
].join(" "),
|
|
30
|
+
parameters: Type.Object({
|
|
31
|
+
args: Type.String({ description: "Arguments to pass to gh (without the leading 'gh')" }),
|
|
32
|
+
}),
|
|
33
|
+
execute: async (_toolCallId, rawParams) => {
|
|
34
|
+
const params = rawParams as { args: string };
|
|
35
|
+
const args = params.args.trim();
|
|
36
|
+
const parts = args.split(/\s+/);
|
|
37
|
+
const subcommand = parts[0];
|
|
38
|
+
|
|
39
|
+
if (!subcommand || !ALLOWED_GH_SUBCOMMANDS.has(subcommand)) {
|
|
40
|
+
throw new Error(`Subcommand '${subcommand}' is not allowed. Allowed: ${[...ALLOWED_GH_SUBCOMMANDS].join(", ")}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const action = parts[1];
|
|
44
|
+
if (!action || !ALLOWED_ACTIONS.has(action)) {
|
|
45
|
+
throw new Error(`Action '${action}' is not allowed. Allowed: ${[...ALLOWED_ACTIONS].join(", ")}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const output = execFileSync("gh", parts, {
|
|
50
|
+
encoding: "utf8",
|
|
51
|
+
timeout: 300_000,
|
|
52
|
+
maxBuffer: 1024 * 1024,
|
|
53
|
+
});
|
|
54
|
+
return { content: [{ type: "text", text: output }], details: {} };
|
|
55
|
+
} catch (err) {
|
|
56
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
57
|
+
throw new Error(`gh command failed: ${message}`);
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const agent = new Agent({
|
|
63
|
+
initialState: {
|
|
64
|
+
systemPrompt: [
|
|
65
|
+
`Generate release notes for the ${tag} release of Libretto.`,
|
|
66
|
+
"",
|
|
67
|
+
"Use the gh tool to explore what changed since the previous release.",
|
|
68
|
+
"Useful queries:",
|
|
69
|
+
"- 'release list --limit 5' to find the previous release tag",
|
|
70
|
+
"- 'pr list --state merged --limit 50 --json number,title,body,labels' to find merged PRs",
|
|
71
|
+
"- 'pr diff NUMBER' to see the full diff of a PR (base to head, not individual commits)",
|
|
72
|
+
"- 'pr view NUMBER --json title,body,files' to see PR details",
|
|
73
|
+
"",
|
|
74
|
+
"IMPORTANT: Always read the full PR diff to understand what actually changed.",
|
|
75
|
+
"Do NOT rely solely on PR titles and descriptions — they may be incomplete or misleading.",
|
|
76
|
+
"The diff is the source of truth for what the release note should say.",
|
|
77
|
+
"",
|
|
78
|
+
"Guidelines:",
|
|
79
|
+
"- Write concise, user-facing release notes in markdown.",
|
|
80
|
+
"- Group changes into sections like Features, Fixes, and Improvements. Only include sections that have entries.",
|
|
81
|
+
"- Focus on what changed from the user's perspective, not internal implementation details.",
|
|
82
|
+
"- Do NOT include PR numbers or links.",
|
|
83
|
+
"- Skip PRs labeled 'skip-changelog'.",
|
|
84
|
+
"- Your response must contain ONLY the raw markdown release notes. No preamble like 'Here are the release notes'. No commentary or explanation. No '---' separators. The very first character of your response must be '#'. Example format:",
|
|
85
|
+
"",
|
|
86
|
+
"## Features",
|
|
87
|
+
"",
|
|
88
|
+
"- **Thing**: Description",
|
|
89
|
+
].join("\n"),
|
|
90
|
+
model: getModel("anthropic", "claude-sonnet-4-6"),
|
|
91
|
+
tools: [ghTool],
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
let finalText = "";
|
|
96
|
+
|
|
97
|
+
agent.subscribe((event: AgentEvent) => {
|
|
98
|
+
if (event.type === "agent_end") {
|
|
99
|
+
const messages = event.messages;
|
|
100
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
101
|
+
const msg = messages[i];
|
|
102
|
+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
103
|
+
for (const block of msg.content) {
|
|
104
|
+
if (typeof block === "object" && "type" in block && block.type === "text" && "text" in block) {
|
|
105
|
+
finalText = block.text as string;
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await agent.prompt("Generate the release notes now.");
|
|
115
|
+
|
|
116
|
+
if (!finalText) {
|
|
117
|
+
console.error("Changelog generation failed: no text output from agent.");
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Strip any preamble before the first markdown heading.
|
|
122
|
+
const headingIndex = finalText.indexOf("\n#");
|
|
123
|
+
if (headingIndex >= 0) {
|
|
124
|
+
finalText = finalText.slice(headingIndex + 1);
|
|
125
|
+
} else if (finalText.startsWith("#")) {
|
|
126
|
+
// Already starts with a heading, keep as-is.
|
|
127
|
+
} else {
|
|
128
|
+
console.error("Changelog generation failed: output does not contain markdown headings.");
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
process.stdout.write(finalText);
|
package/scripts/sync-skills.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { SKILL_DIRS, syncRepoSkills } from "./skills-libretto.mjs";
|
|
7
7
|
|
|
8
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
-
const repoRoot = join(__dirname, "..");
|
|
9
|
+
const repoRoot = join(__dirname, "..", "..", "..");
|
|
10
10
|
|
|
11
11
|
syncRepoSkills(repoRoot);
|
|
12
12
|
console.log(`libretto: synced skill mirrors across ${SKILL_DIRS.join(", ")}`);
|
package/skills/libretto/SKILL.md
CHANGED
|
@@ -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
|
|
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
|
-
-
|
|
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
|
-
-
|
|
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,16 @@ 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
|
+
- Use `exec -` to run multi-line scripts from stdin, especially when the code is too long or complex for a command line argument.
|
|
87
|
+
- Available globals: `page`, `context`, `browser`, `state`, `fetch`, `Buffer`.
|
|
84
88
|
- Let failures throw. Do not hide `exec` failures with `try/catch` or `.catch()`.
|
|
85
89
|
- Do not run multiple `exec` commands in parallel.
|
|
86
90
|
|
|
87
91
|
```bash
|
|
88
92
|
npx libretto exec "return await page.url()"
|
|
89
93
|
npx libretto exec "return await page.locator('button').count()"
|
|
90
|
-
npx libretto exec
|
|
94
|
+
npx libretto exec "await page.locator('button:has-text(\"Continue\")').click()"
|
|
95
|
+
echo "return await page.url()" | npx libretto exec - --session debug-example
|
|
91
96
|
```
|
|
92
97
|
|
|
93
98
|
### `pages`
|
|
@@ -100,45 +105,25 @@ npx libretto pages --session debug-example
|
|
|
100
105
|
npx libretto exec --session debug-example --page <page-id> "return await page.url()"
|
|
101
106
|
```
|
|
102
107
|
|
|
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
108
|
### `run`
|
|
126
109
|
|
|
127
|
-
- Use `run` to verify a workflow file after creating it or editing it.
|
|
110
|
+
- Use `run` to verify a workflow file after creating it or editing it, preferring `run --headless` for the normal fix/verify loop.
|
|
111
|
+
- Plain `run` defaults to headed mode.
|
|
128
112
|
- If the workflow fails, Libretto keeps the browser open. Inspect the failed state with `snapshot` and `exec` before editing code.
|
|
113
|
+
- 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
114
|
- If the workflow pauses, resume it with `npx libretto resume --session <name>`.
|
|
130
115
|
- Re-run the same workflow after each fix to verify the browser behavior end to end.
|
|
131
116
|
|
|
132
117
|
```bash
|
|
133
|
-
npx libretto run ./integration.ts
|
|
134
|
-
npx libretto run ./integration.ts
|
|
135
|
-
npx libretto run ./integration.ts main --auth-profile app.example.com --headed
|
|
118
|
+
npx libretto run ./integration.ts workflowName --headless --params '{"status":"open"}'
|
|
119
|
+
npx libretto run ./integration.ts workflowName --auth-profile app.example.com
|
|
136
120
|
```
|
|
137
121
|
|
|
138
122
|
### `resume`
|
|
139
123
|
|
|
140
|
-
- Workflows pause by calling `await pause()` in the workflow file.
|
|
141
|
-
-
|
|
124
|
+
- Workflows pause by calling `await pause("session-name")` in the workflow file. Import `pause` from `"libretto"`.
|
|
125
|
+
- `pause(session)` is a no-op when `NODE_ENV === "production"`.
|
|
126
|
+
- Use `resume` when a workflow hit a `pause()` call.
|
|
142
127
|
- Keep resuming the same session until the workflow completes or pauses again.
|
|
143
128
|
|
|
144
129
|
```bash
|
|
@@ -147,7 +132,7 @@ npx libretto resume --session debug-example
|
|
|
147
132
|
|
|
148
133
|
### `save`
|
|
149
134
|
|
|
150
|
-
- Use `save` when the user
|
|
135
|
+
- Use `save` only when the user explicitly asks to save or reuse authenticated browser state.
|
|
151
136
|
|
|
152
137
|
```bash
|
|
153
138
|
npx libretto save app.example.com
|
|
@@ -155,7 +140,7 @@ npx libretto save app.example.com
|
|
|
155
140
|
|
|
156
141
|
### `close`
|
|
157
142
|
|
|
158
|
-
- Use `close`
|
|
143
|
+
- 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
144
|
- `close --all` is available for workspace cleanup.
|
|
160
145
|
|
|
161
146
|
```bash
|
|
@@ -163,6 +148,36 @@ npx libretto close --session debug-example
|
|
|
163
148
|
npx libretto close --all
|
|
164
149
|
```
|
|
165
150
|
|
|
151
|
+
## Session Logs
|
|
152
|
+
|
|
153
|
+
Session state is stored in `.libretto/sessions/<session>/state.json`.
|
|
154
|
+
|
|
155
|
+
Session logs are JSONL files at `.libretto/sessions/<session>/`:
|
|
156
|
+
|
|
157
|
+
- CLI logs are in `.libretto/sessions/<session>/logs.jsonl`.
|
|
158
|
+
- Action logs are in `.libretto/sessions/<session>/actions.jsonl`.
|
|
159
|
+
- Network logs are in `.libretto/sessions/<session>/network.jsonl`.
|
|
160
|
+
|
|
161
|
+
Use `jq` to query jsonl logs directly — for any filtering, slicing, or inspection task.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Last 20 action entries
|
|
165
|
+
tail -n 20 .libretto/sessions/<session>/actions.jsonl | jq .
|
|
166
|
+
|
|
167
|
+
# POST requests only
|
|
168
|
+
jq 'select(.method == "POST")' .libretto/sessions/<session>/network.jsonl
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Action log (`actions.jsonl`)
|
|
172
|
+
|
|
173
|
+
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).
|
|
174
|
+
|
|
175
|
+
Read `references/action-logs.md` for full field descriptions and user-vs-agent entry semantics.
|
|
176
|
+
|
|
177
|
+
### Network log (`network.jsonl`)
|
|
178
|
+
|
|
179
|
+
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).
|
|
180
|
+
|
|
166
181
|
## Examples
|
|
167
182
|
|
|
168
183
|
### Building new browser automation workflows
|
|
@@ -202,5 +217,6 @@ Assistant: I found the issue. I'll patch the workflow code, then rerun `npx libr
|
|
|
202
217
|
- Read `references/configuration-file-reference.md` when you need to inspect or change `.libretto/config.json` for snapshot model selection or viewport defaults.
|
|
203
218
|
- 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
219
|
- Read `references/code-generation-rules.md` before writing or editing production workflow files.
|
|
205
|
-
- Read `references/auth-profiles.md` when
|
|
220
|
+
- Read `references/auth-profiles.md` when auth-profile behavior is relevant.
|
|
206
221
|
- Read `references/pages-and-page-targeting.md` when a session has multiple open pages or you need `--page`.
|
|
222
|
+
- 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
|
|
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
|
|
|
@@ -6,7 +6,7 @@ Follow the user's existing codebase conventions, abstractions, and patterns when
|
|
|
6
6
|
|
|
7
7
|
## Workflow File Structure
|
|
8
8
|
|
|
9
|
-
Generated files must export a `workflow()` instance so they can be run via `npx libretto run <file> <
|
|
9
|
+
Generated files must export a `workflow()` instance so they can be run via `npx libretto run <file> <workflowName>`. Import `workflow` and its types from `"libretto"`:
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
12
|
import { workflow, pause, type LibrettoWorkflowContext } from "libretto";
|
|
@@ -23,7 +23,8 @@ type Output = {
|
|
|
23
23
|
};
|
|
24
24
|
|
|
25
25
|
export const myWorkflow = workflow<Input, Output>(
|
|
26
|
-
|
|
26
|
+
"myWorkflow",
|
|
27
|
+
async (ctx: LibrettoWorkflowContext, input): Promise<Output> => {
|
|
27
28
|
const { session, page, logger } = ctx;
|
|
28
29
|
|
|
29
30
|
logger.info("workflow-start", { session, query: input.query });
|
|
@@ -37,12 +38,12 @@ export const myWorkflow = workflow<Input, Output>(
|
|
|
37
38
|
|
|
38
39
|
Key points:
|
|
39
40
|
|
|
40
|
-
-
|
|
41
|
-
- `
|
|
41
|
+
- `workflow(name, handler)` takes a unique workflow name and returns the workflow object that Libretto can run.
|
|
42
|
+
- `npx libretto run ./file.ts myWorkflow` resolves `myWorkflow` from the workflows exported by `./file.ts`, so export or re-export the workflow from that file directly or through a `workflows` object, and make sure the run argument matches the name passed to `workflow("myWorkflow", ...)`.
|
|
42
43
|
- `ctx` provides `session`, `page`, `logger`, and `services` (generic, default `{}`)
|
|
43
44
|
- `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
45
|
- Use `await pause(ctx.session)` (or `await pause(session)`) to pause the workflow for debugging. It is a no-op in production.
|
|
46
|
+
- 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
47
|
- The browser is launched and closed automatically by the CLI. Do not launch or close it in the handler.
|
|
47
48
|
|
|
48
49
|
## Passing Application Dependencies via Services
|
|
@@ -57,6 +58,7 @@ import { type Transaction } from "./db";
|
|
|
57
58
|
type MyServices = { tx?: Transaction };
|
|
58
59
|
|
|
59
60
|
export const myWorkflow = workflow<Input, Output, MyServices>(
|
|
61
|
+
"myWorkflow",
|
|
60
62
|
async (ctx, input) => {
|
|
61
63
|
if (ctx.services.tx) {
|
|
62
64
|
await ctx.services.tx.insert(/* ... */);
|
|
@@ -80,12 +82,14 @@ await myWorkflow.run(
|
|
|
80
82
|
When running standalone via `npx libretto run`, services defaults to `{}`,
|
|
81
83
|
so mark fields optional for anything unavailable in that context.
|
|
82
84
|
|
|
83
|
-
## Playwright
|
|
85
|
+
## Playwright DOM Interaction Rules
|
|
84
86
|
|
|
85
87
|
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
88
|
|
|
87
89
|
During the interactive `exec` phase, `page.evaluate` is fine for quick prototyping. In generated production code, translate those patterns into Playwright locators.
|
|
88
90
|
|
|
91
|
+
Before extracting data (for example text, rows, or field values), wait for the target content itself to be ready, not just its container.
|
|
92
|
+
|
|
89
93
|
### Anti-Patterns
|
|
90
94
|
|
|
91
95
|
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
|
|
29
|
+
- Use `pages` to resolve the correct page id, then pass `--page` to `exec` or `snapshot` when needed.
|
|
@@ -605,9 +605,10 @@ async function runIntegrationFromFile(
|
|
|
605
605
|
);
|
|
606
606
|
const payload = JSON.stringify({
|
|
607
607
|
integrationPath: args.integrationPath,
|
|
608
|
-
|
|
608
|
+
workflowName: args.workflowName,
|
|
609
609
|
session: args.session,
|
|
610
610
|
params: args.params,
|
|
611
|
+
credentials: args.credentials,
|
|
611
612
|
headless: args.headless,
|
|
612
613
|
visualize: args.visualize,
|
|
613
614
|
authProfileDomain: args.authProfileDomain,
|
|
@@ -656,11 +657,20 @@ async function runIntegrationFromFile(
|
|
|
656
657
|
console.log("Integration completed.");
|
|
657
658
|
}
|
|
658
659
|
|
|
660
|
+
function readStdinSync(): string | null {
|
|
661
|
+
if (process.stdin.isTTY === true) return null;
|
|
662
|
+
try {
|
|
663
|
+
const content = readFileSync(0, "utf8");
|
|
664
|
+
return content.trim().length > 0 ? content : null;
|
|
665
|
+
} catch {
|
|
666
|
+
return null;
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
659
670
|
export const execInput = SimpleCLI.input({
|
|
660
671
|
positionals: [
|
|
661
|
-
SimpleCLI.positional("
|
|
672
|
+
SimpleCLI.positional("code", z.string().optional(), {
|
|
662
673
|
help: "Playwright TypeScript code to execute",
|
|
663
|
-
variadic: true,
|
|
664
674
|
}),
|
|
665
675
|
],
|
|
666
676
|
named: {
|
|
@@ -671,8 +681,8 @@ export const execInput = SimpleCLI.input({
|
|
|
671
681
|
page: pageOption(),
|
|
672
682
|
},
|
|
673
683
|
}).refine(
|
|
674
|
-
(input) => input.
|
|
675
|
-
`Usage: libretto exec <code> [--session <name>] [--visualize]`,
|
|
684
|
+
(input) => input.code !== undefined,
|
|
685
|
+
`Usage: libretto exec <code|-> [--session <name>] [--visualize]\n echo '<code>' | libretto exec - [--session <name>] [--visualize]`,
|
|
676
686
|
);
|
|
677
687
|
|
|
678
688
|
export const execCommand = SimpleCLI.command({
|
|
@@ -681,8 +691,15 @@ export const execCommand = SimpleCLI.command({
|
|
|
681
691
|
.input(execInput)
|
|
682
692
|
.use(withRequiredSession())
|
|
683
693
|
.handle(async ({ input, ctx }) => {
|
|
694
|
+
const code = input.code!;
|
|
695
|
+
const codeFromArgsOrStdin = code === "-" ? readStdinSync() : code;
|
|
696
|
+
if (codeFromArgsOrStdin === null) {
|
|
697
|
+
throw new Error(
|
|
698
|
+
"Missing stdin input for `exec -`. Pipe Playwright code into stdin.",
|
|
699
|
+
);
|
|
700
|
+
}
|
|
684
701
|
await runExec(
|
|
685
|
-
|
|
702
|
+
codeFromArgsOrStdin,
|
|
686
703
|
ctx.session,
|
|
687
704
|
ctx.logger,
|
|
688
705
|
input.visualize,
|
|
@@ -690,15 +707,15 @@ export const execCommand = SimpleCLI.command({
|
|
|
690
707
|
);
|
|
691
708
|
});
|
|
692
709
|
|
|
693
|
-
const runUsage = `Usage: libretto run <integrationFile> <
|
|
710
|
+
const runUsage = `Usage: libretto run <integrationFile> <workflowName> [--params <json> | --params-file <path>] [--credentials <json>] [--tsconfig <path>] [--headed|--headless] [--no-visualize] [--viewport WxH]`;
|
|
694
711
|
|
|
695
712
|
export const runInput = SimpleCLI.input({
|
|
696
713
|
positionals: [
|
|
697
714
|
SimpleCLI.positional("integrationFile", z.string().optional(), {
|
|
698
715
|
help: "Path to the integration file",
|
|
699
716
|
}),
|
|
700
|
-
SimpleCLI.positional("
|
|
701
|
-
help: "
|
|
717
|
+
SimpleCLI.positional("workflowName", z.string().optional(), {
|
|
718
|
+
help: "Workflow name to run (from workflow(name, handler))",
|
|
702
719
|
}),
|
|
703
720
|
],
|
|
704
721
|
named: {
|
|
@@ -710,6 +727,9 @@ export const runInput = SimpleCLI.input({
|
|
|
710
727
|
name: "params-file",
|
|
711
728
|
help: "Path to a JSON params file",
|
|
712
729
|
}),
|
|
730
|
+
credentials: SimpleCLI.option(z.string().optional(), {
|
|
731
|
+
help: "Inline JSON credentials passed to ctx.credentials",
|
|
732
|
+
}),
|
|
713
733
|
tsconfig: SimpleCLI.option(z.string().optional(), {
|
|
714
734
|
help: "Path to a tsconfig used for workflow module resolution",
|
|
715
735
|
}),
|
|
@@ -729,7 +749,7 @@ export const runInput = SimpleCLI.input({
|
|
|
729
749
|
},
|
|
730
750
|
})
|
|
731
751
|
.refine(
|
|
732
|
-
(input) => Boolean(input.integrationFile && input.
|
|
752
|
+
(input) => Boolean(input.integrationFile && input.workflowName),
|
|
733
753
|
runUsage,
|
|
734
754
|
)
|
|
735
755
|
.refine(
|
|
@@ -772,6 +792,13 @@ export const runCommand = SimpleCLI.command({
|
|
|
772
792
|
assertSessionAvailableForStart(ctx.session, ctx.logger);
|
|
773
793
|
|
|
774
794
|
const params = resolveRunParams(input.params, input.paramsFile);
|
|
795
|
+
const rawCredentials = input.credentials
|
|
796
|
+
? parseJsonArg("--credentials", input.credentials)
|
|
797
|
+
: undefined;
|
|
798
|
+
if (rawCredentials !== undefined && (typeof rawCredentials !== "object" || rawCredentials === null || Array.isArray(rawCredentials))) {
|
|
799
|
+
throw new Error("--credentials must be a JSON object (e.g., '{\"key\": \"value\"}').");
|
|
800
|
+
}
|
|
801
|
+
const credentials = rawCredentials as Record<string, unknown> | undefined;
|
|
775
802
|
const headlessMode = input.headed
|
|
776
803
|
? false
|
|
777
804
|
: input.headless
|
|
@@ -786,9 +813,10 @@ export const runCommand = SimpleCLI.command({
|
|
|
786
813
|
await runIntegrationFromFile(
|
|
787
814
|
{
|
|
788
815
|
integrationPath: input.integrationFile!,
|
|
789
|
-
|
|
816
|
+
workflowName: input.workflowName!,
|
|
790
817
|
session: ctx.session,
|
|
791
818
|
params,
|
|
819
|
+
credentials,
|
|
792
820
|
tsconfigPath: input.tsconfig,
|
|
793
821
|
headless: headlessMode ?? false,
|
|
794
822
|
visualize,
|