libretto 0.4.4 → 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 +39 -113
- package/dist/cli/commands/ai.js +1 -1
- package/dist/cli/commands/browser.js +87 -60
- package/dist/cli/commands/execution.js +201 -88
- package/dist/cli/commands/init.js +30 -8
- package/dist/cli/commands/logs.js +5 -6
- 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 +141 -33
- package/dist/cli/core/context.js +7 -18
- package/dist/cli/core/session-telemetry.js +5 -2
- package/dist/cli/core/session.js +23 -10
- package/dist/cli/core/snapshot-analyzer.js +16 -33
- 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 +26 -7
- 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/paths/paths.js +2 -1
- package/dist/shared/paths/repo-root.d.ts +3 -0
- package/dist/shared/paths/repo-root.js +24 -0
- 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 +11 -8
- package/scripts/check-skills-sync.mjs +25 -0
- package/scripts/compare-eval-summary.mjs +47 -0
- package/scripts/postinstall.mjs +26 -17
- 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 +130 -377
- package/skills/libretto/references/auth-profiles.md +30 -0
- package/skills/libretto/{code-generation-rules.md → references/code-generation-rules.md} +27 -42
- package/skills/libretto/references/configuration-file-reference.md +53 -0
- package/skills/libretto/references/pages-and-page-targeting.md +29 -0
- package/skills/libretto/references/site-security-review.md +143 -0
- package/src/cli/cli.ts +86 -0
- package/src/cli/commands/ai.ts +35 -0
- package/src/cli/commands/browser.ts +189 -0
- package/src/cli/commands/execution.ts +822 -0
- package/src/cli/commands/init.ts +350 -0
- package/src/cli/commands/logs.ts +128 -0
- package/src/cli/commands/shared.ts +69 -0
- package/src/cli/commands/snapshot.ts +312 -0
- package/src/cli/core/ai-config.ts +264 -0
- package/src/cli/core/api-snapshot-analyzer.ts +108 -0
- package/src/cli/core/browser.ts +976 -0
- package/src/cli/core/context.ts +127 -0
- package/src/cli/core/pause-signals.ts +35 -0
- package/src/cli/core/session-telemetry.ts +564 -0
- package/src/cli/core/session.ts +223 -0
- package/src/cli/core/snapshot-analyzer.ts +855 -0
- package/src/cli/core/snapshot-api-config.ts +231 -0
- package/src/cli/core/telemetry.ts +459 -0
- package/src/cli/framework/simple-cli.ts +1340 -0
- package/src/cli/index.ts +13 -0
- package/src/cli/router.ts +20 -0
- package/src/cli/workers/run-integration-runtime.ts +338 -0
- package/src/cli/workers/run-integration-worker-protocol.ts +16 -0
- package/src/cli/workers/run-integration-worker.ts +72 -0
- package/src/index.ts +127 -0
- package/src/runtime/download/download.ts +104 -0
- package/src/runtime/download/index.ts +7 -0
- package/src/runtime/extract/extract.ts +102 -0
- package/src/runtime/extract/index.ts +1 -0
- package/src/runtime/network/index.ts +5 -0
- package/src/runtime/network/network.ts +119 -0
- package/{dist/runtime/recovery/agent.cjs → src/runtime/recovery/agent.ts} +114 -76
- package/src/runtime/recovery/errors.ts +155 -0
- package/src/runtime/recovery/index.ts +7 -0
- package/src/runtime/recovery/recovery.ts +53 -0
- package/{dist/shared/condense-dom/condense-dom.cjs → src/shared/condense-dom/condense-dom.ts} +249 -124
- package/src/shared/config/config.ts +3 -0
- package/src/shared/config/index.ts +0 -0
- package/src/shared/debug/index.ts +1 -0
- package/src/shared/debug/pause.ts +91 -0
- package/src/shared/instrumentation/errors.ts +84 -0
- package/src/shared/instrumentation/index.ts +9 -0
- package/src/shared/instrumentation/instrument.ts +406 -0
- package/src/shared/llm/ai-sdk-adapter.ts +81 -0
- package/{dist/shared/llm/client.cjs → src/shared/llm/client.ts} +86 -80
- package/src/shared/llm/index.ts +3 -0
- package/src/shared/llm/types.ts +63 -0
- package/src/shared/logger/index.ts +13 -0
- package/src/shared/logger/logger.ts +358 -0
- package/src/shared/logger/sinks.ts +148 -0
- package/src/shared/paths/paths.ts +110 -0
- package/src/shared/paths/repo-root.ts +27 -0
- package/src/shared/run/api.ts +6 -0
- package/src/shared/run/browser.ts +107 -0
- package/src/shared/state/index.ts +11 -0
- package/src/shared/state/session-state.ts +77 -0
- package/src/shared/visualization/ghost-cursor.ts +213 -0
- package/src/shared/visualization/highlight.ts +149 -0
- package/src/shared/visualization/index.ts +18 -0
- package/src/shared/workflow/workflow.ts +36 -0
- package/dist/index.cjs +0 -144
- package/dist/index.d.cts +0 -21
- package/dist/runtime/download/download.cjs +0 -70
- package/dist/runtime/download/download.d.cts +0 -35
- package/dist/runtime/download/index.cjs +0 -30
- package/dist/runtime/download/index.d.cts +0 -3
- package/dist/runtime/extract/extract.cjs +0 -88
- package/dist/runtime/extract/extract.d.cts +0 -23
- package/dist/runtime/extract/index.cjs +0 -28
- package/dist/runtime/extract/index.d.cts +0 -5
- package/dist/runtime/network/index.cjs +0 -28
- package/dist/runtime/network/index.d.cts +0 -4
- package/dist/runtime/network/network.cjs +0 -91
- package/dist/runtime/network/network.d.cts +0 -28
- package/dist/runtime/recovery/agent.d.cts +0 -13
- package/dist/runtime/recovery/errors.cjs +0 -124
- package/dist/runtime/recovery/errors.d.cts +0 -31
- package/dist/runtime/recovery/index.cjs +0 -34
- package/dist/runtime/recovery/index.d.cts +0 -7
- package/dist/runtime/recovery/recovery.cjs +0 -55
- package/dist/runtime/recovery/recovery.d.cts +0 -12
- package/dist/shared/condense-dom/condense-dom.d.cts +0 -34
- package/dist/shared/config/config.cjs +0 -44
- package/dist/shared/config/config.d.cts +0 -10
- package/dist/shared/config/index.cjs +0 -32
- package/dist/shared/config/index.d.cts +0 -1
- package/dist/shared/debug/index.cjs +0 -28
- package/dist/shared/debug/index.d.cts +0 -1
- package/dist/shared/debug/pause.cjs +0 -86
- package/dist/shared/debug/pause.d.cts +0 -12
- package/dist/shared/instrumentation/errors.cjs +0 -81
- package/dist/shared/instrumentation/errors.d.cts +0 -12
- package/dist/shared/instrumentation/index.cjs +0 -35
- package/dist/shared/instrumentation/index.d.cts +0 -6
- package/dist/shared/instrumentation/instrument.cjs +0 -206
- package/dist/shared/instrumentation/instrument.d.cts +0 -32
- package/dist/shared/llm/ai-sdk-adapter.cjs +0 -71
- package/dist/shared/llm/ai-sdk-adapter.d.cts +0 -22
- package/dist/shared/llm/client.d.cts +0 -13
- package/dist/shared/llm/index.cjs +0 -31
- package/dist/shared/llm/index.d.cts +0 -5
- package/dist/shared/llm/types.cjs +0 -16
- package/dist/shared/llm/types.d.cts +0 -67
- package/dist/shared/logger/index.cjs +0 -37
- package/dist/shared/logger/index.d.cts +0 -2
- package/dist/shared/logger/logger.cjs +0 -232
- package/dist/shared/logger/logger.d.cts +0 -86
- package/dist/shared/logger/sinks.cjs +0 -160
- package/dist/shared/logger/sinks.d.cts +0 -9
- package/dist/shared/paths/paths.cjs +0 -104
- package/dist/shared/paths/paths.d.cts +0 -10
- package/dist/shared/run/api.cjs +0 -28
- package/dist/shared/run/api.d.cts +0 -2
- package/dist/shared/run/browser.cjs +0 -98
- package/dist/shared/run/browser.d.cts +0 -22
- package/dist/shared/state/index.cjs +0 -38
- package/dist/shared/state/index.d.cts +0 -2
- package/dist/shared/state/session-state.cjs +0 -92
- package/dist/shared/state/session-state.d.cts +0 -40
- package/dist/shared/visualization/ghost-cursor.cjs +0 -174
- package/dist/shared/visualization/ghost-cursor.d.cts +0 -37
- package/dist/shared/visualization/highlight.cjs +0 -134
- package/dist/shared/visualization/highlight.d.cts +0 -22
- package/dist/shared/visualization/index.cjs +0 -45
- package/dist/shared/visualization/index.d.cts +0 -3
- package/dist/shared/workflow/workflow.cjs +0 -47
- package/dist/shared/workflow/workflow.d.cts +0 -21
- package/skills/libretto/integration-approach-selection.md +0 -174
package/README.md
CHANGED
|
@@ -1,70 +1,132 @@
|
|
|
1
1
|
# Libretto
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/libretto)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://github.com/saffron-health/libretto/discussions)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Libretto is a toolkit for building robust web integrations. It gives your coding agent a live browser and a token-efficient CLI to:
|
|
8
|
+
|
|
9
|
+
- Inspect live pages with minimal context overhead
|
|
10
|
+
- Capture network traffic to reverse-engineer site APIs
|
|
11
|
+
- Record user actions and replay them as automation scripts
|
|
12
|
+
- Debug broken workflows interactively against the real site
|
|
13
|
+
|
|
14
|
+
We at [Saffron Health](https://saffron.health) built Libretto to help us maintain our browser integrations to common healthcare software. We're open-sourcing it so other teams have an easier time doing the same thing.
|
|
6
15
|
|
|
7
16
|
## Installation
|
|
8
17
|
|
|
9
18
|
```bash
|
|
10
19
|
npm install --save-dev libretto
|
|
11
|
-
```
|
|
12
20
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```bash
|
|
21
|
+
# Install skill, download Chromium if not already installed, configure snapshot analysis
|
|
16
22
|
npx libretto init
|
|
17
|
-
```
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
# Configure snapshot analysis model (see Configuration section below)
|
|
25
|
+
npx libretto ai configure <openai | anthropic | gemini | vertex>
|
|
26
|
+
```
|
|
20
27
|
|
|
21
|
-
##
|
|
28
|
+
## Use cases
|
|
22
29
|
|
|
23
|
-
Libretto is
|
|
30
|
+
Libretto is designed to be used as a skill through your coding agent. Here are some example prompts:
|
|
24
31
|
|
|
25
32
|
### One-shot script generation
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
> Use the Libretto skill. Go on LinkedIn and scrape the first 10 posts for content, who posted it, the number of reactions, the first 25 comments, and the first 25 reposts.
|
|
35
|
+
|
|
36
|
+
Your coding agent will open a window for you to log into LinkedIn, and then automatically start exploring.
|
|
30
37
|
|
|
31
38
|
### Interactive script building
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
> I'm gonna show you a workflow in the eclinicalworks EHR to get a patient's primary insurance ID. Use libretto skill to turn it into a playwright script that takes patient name and dob as input to get back the insurance ID. URL is ...
|
|
41
|
+
|
|
42
|
+
Libretto can read your actions you perform in the browser, so you can perform a workflow, then ask it to use your actions to rebuild the workflow.
|
|
36
43
|
|
|
37
44
|
### Convert browser automation to network requests
|
|
38
45
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
46
|
+
> We have a browser script at ./integration.ts that automates going to Hacker News and getting the first 10 posts. Convert it to direct network scripts instead. Use the Libretto skill.
|
|
47
|
+
|
|
48
|
+
Libretto can read network requests from the browser, which it can use to reverse engineer the API and create a script that directly calls those requests. Directly making API calls is faster, and more reliable, than UI automation. You can also ask Libretto to conduct a security analysis which analyzes the requests for common security cookies, so you can understand whether a network request approach will be safe.
|
|
42
49
|
|
|
43
50
|
### Fix broken integrations
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
> We have a browser script at ./integration.ts that is supposed to go to Availity and perform an eligibility check for a patient. But I'm getting a broken selector error when I run it. Fix it. Use the Libretto skill.
|
|
53
|
+
|
|
54
|
+
Agents can use Libretto to reproduce the failure, pause the workflow at any point, inspect the live page, and fix issues, all autonomously.
|
|
55
|
+
|
|
56
|
+
### CLI usage
|
|
57
|
+
|
|
58
|
+
You can also use Libretto directly from the command line. All commands accept `--session <name>` to target a specific session.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npx libretto init # initialize libretto in the current project
|
|
62
|
+
npx libretto open <url> # launch browser and open a URL (headed by default)
|
|
63
|
+
npx libretto snapshot --objective "..." --context "..." # capture PNG + HTML and analyze with an LLM
|
|
64
|
+
npx libretto exec "<code>" # execute Playwright TypeScript against the open page
|
|
65
|
+
npx libretto run <file> <export> # run an exported workflow from a file
|
|
66
|
+
npx libretto resume # resume a paused workflow
|
|
67
|
+
npx libretto network # view captured network requests
|
|
68
|
+
npx libretto actions # view captured user/agent actions
|
|
69
|
+
npx libretto pages # list open pages in the session
|
|
70
|
+
npx libretto save <domain> # save browser session (cookies, localStorage) for reuse
|
|
71
|
+
npx libretto close # close the browser
|
|
72
|
+
npx libretto ai configure <provider> # configure snapshot analysis model
|
|
47
73
|
```
|
|
48
74
|
|
|
49
|
-
|
|
75
|
+
## Configuration
|
|
76
|
+
|
|
77
|
+
All Libretto state lives in a `.libretto/` directory at your project root. Configuration is stored in `.libretto/config.json`.
|
|
78
|
+
|
|
79
|
+
### Config file
|
|
80
|
+
|
|
81
|
+
`.libretto/config.json` controls snapshot analysis and viewport settings:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{
|
|
85
|
+
"version": 1,
|
|
86
|
+
"ai": {
|
|
87
|
+
"model": "openai/gpt-5.4",
|
|
88
|
+
"updatedAt": "2026-01-01T00:00:00.000Z"
|
|
89
|
+
},
|
|
90
|
+
"viewport": { "width": 1280, "height": 800 }
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The `ai` field configures which model Libretto uses for snapshot analysis — extracting selectors, identifying interactive elements, or diagnosing why a step failed. This keeps heavy visual context out of your coding agent's context window. Snapshot analysis is required.
|
|
95
|
+
|
|
96
|
+
The easiest way to set the model is through the CLI:
|
|
50
97
|
|
|
51
98
|
```bash
|
|
52
|
-
npx libretto
|
|
53
|
-
npx libretto run ./integration.ts main
|
|
99
|
+
npx libretto ai configure <openai | anthropic | gemini | vertex>
|
|
54
100
|
```
|
|
55
101
|
|
|
56
|
-
|
|
102
|
+
Provider credentials are read from environment variables or a `.env` file at your project root: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY` / `GOOGLE_GENERATIVE_AI_API_KEY`, or `GOOGLE_CLOUD_PROJECT` for Vertex.
|
|
103
|
+
|
|
104
|
+
The `viewport` field sets the default browser viewport size. Both fields are optional.
|
|
105
|
+
|
|
106
|
+
### Sessions
|
|
107
|
+
|
|
108
|
+
Each Libretto session gets its own directory under `.libretto/sessions/<name>/` containing runtime state. Sessions are git-ignored.
|
|
57
109
|
|
|
58
|
-
|
|
110
|
+
- `state.json` — session metadata (debug port, PID, status)
|
|
111
|
+
- `logs.jsonl` — structured session logs
|
|
112
|
+
- `network.jsonl` — captured network requests
|
|
113
|
+
- `actions.jsonl` — recorded user actions
|
|
114
|
+
- `snapshots/` — screenshot PNGs and HTML snapshots
|
|
59
115
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
116
|
+
### Profiles
|
|
117
|
+
|
|
118
|
+
Profiles save browser sessions (cookies, localStorage) so you can reuse authenticated state across runs. They are stored in `.libretto/profiles/<domain>.json`, created via `npx libretto save <domain>`. Profiles are machine-local and git-ignored.
|
|
119
|
+
|
|
120
|
+
## Community
|
|
121
|
+
|
|
122
|
+
Have a question, idea, or want to share what you've built? Join the conversation on [GitHub Discussions](https://github.com/saffron-health/libretto/discussions).
|
|
123
|
+
|
|
124
|
+
- **[Q&A](https://github.com/saffron-health/libretto/discussions/categories/q-a)** — Ask questions and get help
|
|
125
|
+
- **[Ideas](https://github.com/saffron-health/libretto/discussions/categories/ideas)** — Suggest new features or improvements
|
|
126
|
+
- **[Show and tell](https://github.com/saffron-health/libretto/discussions/categories/show-and-tell)** — Share your workflows and automations
|
|
127
|
+
- **[General](https://github.com/saffron-health/libretto/discussions/categories/general)** — Chat about anything Libretto-related
|
|
128
|
+
|
|
129
|
+
Found a bug? Please [open an issue](https://github.com/saffron-health/libretto/issues/new).
|
|
68
130
|
|
|
69
131
|
## Authors
|
|
70
132
|
|
|
@@ -72,11 +134,19 @@ Maintained by the team at [Saffron Health](https://saffron.health).
|
|
|
72
134
|
|
|
73
135
|
## Development
|
|
74
136
|
|
|
75
|
-
For local development in this repository:
|
|
76
|
-
|
|
77
137
|
```bash
|
|
78
138
|
pnpm i
|
|
79
139
|
pnpm build
|
|
80
140
|
pnpm type-check
|
|
81
141
|
pnpm test
|
|
82
142
|
```
|
|
143
|
+
|
|
144
|
+
Source layout:
|
|
145
|
+
|
|
146
|
+
- `src/cli/` — CLI commands
|
|
147
|
+
- `src/runtime/` — browser runtime (network, recovery, downloads, extraction)
|
|
148
|
+
- `src/shared/` — shared utilities (config, LLM client, logging, state)
|
|
149
|
+
- `test/` — test files (`*.spec.ts`)
|
|
150
|
+
- `skills/libretto/` — source of truth for the Libretto skill; mirrors are synced on `pnpm i`
|
|
151
|
+
|
|
152
|
+
To check that skill mirrors are in sync without fixing them, run `pnpm check:skills`. To release, run `pnpm prepare-release`.
|
package/dist/cli/cli.js
CHANGED
|
@@ -1,45 +1,36 @@
|
|
|
1
|
-
import {
|
|
2
|
-
closeLogger,
|
|
3
|
-
createLoggerForSession,
|
|
4
|
-
ensureLibrettoSetup
|
|
5
|
-
} from "./core/context.js";
|
|
6
|
-
import {
|
|
7
|
-
SESSION_DEFAULT,
|
|
8
|
-
validateSessionName
|
|
9
|
-
} from "./core/session.js";
|
|
1
|
+
import { ensureLibrettoSetup } from "./core/context.js";
|
|
10
2
|
import { createCLIApp } from "./router.js";
|
|
11
3
|
function renderUsage(app) {
|
|
12
4
|
return `${app.renderHelp()}
|
|
13
5
|
|
|
14
6
|
Options:
|
|
15
|
-
--session <name> Use a named session (
|
|
16
|
-
Built-in sessions: default, dev-server, browser-agent
|
|
7
|
+
--session <name> Use a named session (auto-generated for open/run if omitted)
|
|
17
8
|
|
|
18
9
|
Examples:
|
|
19
|
-
libretto
|
|
10
|
+
libretto open https://linkedin.com
|
|
20
11
|
|
|
21
12
|
# ... manually log in ...
|
|
22
|
-
libretto
|
|
13
|
+
libretto save linkedin.com
|
|
23
14
|
# Next time you open linkedin.com, you'll be logged in automatically
|
|
24
15
|
|
|
25
|
-
libretto
|
|
26
|
-
libretto
|
|
27
|
-
libretto
|
|
28
|
-
libretto
|
|
29
|
-
libretto
|
|
30
|
-
libretto
|
|
31
|
-
libretto
|
|
32
|
-
libretto
|
|
33
|
-
libretto
|
|
34
|
-
libretto
|
|
35
|
-
libretto
|
|
36
|
-
libretto
|
|
37
|
-
libretto
|
|
16
|
+
libretto exec "await page.locator('button:has-text(\\"Sign in\\")').click()"
|
|
17
|
+
libretto exec "await page.fill('input[name=\\"email\\"]', 'test@example.com')"
|
|
18
|
+
libretto ai configure openai
|
|
19
|
+
libretto ai configure anthropic
|
|
20
|
+
libretto ai configure gemini
|
|
21
|
+
libretto ai configure vertex
|
|
22
|
+
libretto ai configure openai/gpt-4o
|
|
23
|
+
libretto snapshot
|
|
24
|
+
libretto snapshot --objective "Find the submit button" --context "Submitting a referral form, already filled in patient details"
|
|
25
|
+
libretto resume --session my-session
|
|
26
|
+
libretto close
|
|
27
|
+
libretto close --all
|
|
28
|
+
libretto close --all --force
|
|
38
29
|
|
|
39
30
|
# Multiple sessions
|
|
40
|
-
libretto
|
|
41
|
-
libretto
|
|
42
|
-
libretto
|
|
31
|
+
libretto open https://site1.com --session test1
|
|
32
|
+
libretto open https://site2.com --session test2
|
|
33
|
+
libretto exec "return await page.title()" --session test1
|
|
43
34
|
|
|
44
35
|
Available in exec:
|
|
45
36
|
page, context, state, browser, networkLog, actionLog
|
|
@@ -56,66 +47,6 @@ Sessions:
|
|
|
56
47
|
Each session runs an isolated browser instance on a dynamic port.
|
|
57
48
|
`;
|
|
58
49
|
}
|
|
59
|
-
function readSessionArgBeforePassthrough(rawArgs) {
|
|
60
|
-
for (let index = 0; index < rawArgs.length; index += 1) {
|
|
61
|
-
const token = rawArgs[index];
|
|
62
|
-
if (token === "--") return void 0;
|
|
63
|
-
if (token === "--session") {
|
|
64
|
-
const value2 = rawArgs[index + 1];
|
|
65
|
-
if (!value2 || value2 === "--" || value2.startsWith("--")) {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
return value2;
|
|
69
|
-
}
|
|
70
|
-
if (!token.startsWith("--session=")) continue;
|
|
71
|
-
const value = token.slice("--session=".length);
|
|
72
|
-
if (value.length === 0 || value === "--" || value.startsWith("--")) {
|
|
73
|
-
return null;
|
|
74
|
-
}
|
|
75
|
-
return value;
|
|
76
|
-
}
|
|
77
|
-
return void 0;
|
|
78
|
-
}
|
|
79
|
-
function parseSessionForLog(rawArgs) {
|
|
80
|
-
const value = readSessionArgBeforePassthrough(rawArgs);
|
|
81
|
-
if (value === void 0 || value === null) {
|
|
82
|
-
return SESSION_DEFAULT;
|
|
83
|
-
}
|
|
84
|
-
try {
|
|
85
|
-
validateSessionName(value);
|
|
86
|
-
return value;
|
|
87
|
-
} catch {
|
|
88
|
-
return SESSION_DEFAULT;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
function validateLegacySessionArg(rawArgs) {
|
|
92
|
-
const value = readSessionArgBeforePassthrough(rawArgs);
|
|
93
|
-
if (value === void 0) return;
|
|
94
|
-
if (value === null) {
|
|
95
|
-
throw new Error(
|
|
96
|
-
"Usage: libretto-cli <command> [--session <name>]\nMissing or invalid --session value."
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
validateSessionName(value);
|
|
100
|
-
}
|
|
101
|
-
function initializeLogger(rawArgs) {
|
|
102
|
-
const sessionForLog = parseSessionForLog(rawArgs);
|
|
103
|
-
const logger = createLoggerForSession(sessionForLog);
|
|
104
|
-
logger.info("cli-start", {
|
|
105
|
-
args: rawArgs,
|
|
106
|
-
cwd: process.cwd(),
|
|
107
|
-
session: sessionForLog
|
|
108
|
-
});
|
|
109
|
-
return logger;
|
|
110
|
-
}
|
|
111
|
-
async function withCliLogger(rawArgs, run) {
|
|
112
|
-
const logger = initializeLogger(rawArgs);
|
|
113
|
-
try {
|
|
114
|
-
return await run(logger);
|
|
115
|
-
} finally {
|
|
116
|
-
await closeLogger(logger);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
50
|
function isRootHelpRequest(rawArgs) {
|
|
120
51
|
if (rawArgs.length === 0) return true;
|
|
121
52
|
if (rawArgs[0] === "--help" || rawArgs[0] === "-h") return true;
|
|
@@ -125,32 +56,27 @@ async function runLibrettoCLI() {
|
|
|
125
56
|
const rawArgs = process.argv.slice(2);
|
|
126
57
|
let exitCode = 0;
|
|
127
58
|
ensureLibrettoSetup();
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
logger.error("cli-error", { error: err, args: rawArgs });
|
|
143
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
144
|
-
if (message.startsWith("Unknown command: ")) {
|
|
145
|
-
console.error(`${message}
|
|
59
|
+
const app = createCLIApp();
|
|
60
|
+
try {
|
|
61
|
+
if (isRootHelpRequest(rawArgs)) {
|
|
62
|
+
console.log(renderUsage(app));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const result = await app.run(rawArgs);
|
|
66
|
+
if (typeof result === "string") {
|
|
67
|
+
console.log(result);
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
71
|
+
if (message.startsWith("Unknown command: ")) {
|
|
72
|
+
console.error(`${message}
|
|
146
73
|
`);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
151
|
-
exitCode = 1;
|
|
74
|
+
console.log(renderUsage(app));
|
|
75
|
+
} else {
|
|
76
|
+
console.error(message);
|
|
152
77
|
}
|
|
153
|
-
|
|
78
|
+
exitCode = 1;
|
|
79
|
+
}
|
|
154
80
|
process.exit(exitCode);
|
|
155
81
|
}
|
|
156
82
|
export {
|
package/dist/cli/commands/ai.js
CHANGED
|
@@ -2,17 +2,21 @@ import { z } from "zod";
|
|
|
2
2
|
import {
|
|
3
3
|
runClose as runCloseWithLogger,
|
|
4
4
|
runCloseAll as runCloseAllWithLogger,
|
|
5
|
+
runConnect as runConnectWithLogger,
|
|
5
6
|
runOpen,
|
|
6
7
|
runPages,
|
|
7
8
|
runSave
|
|
8
9
|
} from "../core/browser.js";
|
|
9
|
-
import { withSessionLogger } from "../core/context.js";
|
|
10
|
-
import {
|
|
10
|
+
import { createLoggerForSession, withSessionLogger } from "../core/context.js";
|
|
11
|
+
import {
|
|
12
|
+
assertSessionAvailableForStart,
|
|
13
|
+
validateSessionName
|
|
14
|
+
} from "../core/session.js";
|
|
11
15
|
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
12
16
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
17
|
+
sessionOption,
|
|
18
|
+
withAutoSession,
|
|
19
|
+
withRequiredSession
|
|
16
20
|
} from "./shared.js";
|
|
17
21
|
function parseViewportArg(viewportArg) {
|
|
18
22
|
if (!viewportArg) return void 0;
|
|
@@ -47,21 +51,37 @@ const openInput = SimpleCLI.input({
|
|
|
47
51
|
}
|
|
48
52
|
}).refine(
|
|
49
53
|
(input) => Boolean(input.url),
|
|
50
|
-
|
|
54
|
+
`Usage: libretto open <url> [--headless] [--viewport WxH] [--session <name>]`
|
|
51
55
|
).refine(
|
|
52
56
|
(input) => !(input.headed && input.headless),
|
|
53
57
|
"Cannot pass both --headed and --headless."
|
|
54
58
|
);
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
const openCommand = SimpleCLI.command({
|
|
60
|
+
description: "Launch browser and open URL (headed by default)"
|
|
61
|
+
}).input(openInput).use(withAutoSession()).handle(async ({ input, ctx }) => {
|
|
62
|
+
assertSessionAvailableForStart(ctx.session, ctx.logger);
|
|
63
|
+
const headed = input.headed || !input.headless;
|
|
64
|
+
const viewport = parseViewportArg(input.viewport);
|
|
65
|
+
await runOpen(input.url, headed, ctx.session, ctx.logger, { viewport });
|
|
66
|
+
});
|
|
67
|
+
const connectInput = SimpleCLI.input({
|
|
68
|
+
positionals: [
|
|
69
|
+
SimpleCLI.positional("cdpUrl", z.string().optional(), {
|
|
70
|
+
help: "CDP endpoint URL (e.g. http://127.0.0.1:9222)"
|
|
71
|
+
})
|
|
72
|
+
],
|
|
73
|
+
named: {
|
|
74
|
+
session: sessionOption()
|
|
75
|
+
}
|
|
76
|
+
}).refine(
|
|
77
|
+
(input) => Boolean(input.cdpUrl),
|
|
78
|
+
`Usage: libretto connect <cdp-url> --session <name>`
|
|
79
|
+
);
|
|
80
|
+
const connectCommand = SimpleCLI.command({
|
|
81
|
+
description: "Connect to an existing Chrome DevTools Protocol (CDP) endpoint"
|
|
82
|
+
}).input(connectInput).use(withAutoSession()).handle(async ({ input, ctx }) => {
|
|
83
|
+
await runConnectWithLogger(input.cdpUrl, ctx.session, ctx.logger);
|
|
84
|
+
});
|
|
65
85
|
const saveInput = SimpleCLI.input({
|
|
66
86
|
positionals: [
|
|
67
87
|
SimpleCLI.positional("urlOrDomain", z.string().optional(), {
|
|
@@ -73,72 +93,79 @@ const saveInput = SimpleCLI.input({
|
|
|
73
93
|
}
|
|
74
94
|
}).refine(
|
|
75
95
|
(input) => Boolean(input.urlOrDomain),
|
|
76
|
-
|
|
96
|
+
`Usage: libretto save <url|domain> --session <name>`
|
|
77
97
|
);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
});
|
|
84
|
-
}
|
|
98
|
+
const saveCommand = SimpleCLI.command({
|
|
99
|
+
description: "Save current browser session"
|
|
100
|
+
}).input(saveInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
|
|
101
|
+
await runSave(input.urlOrDomain, ctx.session, ctx.logger);
|
|
102
|
+
});
|
|
85
103
|
const pagesInput = SimpleCLI.input({
|
|
86
104
|
positionals: [],
|
|
87
105
|
named: {
|
|
88
106
|
session: sessionOption()
|
|
89
107
|
}
|
|
90
108
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
});
|
|
97
|
-
}
|
|
109
|
+
const pagesCommand = SimpleCLI.command({
|
|
110
|
+
description: "List open pages in the session"
|
|
111
|
+
}).input(pagesInput).use(withRequiredSession()).handle(async ({ ctx }) => {
|
|
112
|
+
await runPages(ctx.session, ctx.logger);
|
|
113
|
+
});
|
|
98
114
|
const closeInput = SimpleCLI.input({
|
|
99
115
|
positionals: [],
|
|
100
116
|
named: {
|
|
101
117
|
session: sessionOption(),
|
|
102
|
-
all: SimpleCLI.flag({
|
|
103
|
-
|
|
118
|
+
all: SimpleCLI.flag({
|
|
119
|
+
help: "Close all tracked sessions in this workspace"
|
|
120
|
+
}),
|
|
121
|
+
force: SimpleCLI.flag({
|
|
122
|
+
help: "Force kill sessions that ignore SIGTERM (requires --all)"
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
}).refine(
|
|
126
|
+
(input) => input.all || input.session,
|
|
127
|
+
`Usage: libretto close --session <name>
|
|
128
|
+
Usage: libretto close --all [--force]`
|
|
129
|
+
);
|
|
130
|
+
const closeCommand = SimpleCLI.command({
|
|
131
|
+
description: "Close the browser"
|
|
132
|
+
}).input(closeInput).handle(async ({ input }) => {
|
|
133
|
+
if (input.force && !input.all) {
|
|
134
|
+
throw new Error(`Usage: libretto close --all [--force]`);
|
|
104
135
|
}
|
|
136
|
+
if (input.all) {
|
|
137
|
+
const logger2 = createLoggerForSession("cli");
|
|
138
|
+
await runCloseAllWithLogger(logger2, { force: input.force });
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
validateSessionName(input.session);
|
|
142
|
+
const logger = createLoggerForSession(input.session);
|
|
143
|
+
await runCloseWithLogger(input.session, logger);
|
|
105
144
|
});
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (input.all) {
|
|
114
|
-
await runCloseAllWithLogger(logger, { force: input.force });
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
await runCloseWithLogger(ctx.session, logger);
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
function createBrowserCommands(logger) {
|
|
121
|
-
return {
|
|
122
|
-
open: createOpenCommand(logger),
|
|
123
|
-
save: createSaveCommand(logger),
|
|
124
|
-
pages: createPagesCommand(logger),
|
|
125
|
-
close: createCloseCommand(logger)
|
|
126
|
-
};
|
|
127
|
-
}
|
|
145
|
+
const browserCommands = {
|
|
146
|
+
open: openCommand,
|
|
147
|
+
connect: connectCommand,
|
|
148
|
+
save: saveCommand,
|
|
149
|
+
pages: pagesCommand,
|
|
150
|
+
close: closeCommand
|
|
151
|
+
};
|
|
128
152
|
async function runClose(session) {
|
|
129
153
|
await withSessionLogger(session, async (logger) => {
|
|
130
154
|
await runCloseWithLogger(session, logger);
|
|
131
155
|
});
|
|
132
156
|
}
|
|
133
157
|
export {
|
|
158
|
+
browserCommands,
|
|
159
|
+
closeCommand,
|
|
134
160
|
closeInput,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
createPagesCommand,
|
|
139
|
-
createSaveCommand,
|
|
161
|
+
connectCommand,
|
|
162
|
+
connectInput,
|
|
163
|
+
openCommand,
|
|
140
164
|
openInput,
|
|
165
|
+
pagesCommand,
|
|
141
166
|
pagesInput,
|
|
167
|
+
parseViewportArg,
|
|
142
168
|
runClose,
|
|
169
|
+
saveCommand,
|
|
143
170
|
saveInput
|
|
144
171
|
};
|