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.
Files changed (194) hide show
  1. package/README.md +106 -36
  2. package/dist/cli/cli.js +39 -113
  3. package/dist/cli/commands/ai.js +1 -1
  4. package/dist/cli/commands/browser.js +87 -60
  5. package/dist/cli/commands/execution.js +201 -88
  6. package/dist/cli/commands/init.js +30 -8
  7. package/dist/cli/commands/logs.js +5 -6
  8. package/dist/cli/commands/shared.js +30 -29
  9. package/dist/cli/commands/snapshot.js +26 -39
  10. package/dist/cli/core/ai-config.js +9 -2
  11. package/dist/cli/core/api-snapshot-analyzer.js +15 -5
  12. package/dist/cli/core/browser.js +141 -33
  13. package/dist/cli/core/context.js +7 -18
  14. package/dist/cli/core/session-telemetry.js +5 -2
  15. package/dist/cli/core/session.js +23 -10
  16. package/dist/cli/core/snapshot-analyzer.js +16 -33
  17. package/dist/cli/core/snapshot-api-config.js +2 -6
  18. package/dist/cli/core/telemetry.js +10 -2
  19. package/dist/cli/framework/simple-cli.js +45 -25
  20. package/dist/cli/router.js +14 -21
  21. package/dist/cli/workers/run-integration-runtime.js +26 -7
  22. package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
  23. package/dist/cli/workers/run-integration-worker.js +1 -4
  24. package/dist/index.d.ts +1 -2
  25. package/dist/index.js +7 -10
  26. package/dist/runtime/download/download.js +5 -1
  27. package/dist/runtime/extract/extract.js +11 -2
  28. package/dist/runtime/network/network.js +8 -1
  29. package/dist/runtime/recovery/agent.js +6 -2
  30. package/dist/runtime/recovery/errors.js +3 -1
  31. package/dist/runtime/recovery/recovery.js +3 -1
  32. package/dist/shared/condense-dom/condense-dom.js +6 -13
  33. package/dist/shared/config/config.d.ts +1 -9
  34. package/dist/shared/config/config.js +0 -18
  35. package/dist/shared/config/index.d.ts +2 -1
  36. package/dist/shared/config/index.js +0 -10
  37. package/dist/shared/debug/pause.js +9 -3
  38. package/dist/shared/instrumentation/instrument.js +101 -5
  39. package/dist/shared/llm/ai-sdk-adapter.js +3 -1
  40. package/dist/shared/llm/client.js +3 -1
  41. package/dist/shared/logger/index.js +4 -1
  42. package/dist/shared/paths/paths.js +2 -1
  43. package/dist/shared/paths/repo-root.d.ts +3 -0
  44. package/dist/shared/paths/repo-root.js +24 -0
  45. package/dist/shared/run/api.js +3 -1
  46. package/dist/shared/run/browser.js +7 -2
  47. package/dist/shared/state/session-state.d.ts +2 -1
  48. package/dist/shared/state/session-state.js +5 -2
  49. package/dist/shared/visualization/ghost-cursor.js +19 -10
  50. package/dist/shared/visualization/highlight.js +9 -6
  51. package/dist/shared/workflow/workflow.d.ts +4 -5
  52. package/dist/shared/workflow/workflow.js +3 -5
  53. package/package.json +11 -8
  54. package/scripts/check-skills-sync.mjs +25 -0
  55. package/scripts/compare-eval-summary.mjs +47 -0
  56. package/scripts/postinstall.mjs +26 -17
  57. package/scripts/prepare-release.sh +97 -0
  58. package/scripts/skills-libretto.mjs +103 -0
  59. package/scripts/summarize-evals.mjs +135 -0
  60. package/scripts/sync-skills.mjs +12 -0
  61. package/skills/libretto/SKILL.md +130 -377
  62. package/skills/libretto/references/auth-profiles.md +30 -0
  63. package/skills/libretto/{code-generation-rules.md → references/code-generation-rules.md} +27 -42
  64. package/skills/libretto/references/configuration-file-reference.md +53 -0
  65. package/skills/libretto/references/pages-and-page-targeting.md +29 -0
  66. package/skills/libretto/references/site-security-review.md +143 -0
  67. package/src/cli/cli.ts +86 -0
  68. package/src/cli/commands/ai.ts +35 -0
  69. package/src/cli/commands/browser.ts +189 -0
  70. package/src/cli/commands/execution.ts +822 -0
  71. package/src/cli/commands/init.ts +350 -0
  72. package/src/cli/commands/logs.ts +128 -0
  73. package/src/cli/commands/shared.ts +69 -0
  74. package/src/cli/commands/snapshot.ts +312 -0
  75. package/src/cli/core/ai-config.ts +264 -0
  76. package/src/cli/core/api-snapshot-analyzer.ts +108 -0
  77. package/src/cli/core/browser.ts +976 -0
  78. package/src/cli/core/context.ts +127 -0
  79. package/src/cli/core/pause-signals.ts +35 -0
  80. package/src/cli/core/session-telemetry.ts +564 -0
  81. package/src/cli/core/session.ts +223 -0
  82. package/src/cli/core/snapshot-analyzer.ts +855 -0
  83. package/src/cli/core/snapshot-api-config.ts +231 -0
  84. package/src/cli/core/telemetry.ts +459 -0
  85. package/src/cli/framework/simple-cli.ts +1340 -0
  86. package/src/cli/index.ts +13 -0
  87. package/src/cli/router.ts +20 -0
  88. package/src/cli/workers/run-integration-runtime.ts +338 -0
  89. package/src/cli/workers/run-integration-worker-protocol.ts +16 -0
  90. package/src/cli/workers/run-integration-worker.ts +72 -0
  91. package/src/index.ts +127 -0
  92. package/src/runtime/download/download.ts +104 -0
  93. package/src/runtime/download/index.ts +7 -0
  94. package/src/runtime/extract/extract.ts +102 -0
  95. package/src/runtime/extract/index.ts +1 -0
  96. package/src/runtime/network/index.ts +5 -0
  97. package/src/runtime/network/network.ts +119 -0
  98. package/{dist/runtime/recovery/agent.cjs → src/runtime/recovery/agent.ts} +114 -76
  99. package/src/runtime/recovery/errors.ts +155 -0
  100. package/src/runtime/recovery/index.ts +7 -0
  101. package/src/runtime/recovery/recovery.ts +53 -0
  102. package/{dist/shared/condense-dom/condense-dom.cjs → src/shared/condense-dom/condense-dom.ts} +249 -124
  103. package/src/shared/config/config.ts +3 -0
  104. package/src/shared/config/index.ts +0 -0
  105. package/src/shared/debug/index.ts +1 -0
  106. package/src/shared/debug/pause.ts +91 -0
  107. package/src/shared/instrumentation/errors.ts +84 -0
  108. package/src/shared/instrumentation/index.ts +9 -0
  109. package/src/shared/instrumentation/instrument.ts +406 -0
  110. package/src/shared/llm/ai-sdk-adapter.ts +81 -0
  111. package/{dist/shared/llm/client.cjs → src/shared/llm/client.ts} +86 -80
  112. package/src/shared/llm/index.ts +3 -0
  113. package/src/shared/llm/types.ts +63 -0
  114. package/src/shared/logger/index.ts +13 -0
  115. package/src/shared/logger/logger.ts +358 -0
  116. package/src/shared/logger/sinks.ts +148 -0
  117. package/src/shared/paths/paths.ts +110 -0
  118. package/src/shared/paths/repo-root.ts +27 -0
  119. package/src/shared/run/api.ts +6 -0
  120. package/src/shared/run/browser.ts +107 -0
  121. package/src/shared/state/index.ts +11 -0
  122. package/src/shared/state/session-state.ts +77 -0
  123. package/src/shared/visualization/ghost-cursor.ts +213 -0
  124. package/src/shared/visualization/highlight.ts +149 -0
  125. package/src/shared/visualization/index.ts +18 -0
  126. package/src/shared/workflow/workflow.ts +36 -0
  127. package/dist/index.cjs +0 -144
  128. package/dist/index.d.cts +0 -21
  129. package/dist/runtime/download/download.cjs +0 -70
  130. package/dist/runtime/download/download.d.cts +0 -35
  131. package/dist/runtime/download/index.cjs +0 -30
  132. package/dist/runtime/download/index.d.cts +0 -3
  133. package/dist/runtime/extract/extract.cjs +0 -88
  134. package/dist/runtime/extract/extract.d.cts +0 -23
  135. package/dist/runtime/extract/index.cjs +0 -28
  136. package/dist/runtime/extract/index.d.cts +0 -5
  137. package/dist/runtime/network/index.cjs +0 -28
  138. package/dist/runtime/network/index.d.cts +0 -4
  139. package/dist/runtime/network/network.cjs +0 -91
  140. package/dist/runtime/network/network.d.cts +0 -28
  141. package/dist/runtime/recovery/agent.d.cts +0 -13
  142. package/dist/runtime/recovery/errors.cjs +0 -124
  143. package/dist/runtime/recovery/errors.d.cts +0 -31
  144. package/dist/runtime/recovery/index.cjs +0 -34
  145. package/dist/runtime/recovery/index.d.cts +0 -7
  146. package/dist/runtime/recovery/recovery.cjs +0 -55
  147. package/dist/runtime/recovery/recovery.d.cts +0 -12
  148. package/dist/shared/condense-dom/condense-dom.d.cts +0 -34
  149. package/dist/shared/config/config.cjs +0 -44
  150. package/dist/shared/config/config.d.cts +0 -10
  151. package/dist/shared/config/index.cjs +0 -32
  152. package/dist/shared/config/index.d.cts +0 -1
  153. package/dist/shared/debug/index.cjs +0 -28
  154. package/dist/shared/debug/index.d.cts +0 -1
  155. package/dist/shared/debug/pause.cjs +0 -86
  156. package/dist/shared/debug/pause.d.cts +0 -12
  157. package/dist/shared/instrumentation/errors.cjs +0 -81
  158. package/dist/shared/instrumentation/errors.d.cts +0 -12
  159. package/dist/shared/instrumentation/index.cjs +0 -35
  160. package/dist/shared/instrumentation/index.d.cts +0 -6
  161. package/dist/shared/instrumentation/instrument.cjs +0 -206
  162. package/dist/shared/instrumentation/instrument.d.cts +0 -32
  163. package/dist/shared/llm/ai-sdk-adapter.cjs +0 -71
  164. package/dist/shared/llm/ai-sdk-adapter.d.cts +0 -22
  165. package/dist/shared/llm/client.d.cts +0 -13
  166. package/dist/shared/llm/index.cjs +0 -31
  167. package/dist/shared/llm/index.d.cts +0 -5
  168. package/dist/shared/llm/types.cjs +0 -16
  169. package/dist/shared/llm/types.d.cts +0 -67
  170. package/dist/shared/logger/index.cjs +0 -37
  171. package/dist/shared/logger/index.d.cts +0 -2
  172. package/dist/shared/logger/logger.cjs +0 -232
  173. package/dist/shared/logger/logger.d.cts +0 -86
  174. package/dist/shared/logger/sinks.cjs +0 -160
  175. package/dist/shared/logger/sinks.d.cts +0 -9
  176. package/dist/shared/paths/paths.cjs +0 -104
  177. package/dist/shared/paths/paths.d.cts +0 -10
  178. package/dist/shared/run/api.cjs +0 -28
  179. package/dist/shared/run/api.d.cts +0 -2
  180. package/dist/shared/run/browser.cjs +0 -98
  181. package/dist/shared/run/browser.d.cts +0 -22
  182. package/dist/shared/state/index.cjs +0 -38
  183. package/dist/shared/state/index.d.cts +0 -2
  184. package/dist/shared/state/session-state.cjs +0 -92
  185. package/dist/shared/state/session-state.d.cts +0 -40
  186. package/dist/shared/visualization/ghost-cursor.cjs +0 -174
  187. package/dist/shared/visualization/ghost-cursor.d.cts +0 -37
  188. package/dist/shared/visualization/highlight.cjs +0 -134
  189. package/dist/shared/visualization/highlight.d.cts +0 -22
  190. package/dist/shared/visualization/index.cjs +0 -45
  191. package/dist/shared/visualization/index.d.cts +0 -3
  192. package/dist/shared/workflow/workflow.cjs +0 -47
  193. package/dist/shared/workflow/workflow.d.cts +0 -21
  194. package/skills/libretto/integration-approach-selection.md +0 -174
package/README.md CHANGED
@@ -1,70 +1,132 @@
1
1
  # Libretto
2
2
 
3
- Libretto gives your coding agent superpowers for building, debugging, and maintaining browser RPA integrations.
3
+ [![npm version](https://img.shields.io/npm/v/libretto)](https://www.npmjs.com/package/libretto)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![GitHub Discussions](https://img.shields.io/github/discussions/saffron-health/libretto)](https://github.com/saffron-health/libretto/discussions)
4
6
 
5
- It is designed for engineering teams that automate workflows in web apps and want to move from brittle browser-only scripts to faster, more reliable network-first integrations.
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
- Chromium is downloaded automatically via a `postinstall` script. If postinstall scripts are disabled (e.g. `--ignore-scripts`, common in monorepos), run init manually:
14
-
15
- ```bash
21
+ # Install skill, download Chromium if not already installed, configure snapshot analysis
16
22
  npx libretto init
17
- ```
18
23
 
19
- This installs the Chromium browser binary and optionally configures an AI subagent (Gemini, Claude, or Codex) that can analyze page snapshots without consuming the coding agent's context window.
24
+ # Configure snapshot analysis model (see Configuration section below)
25
+ npx libretto ai configure <openai | anthropic | gemini | vertex>
26
+ ```
20
27
 
21
- ## Usage
28
+ ## Use cases
22
29
 
23
- Libretto is usually used through prompts with the Libretto skill.
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
- ```text
28
- 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.
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
- ```text
34
- Use the Libretto skill. Let's interactively build a script to scrape scheduling info from the eClinicalWorks EHR.
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
- ```text
40
- 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.
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
- ```text
46
- 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.
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
- You can also run workflows directly from the CLI:
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 help
53
- npx libretto run ./integration.ts main
99
+ npx libretto ai configure <openai | anthropic | gemini | vertex>
54
100
  ```
55
101
 
56
- ## The `.libretto/` directory
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
- Libretto stores local runtime state in a `.libretto/` directory at your project root. Sensitive directories (`sessions/` and `profiles/`) are automatically git-ignored via `.libretto/.gitignore`.
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
- - **`profiles/<domain>.json`** — Saved browser sessions (cookies, localStorage) for authenticated sites. Created via `npx libretto save <domain>`. Machine-local and never committed.
61
- - **`sessions/<name>/`** — Per-session runtime state:
62
- - `state.json` Session metadata (debug port, PID, status)
63
- - `logs.jsonl` — Structured session logs
64
- - `network.jsonl` — Captured network requests (URLs, methods, headers, response status)
65
- - `actions.jsonl` — Recorded user actions (clicks, fills, navigations)
66
- - `snapshots/` Screenshot PNGs and HTML snapshots captured via `npx libretto snapshot`
67
- - **`ai.json`** — AI runtime configuration set via `npx libretto ai configure`.
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 (default: "default")
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-cli open https://linkedin.com
10
+ libretto open https://linkedin.com
20
11
 
21
12
  # ... manually log in ...
22
- libretto-cli save linkedin.com
13
+ libretto save linkedin.com
23
14
  # Next time you open linkedin.com, you'll be logged in automatically
24
15
 
25
- libretto-cli exec "await page.locator('button:has-text(\\"Sign in\\")').click()"
26
- libretto-cli exec "await page.fill('input[name=\\"email\\"]', 'test@example.com')"
27
- libretto-cli ai configure openai
28
- libretto-cli ai configure anthropic
29
- libretto-cli ai configure gemini
30
- libretto-cli ai configure vertex
31
- libretto-cli ai configure openai/gpt-4o
32
- libretto-cli snapshot
33
- libretto-cli snapshot --objective "Find the submit button" --context "Submitting a referral form, already filled in patient details"
34
- libretto-cli resume --session default
35
- libretto-cli close
36
- libretto-cli close --all
37
- libretto-cli close --all --force
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-cli open https://site1.com --session test1
41
- libretto-cli open https://site2.com --session test2
42
- libretto-cli exec "return await page.title()" --session test1
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
- await withCliLogger(rawArgs, async (logger) => {
129
- const app = createCLIApp(logger);
130
- try {
131
- validateLegacySessionArg(rawArgs);
132
- if (isRootHelpRequest(rawArgs)) {
133
- console.log(renderUsage(app));
134
- return;
135
- }
136
- logger.info("cli-command", { args: rawArgs });
137
- const result = await app.run(rawArgs);
138
- if (typeof result === "string") {
139
- console.log(result);
140
- }
141
- } catch (err) {
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
- console.log(renderUsage(app));
148
- } else {
149
- console.error(message);
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 {
@@ -23,7 +23,7 @@ const aiCommands = SimpleCLI.group({
23
23
  preset: input.preset
24
24
  },
25
25
  {
26
- configureCommandName: "libretto-cli ai configure"
26
+ configureCommandName: `libretto ai configure`
27
27
  }
28
28
  );
29
29
  })
@@ -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 { assertSessionAvailableForStart } from "../core/session.js";
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
- loadSessionStateMiddleware,
14
- resolveSessionMiddleware,
15
- sessionOption
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
- "Usage: libretto-cli open <url> [--headless] [--viewport WxH] [--session <name>]"
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
- function createOpenCommand(logger) {
56
- return SimpleCLI.command({
57
- description: "Launch browser and open URL (headed by default)"
58
- }).input(openInput).use(resolveSessionMiddleware).handle(async ({ input, ctx }) => {
59
- assertSessionAvailableForStart(ctx.session, logger);
60
- const headed = input.headed || !input.headless;
61
- const viewport = parseViewportArg(input.viewport);
62
- await runOpen(input.url, headed, ctx.session, logger, { viewport });
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
- "Usage: libretto-cli save <url|domain> [--session <name>]"
96
+ `Usage: libretto save <url|domain> --session <name>`
77
97
  );
78
- function createSaveCommand(logger) {
79
- return SimpleCLI.command({
80
- description: "Save current browser session"
81
- }).input(saveInput).use(resolveSessionMiddleware).use(loadSessionStateMiddleware).handle(async ({ input, ctx }) => {
82
- await runSave(input.urlOrDomain, ctx.session, logger);
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
- function createPagesCommand(logger) {
92
- return SimpleCLI.command({
93
- description: "List open pages in the session"
94
- }).input(pagesInput).use(resolveSessionMiddleware).use(loadSessionStateMiddleware).handle(async ({ ctx }) => {
95
- await runPages(ctx.session, logger);
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({ help: "Close all tracked sessions in this workspace" }),
103
- force: SimpleCLI.flag({ help: "Force kill sessions that ignore SIGTERM (requires --all)" })
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
- function createCloseCommand(logger) {
107
- return SimpleCLI.command({
108
- description: "Close the browser"
109
- }).input(closeInput).use(resolveSessionMiddleware).handle(async ({ input, ctx }) => {
110
- if (input.force && !input.all) {
111
- throw new Error("Usage: libretto-cli close --all [--force]");
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
- createBrowserCommands,
136
- createCloseCommand,
137
- createOpenCommand,
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
  };