libretto 0.1.4 → 0.2.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.
Files changed (183) hide show
  1. package/README.md +213 -17
  2. package/bin/libretto.mjs +18 -0
  3. package/dist/cli/cli.js +201 -0
  4. package/dist/cli/commands/ai.js +21 -0
  5. package/dist/cli/commands/browser.js +56 -0
  6. package/dist/cli/commands/execution.js +407 -0
  7. package/dist/cli/commands/logs.js +65 -0
  8. package/dist/cli/commands/snapshot.js +99 -0
  9. package/dist/cli/core/ai-config.js +149 -0
  10. package/dist/cli/core/browser.js +687 -0
  11. package/dist/cli/core/context.js +113 -0
  12. package/dist/cli/core/pause-signals.js +29 -0
  13. package/dist/cli/core/session.js +183 -0
  14. package/dist/cli/core/snapshot-analyzer.js +492 -0
  15. package/dist/cli/core/telemetry.js +350 -0
  16. package/dist/cli/index.js +13 -0
  17. package/dist/cli/workers/run-integration-runtime.js +204 -0
  18. package/dist/cli/workers/run-integration-worker-protocol.js +0 -0
  19. package/dist/cli/workers/run-integration-worker.js +83 -0
  20. package/dist/index.cjs +127 -0
  21. package/dist/index.d.cts +22 -0
  22. package/dist/index.d.ts +22 -0
  23. package/dist/index.js +110 -0
  24. package/dist/runtime/download/download.cjs +70 -0
  25. package/dist/runtime/download/download.d.cts +35 -0
  26. package/dist/runtime/download/download.d.ts +35 -0
  27. package/dist/runtime/download/download.js +45 -0
  28. package/dist/runtime/download/index.cjs +30 -0
  29. package/dist/runtime/download/index.d.cts +3 -0
  30. package/dist/runtime/download/index.d.ts +3 -0
  31. package/dist/runtime/download/index.js +8 -0
  32. package/dist/runtime/extract/extract.cjs +87 -0
  33. package/dist/runtime/extract/extract.d.cts +23 -0
  34. package/dist/runtime/extract/extract.d.ts +23 -0
  35. package/dist/runtime/extract/extract.js +63 -0
  36. package/dist/runtime/extract/index.cjs +28 -0
  37. package/dist/runtime/extract/index.d.cts +5 -0
  38. package/dist/runtime/extract/index.d.ts +5 -0
  39. package/dist/runtime/extract/index.js +4 -0
  40. package/dist/runtime/network/index.cjs +28 -0
  41. package/dist/runtime/network/index.d.cts +4 -0
  42. package/dist/runtime/network/index.d.ts +4 -0
  43. package/dist/runtime/network/index.js +6 -0
  44. package/dist/runtime/network/network.cjs +91 -0
  45. package/dist/runtime/network/network.d.cts +28 -0
  46. package/dist/runtime/network/network.d.ts +28 -0
  47. package/dist/runtime/network/network.js +67 -0
  48. package/dist/runtime/recovery/agent.cjs +218 -0
  49. package/dist/runtime/recovery/agent.d.cts +13 -0
  50. package/dist/runtime/recovery/agent.d.ts +13 -0
  51. package/dist/runtime/recovery/agent.js +194 -0
  52. package/dist/runtime/recovery/errors.cjs +122 -0
  53. package/dist/runtime/recovery/errors.d.cts +31 -0
  54. package/dist/runtime/recovery/errors.d.ts +31 -0
  55. package/dist/runtime/recovery/errors.js +98 -0
  56. package/dist/runtime/recovery/index.cjs +34 -0
  57. package/dist/runtime/recovery/index.d.cts +7 -0
  58. package/dist/runtime/recovery/index.d.ts +7 -0
  59. package/dist/runtime/recovery/index.js +10 -0
  60. package/dist/runtime/recovery/recovery.cjs +53 -0
  61. package/dist/runtime/recovery/recovery.d.cts +12 -0
  62. package/dist/runtime/recovery/recovery.d.ts +12 -0
  63. package/dist/runtime/recovery/recovery.js +29 -0
  64. package/dist/runtime/step/index.cjs +31 -0
  65. package/dist/runtime/step/index.d.cts +7 -0
  66. package/dist/runtime/step/index.d.ts +7 -0
  67. package/dist/runtime/step/index.js +6 -0
  68. package/dist/runtime/step/runner.cjs +208 -0
  69. package/dist/runtime/step/runner.d.cts +16 -0
  70. package/dist/runtime/step/runner.d.ts +16 -0
  71. package/dist/runtime/step/runner.js +187 -0
  72. package/dist/runtime/step/step.cjs +67 -0
  73. package/dist/runtime/step/step.d.cts +23 -0
  74. package/dist/runtime/step/step.d.ts +23 -0
  75. package/dist/runtime/step/step.js +43 -0
  76. package/dist/runtime/step/types.cjs +16 -0
  77. package/dist/runtime/step/types.d.cts +72 -0
  78. package/dist/runtime/step/types.d.ts +72 -0
  79. package/dist/runtime/step/types.js +0 -0
  80. package/dist/shared/config/config.cjs +44 -0
  81. package/dist/shared/config/config.d.cts +10 -0
  82. package/dist/shared/config/config.d.ts +10 -0
  83. package/dist/shared/config/config.js +18 -0
  84. package/dist/shared/config/index.cjs +32 -0
  85. package/dist/shared/config/index.d.cts +1 -0
  86. package/dist/shared/config/index.d.ts +1 -0
  87. package/dist/shared/config/index.js +10 -0
  88. package/dist/shared/debug/index.cjs +32 -0
  89. package/dist/shared/debug/index.d.cts +2 -0
  90. package/dist/shared/debug/index.d.ts +2 -0
  91. package/dist/shared/debug/index.js +10 -0
  92. package/dist/shared/debug/pause.cjs +56 -0
  93. package/dist/shared/debug/pause.d.cts +23 -0
  94. package/dist/shared/debug/pause.d.ts +23 -0
  95. package/dist/shared/debug/pause.js +30 -0
  96. package/dist/shared/instrumentation/errors.cjs +81 -0
  97. package/dist/shared/instrumentation/errors.d.cts +12 -0
  98. package/dist/shared/instrumentation/errors.d.ts +12 -0
  99. package/dist/shared/instrumentation/errors.js +57 -0
  100. package/dist/shared/instrumentation/index.cjs +35 -0
  101. package/dist/shared/instrumentation/index.d.cts +6 -0
  102. package/dist/shared/instrumentation/index.d.ts +6 -0
  103. package/dist/shared/instrumentation/index.js +12 -0
  104. package/dist/shared/instrumentation/instrument.cjs +206 -0
  105. package/dist/shared/instrumentation/instrument.d.cts +32 -0
  106. package/dist/shared/instrumentation/instrument.d.ts +32 -0
  107. package/dist/shared/instrumentation/instrument.js +190 -0
  108. package/dist/shared/llm/client.cjs +139 -0
  109. package/dist/shared/llm/client.d.cts +6 -0
  110. package/dist/shared/llm/client.d.ts +6 -0
  111. package/dist/shared/llm/client.js +115 -0
  112. package/dist/shared/llm/index.cjs +28 -0
  113. package/dist/shared/llm/index.d.cts +3 -0
  114. package/dist/shared/llm/index.d.ts +3 -0
  115. package/dist/shared/llm/index.js +4 -0
  116. package/dist/shared/llm/types.cjs +16 -0
  117. package/dist/shared/llm/types.d.cts +34 -0
  118. package/dist/shared/llm/types.d.ts +34 -0
  119. package/dist/shared/llm/types.js +0 -0
  120. package/dist/shared/logger/index.cjs +35 -0
  121. package/dist/shared/logger/index.d.cts +2 -0
  122. package/dist/shared/logger/index.d.ts +2 -0
  123. package/dist/shared/logger/index.js +12 -0
  124. package/dist/shared/logger/logger.cjs +200 -0
  125. package/dist/shared/logger/logger.d.cts +70 -0
  126. package/dist/shared/logger/logger.d.ts +70 -0
  127. package/dist/shared/logger/logger.js +176 -0
  128. package/dist/shared/logger/sinks.cjs +160 -0
  129. package/dist/shared/logger/sinks.d.cts +9 -0
  130. package/dist/shared/logger/sinks.d.ts +9 -0
  131. package/dist/shared/logger/sinks.js +124 -0
  132. package/dist/shared/paths/paths.cjs +104 -0
  133. package/dist/shared/paths/paths.d.cts +10 -0
  134. package/dist/shared/paths/paths.d.ts +10 -0
  135. package/dist/shared/paths/paths.js +73 -0
  136. package/dist/shared/run/api.cjs +35 -0
  137. package/dist/shared/run/api.d.cts +3 -0
  138. package/dist/shared/run/api.d.ts +3 -0
  139. package/dist/shared/run/api.js +12 -0
  140. package/dist/shared/run/browser.cjs +98 -0
  141. package/dist/shared/run/browser.d.cts +22 -0
  142. package/dist/shared/run/browser.d.ts +22 -0
  143. package/dist/shared/run/browser.js +74 -0
  144. package/dist/shared/state/index.cjs +38 -0
  145. package/dist/shared/state/index.d.cts +2 -0
  146. package/dist/shared/state/index.d.ts +2 -0
  147. package/dist/shared/state/index.js +16 -0
  148. package/dist/shared/state/session-state.cjs +85 -0
  149. package/dist/shared/state/session-state.d.cts +34 -0
  150. package/dist/shared/state/session-state.d.ts +34 -0
  151. package/dist/shared/state/session-state.js +56 -0
  152. package/dist/shared/visualization/ghost-cursor.cjs +174 -0
  153. package/dist/shared/visualization/ghost-cursor.d.cts +37 -0
  154. package/dist/shared/visualization/ghost-cursor.d.ts +37 -0
  155. package/dist/shared/visualization/ghost-cursor.js +145 -0
  156. package/dist/shared/visualization/highlight.cjs +134 -0
  157. package/dist/shared/visualization/highlight.d.cts +22 -0
  158. package/dist/shared/visualization/highlight.d.ts +22 -0
  159. package/dist/shared/visualization/highlight.js +108 -0
  160. package/dist/shared/visualization/index.cjs +45 -0
  161. package/dist/shared/visualization/index.d.cts +3 -0
  162. package/dist/shared/visualization/index.d.ts +3 -0
  163. package/dist/shared/visualization/index.js +24 -0
  164. package/dist/shared/workflow/workflow.cjs +47 -0
  165. package/dist/shared/workflow/workflow.d.cts +33 -0
  166. package/dist/shared/workflow/workflow.d.ts +33 -0
  167. package/dist/shared/workflow/workflow.js +21 -0
  168. package/package.json +123 -26
  169. package/.npmignore +0 -2
  170. package/bin/libretto +0 -31
  171. package/lib/connect.js +0 -34
  172. package/lib/export.js +0 -224
  173. package/lib/import.js +0 -168
  174. package/lib/index.js +0 -8
  175. package/lib/log.js +0 -9
  176. package/lib/validate.js +0 -20
  177. package/makefile +0 -8
  178. package/src/connect.coffee +0 -25
  179. package/src/export.coffee +0 -222
  180. package/src/import.coffee +0 -171
  181. package/src/index.coffee +0 -3
  182. package/src/log.coffee +0 -3
  183. package/src/validate.coffee +0 -10
package/README.md CHANGED
@@ -1,33 +1,229 @@
1
- Libretto loads fixture data into your mongodb database.
1
+ # Libretto
2
2
 
3
- Exporting:
3
+ A TypeScript library for browser automation with AI-powered recovery and data extraction. Built on Playwright.
4
+
5
+ ## Features
6
+
7
+ - **Step-based workflows** — Define automation as named steps with built-in error handling and recovery
8
+ - **AI-powered recovery** — Vision-based agent that automatically detects and dismisses popups or obstacles using an LLM
9
+ - **Structured data extraction** — Extract typed data from web pages using AI vision + Zod schemas
10
+ - **Error detection** — Classify form/submission errors against known patterns
11
+ - **Debug bundles** — On failure, captures screenshots, DOM, logs, and step history for investigation
12
+ - **Dry-run mode** — Run workflows in simulation without side effects
13
+ - **Pluggable LLM** — Bring your own LLM provider (Claude, GPT, etc.) via a simple interface
14
+
15
+ ## Installation
4
16
 
5
17
  ```bash
6
- libretto export /path/to/fixtures/dir --database=app-testing
18
+ pnpm add libretto playwright zod
7
19
  ```
8
20
 
9
- Importing:
21
+ `playwright` and `zod` are peer dependencies.
10
22
 
11
- ```bash
12
- libretto import /path/to/fixtures/dir --database=app-testing
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { chromium } from "playwright";
27
+ import { step, createRunner } from "libretto";
28
+
29
+ const runner = createRunner({
30
+ llmClient: myLLMClient, // optional — enables AI recovery & extraction
31
+ });
32
+
33
+ const steps = [
34
+ step("navigate", async ({ page, logger }) => {
35
+ await page.goto("https://example.com/login");
36
+ logger.info("navigated to login page");
37
+ }),
38
+
39
+ step("login", async ({ page }) => {
40
+ await page.fill("#email", "user@example.com");
41
+ await page.fill("#password", "secret");
42
+ await page.click('button[type="submit"]');
43
+ await page.waitForURL("**/dashboard");
44
+ }),
45
+
46
+ step("scrape-data", async ({ page, logger }) => {
47
+ const title = await page.textContent("h1");
48
+ logger.info("page title", { title });
49
+ }),
50
+ ];
51
+
52
+ const browser = await chromium.launch();
53
+ const page = await browser.newPage();
54
+ await runner.run(page, steps);
55
+ await browser.close();
56
+ ```
57
+
58
+ ## Core Concepts
59
+
60
+ ### Steps
61
+
62
+ A step is a named unit of work. Create one with the `step()` factory:
63
+
64
+ ```typescript
65
+ step("step-name", async ({ page, logger, config }) => {
66
+ // page: Playwright Page instance
67
+ // logger: scoped logger for this step
68
+ // config: { dryRun, debug, logDir }
69
+ });
70
+ ```
71
+
72
+ ### Step Options
73
+
74
+ ```typescript
75
+ step("submit-form", handler, {
76
+ dryRun: "skip", // "skip" (default) | "execute" | "simulate"
77
+ simulate: async ({ logger }) => {
78
+ logger.info("simulated form submission");
79
+ },
80
+ recovery: {
81
+ "session-expired": async ({ page, logger }) => {
82
+ await page.click("#re-login");
83
+ },
84
+ },
85
+ });
86
+ ```
87
+
88
+ - **`dryRun`** — Controls behavior when the runner is in dry-run mode:
89
+ - `"skip"` — Skip the step entirely
90
+ - `"execute"` — Run normally even in dry-run mode
91
+ - `"simulate"` — Call the `simulate` function instead
92
+ - **`recovery`** — Named recovery handlers tried after AI recovery fails
93
+
94
+ ### Extending Steps
95
+
96
+ Use `step.extend()` to create a step factory with shared recovery handlers:
97
+
98
+ ```typescript
99
+ const myStep = step.extend({
100
+ recovery: {
101
+ "cookie-banner": async ({ page }) => {
102
+ await page.click("#accept-cookies");
103
+ },
104
+ },
105
+ });
106
+
107
+ // Every step created with myStep inherits the cookie-banner recovery
108
+ myStep("checkout", async ({ page }) => { /* ... */ });
109
+ ```
110
+
111
+ ### Runner
112
+
113
+ ```typescript
114
+ import { createRunner } from "libretto";
115
+
116
+ const runner = createRunner({
117
+ llmClient, // optional — enables AI recovery & extraction
118
+ dryRun: false, // run in dry-run mode
119
+ debug: false, // enable debug mode
120
+ logDir: "./logs", // defaults to .libretto/sessions/<sessionName>/logs
121
+ });
122
+
123
+ await runner.run(page, steps);
124
+ ```
125
+
126
+ The runner executes steps sequentially. For each step it:
127
+ 1. Captures a start screenshot
128
+ 2. Runs the handler with automatic popup recovery (if `llmClient` provided)
129
+ 3. Falls back to custom recovery handlers on failure
130
+ 4. Generates a debug bundle if all recovery fails
131
+ 5. Captures an end screenshot
132
+
133
+ ## LLM Client Interface
134
+
135
+ Provide your own implementation backed by any LLM provider:
136
+
137
+ ```typescript
138
+ import type { LLMClient } from "libretto";
139
+
140
+ const myLLMClient: LLMClient = {
141
+ async generateObject({ prompt, schema, temperature }) {
142
+ // Call your LLM, return parsed + validated result
143
+ },
144
+ async generateObjectFromMessages({ messages, schema, temperature }) {
145
+ // Call your LLM with message history, return parsed + validated result
146
+ },
147
+ };
148
+ ```
149
+
150
+ ## Data Extraction
151
+
152
+ Extract structured data from a page using AI vision:
153
+
154
+ ```typescript
155
+ import { extractFromPage } from "libretto/extract";
156
+ import { z } from "zod";
157
+
158
+ const result = await extractFromPage(page, llmClient, {
159
+ prompt: "Extract the product name and price from this page",
160
+ schema: z.object({
161
+ name: z.string(),
162
+ price: z.number(),
163
+ }),
164
+ });
165
+ // result is typed as { name: string; price: number }
13
166
  ```
14
167
 
15
- Using in tests:
168
+ ## Error Detection
169
+
170
+ Detect and classify form submission errors:
16
171
 
172
+ ```typescript
173
+ import { detectSubmissionError } from "libretto/recovery";
17
174
 
18
- ```javascript
19
- var exec = require("child_process").exec;
175
+ const error = await detectSubmissionError(page, llmClient, [
176
+ { name: "duplicate-entry", description: "Record already exists" },
177
+ { name: "invalid-field", description: "A form field has a validation error" },
178
+ ]);
179
+
180
+ if (error) {
181
+ console.log(error.name, error.details);
182
+ }
183
+ ```
20
184
 
21
- describe("test#", function() {
185
+ ## Logging
22
186
 
23
- //load the fixtures
24
- before(function(next) {
25
- exec("./node_modules/.bin/libretto import " + __dirname + "/fixtures/scenario1 --database=app-testing", next);
26
- });
187
+ ```typescript
188
+ import { Logger, createFileLogSink, prettyConsoleSink } from "libretto/logger";
189
+
190
+ const logger = new Logger()
191
+ .withSink(createFileLogSink({ filePath: "./app.log" }))
192
+ .withSink(prettyConsoleSink);
193
+
194
+ const scoped = logger.withScope("auth");
195
+ scoped.info("login attempt", { user: "alice" });
196
+ scoped.error("login failed", { reason: "bad password" });
197
+ ```
27
198
 
28
- it("do some test", function(next) {
199
+ ## Module Exports
29
200
 
30
- });
201
+ Libretto provides granular imports:
31
202
 
32
- })
203
+ | Import | Contents |
204
+ | ------------------------ | --------------------------------------------- |
205
+ | `libretto` | Everything |
206
+ | `libretto/step` | `step`, `createRunner` |
207
+ | `libretto/logger` | `Logger`, sinks |
208
+ | `libretto/recovery` | `attemptWithRecovery`, `detectSubmissionError` |
209
+ | `libretto/extract` | `extractFromPage` |
210
+ | `libretto/network` | `pageRequest` |
211
+ | `libretto/debug` | `debugPause` |
212
+ | `libretto/config` | `isDryRun`, `isDebugMode`, etc. |
213
+
214
+ ## Configuration
215
+
216
+ Runtime flags can be set via runner config or environment variables:
217
+
218
+ | Env Variable | Effect |
219
+ | --------------------- | ------------------------ |
220
+ | `LIBRETTO_DEBUG` | Enable debug mode |
221
+ | `LIBRETTO_DRY_RUN` | Enable dry-run mode |
222
+
223
+ ## Development
224
+
225
+ ```bash
226
+ pnpm install
227
+ pnpm build # compile to dist/
228
+ pnpm type-check # typecheck without emitting
33
229
  ```
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { execSync } from "node:child_process";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const cliRoot = join(__dirname, "..");
9
+ const distEntry = join(cliRoot, "dist", "cli", "index.js");
10
+
11
+ if (!existsSync(distEntry)) {
12
+ // Build from the libretto monorepo root (builds core then CLI in order)
13
+ const monorepoRoot = join(cliRoot, "..", "..");
14
+ console.error("[libretto] dist not found, building...");
15
+ execSync("pnpm build", { cwd: monorepoRoot, stdio: "inherit" });
16
+ }
17
+
18
+ await import(distEntry);
@@ -0,0 +1,201 @@
1
+ import yargs from "yargs";
2
+ import { hideBin } from "yargs/helpers";
3
+ import { registerAICommands } from "./commands/ai.js";
4
+ import { registerBrowserCommands } from "./commands/browser.js";
5
+ import { registerExecutionCommands } from "./commands/execution.js";
6
+ import { registerLogCommands } from "./commands/logs.js";
7
+ import { registerSnapshotCommands } from "./commands/snapshot.js";
8
+ import {
9
+ closeLogger,
10
+ createLoggerForSession,
11
+ ensureLibrettoSetup
12
+ } from "./core/context.js";
13
+ import {
14
+ SESSION_DEFAULT,
15
+ validateSessionName
16
+ } from "./core/session.js";
17
+ const CLI_COMMANDS = /* @__PURE__ */ new Set([
18
+ "open",
19
+ "run",
20
+ "ai",
21
+ "save",
22
+ "exec",
23
+ "snapshot",
24
+ "network",
25
+ "actions",
26
+ "resume",
27
+ "close",
28
+ "--help",
29
+ "-h",
30
+ "help"
31
+ ]);
32
+ function printUsage() {
33
+ console.log(`Usage: libretto-cli <command> [--session <name>]
34
+
35
+ Commands:
36
+ open <url> [--headless] Launch browser and open URL (headed by default)
37
+ Automatically loads saved profile if available
38
+ run <integrationFile> <integrationExport> [--params <json> | --params-file <path>] [--headed|--headless] [--debug] Run an exported Libretto workflow from a file; pass --debug to enable pause-on-failure debugging (or --no-debug to disable)
39
+ ai configure [preset] [-- <command prefix...>] Configure AI runtime for analysis commands
40
+ save <url|domain> Save current browser session (cookies, localStorage, etc.)
41
+ exec <code> [--visualize] Execute Playwright typescript code (--visualize enables ghost cursor + highlight)
42
+ snapshot [--objective <text> --context <text>] Capture PNG + HTML; analyze when objective is provided (context optional)
43
+ network [--last N] [--filter regex] [--method M] [--clear] View captured network requests
44
+ actions [--last N] [--filter regex] [--action TYPE] [--source SOURCE] [--clear] View captured actions
45
+ resume Resume a paused workflow in the active session
46
+ close Close the browser
47
+
48
+ Options:
49
+ --session <name> Use a named session (default: "default")
50
+ Built-in sessions: default, dev-server, browser-agent
51
+
52
+ Examples:
53
+ libretto-cli open https://linkedin.com
54
+
55
+ # ... manually log in ...
56
+ libretto-cli save linkedin.com
57
+ # Next time you open linkedin.com, you'll be logged in automatically
58
+
59
+ libretto-cli exec "await page.locator('button:has-text(\\"Sign in\\")').click()"
60
+ libretto-cli exec "await page.fill('input[name=\\"email\\"]', 'test@example.com')"
61
+ libretto-cli ai configure codex
62
+ libretto-cli ai configure claude
63
+ libretto-cli ai configure gemini
64
+ libretto-cli ai configure <codex|claude|gemini> -- <command prefix...>
65
+ libretto-cli snapshot
66
+ libretto-cli snapshot --objective "Find the submit button" --context "Submitting a referral form, already filled in patient details"
67
+ libretto-cli resume --session default
68
+ libretto-cli close
69
+
70
+ # Multiple sessions
71
+ libretto-cli open https://site1.com --session test1
72
+ libretto-cli open https://site2.com --session test2
73
+ libretto-cli exec "return await page.title()" --session test1
74
+
75
+ Available in exec:
76
+ page, context, state, browser, networkLog, actionLog
77
+
78
+ Profiles:
79
+ Profiles are saved to .libretto/profiles/<domain>.json (git-ignored)
80
+ They persist cookies, localStorage, and session data across browser launches.
81
+ Local profiles are machine-local and are not shared with other users/environments.
82
+ Sessions can expire; if run fails auth, log in again and re-save the profile.
83
+
84
+ Sessions:
85
+ Session state is stored in .libretto/sessions/<session>/state.json
86
+ CLI logs are stored in .libretto/sessions/<session>/logs.jsonl
87
+ Each session runs an isolated browser instance on a dynamic port.
88
+ `);
89
+ }
90
+ function filterSessionArgs(args) {
91
+ const result = [];
92
+ for (let i = 0; i < args.length; i++) {
93
+ if (args[i] === "--session") {
94
+ i++;
95
+ } else {
96
+ result.push(args[i]);
97
+ }
98
+ }
99
+ return result;
100
+ }
101
+ function parseSessionForLog(rawArgs) {
102
+ const idx = rawArgs.indexOf("--session");
103
+ if (idx < 0) return SESSION_DEFAULT;
104
+ const value = rawArgs[idx + 1];
105
+ if (!value || value.startsWith("--") || CLI_COMMANDS.has(value)) {
106
+ return SESSION_DEFAULT;
107
+ }
108
+ try {
109
+ validateSessionName(value);
110
+ return value;
111
+ } catch {
112
+ return SESSION_DEFAULT;
113
+ }
114
+ }
115
+ function validateLegacySessionArg(rawArgs) {
116
+ const idx = rawArgs.indexOf("--session");
117
+ if (idx < 0) return;
118
+ const value = rawArgs[idx + 1];
119
+ if (!value || value.startsWith("--") || CLI_COMMANDS.has(value)) {
120
+ throw new Error(
121
+ "Usage: libretto-cli <command> [--session <name>]\nMissing or invalid --session value."
122
+ );
123
+ }
124
+ validateSessionName(value);
125
+ }
126
+ function initializeLogger(rawArgs) {
127
+ const sessionForLog = parseSessionForLog(rawArgs);
128
+ const logger = createLoggerForSession(sessionForLog);
129
+ logger.info("cli-start", {
130
+ args: rawArgs,
131
+ cwd: process.cwd(),
132
+ session: sessionForLog
133
+ });
134
+ return logger;
135
+ }
136
+ async function withCliLogger(rawArgs, run) {
137
+ const logger = initializeLogger(rawArgs);
138
+ try {
139
+ return await run(logger);
140
+ } finally {
141
+ await closeLogger(logger);
142
+ }
143
+ }
144
+ function createParser(logger) {
145
+ let parser = yargs(hideBin(process.argv)).scriptName("libretto-cli").parserConfiguration({ "populate--": true }).option("session", {
146
+ type: "string",
147
+ default: SESSION_DEFAULT,
148
+ describe: "Use a named session",
149
+ global: true
150
+ }).middleware((argv) => {
151
+ validateSessionName(String(argv.session));
152
+ }).exitProcess(false).help(false).version(false).fail((msg, err) => {
153
+ if (err) throw err;
154
+ throw new Error(msg || "Command failed");
155
+ });
156
+ parser = registerBrowserCommands(parser, logger);
157
+ parser = registerExecutionCommands(parser, logger);
158
+ parser = registerLogCommands(parser);
159
+ parser = registerAICommands(parser);
160
+ parser = registerSnapshotCommands(parser, logger);
161
+ parser = parser.command("help", "Show usage", () => {
162
+ }, () => {
163
+ printUsage();
164
+ });
165
+ return parser;
166
+ }
167
+ async function runLibrettoCLI() {
168
+ const rawArgs = process.argv.slice(2);
169
+ let exitCode = 0;
170
+ ensureLibrettoSetup();
171
+ await withCliLogger(rawArgs, async (logger) => {
172
+ try {
173
+ validateLegacySessionArg(rawArgs);
174
+ const args = filterSessionArgs(rawArgs);
175
+ const command = args[0];
176
+ if (!command || command === "--help" || command === "-h" || command === "help") {
177
+ printUsage();
178
+ return;
179
+ }
180
+ if (!CLI_COMMANDS.has(command)) {
181
+ console.error(`Unknown command: ${command}
182
+ `);
183
+ printUsage();
184
+ exitCode = 1;
185
+ return;
186
+ }
187
+ const parser = createParser(logger);
188
+ logger.info("cli-command", { command, args });
189
+ await parser.parseAsync();
190
+ } catch (err) {
191
+ logger.error("cli-error", { error: err, args: rawArgs });
192
+ const message = err instanceof Error ? err.message : String(err);
193
+ console.error(message);
194
+ exitCode = 1;
195
+ }
196
+ });
197
+ process.exit(exitCode);
198
+ }
199
+ export {
200
+ runLibrettoCLI
201
+ };
@@ -0,0 +1,21 @@
1
+ import { runAiConfigure } from "../core/ai-config.js";
2
+ function registerAICommands(yargs) {
3
+ return yargs.command(
4
+ "ai configure [preset]",
5
+ "Configure AI runtime",
6
+ (cmd) => cmd.option("clear", { type: "boolean", default: false }),
7
+ (argv) => {
8
+ const customPrefix = Array.isArray(argv["--"]) ? argv["--"] : [];
9
+ runAiConfigure({
10
+ clear: Boolean(argv.clear),
11
+ preset: argv.preset,
12
+ customPrefix
13
+ }, {
14
+ configureCommandName: "libretto-cli ai configure"
15
+ });
16
+ }
17
+ );
18
+ }
19
+ export {
20
+ registerAICommands
21
+ };
@@ -0,0 +1,56 @@
1
+ import {
2
+ runClose as runCloseWithLogger,
3
+ runOpen,
4
+ runSave
5
+ } from "../core/browser.js";
6
+ import { withSessionLogger } from "../core/context.js";
7
+ function registerBrowserCommands(yargs, logger) {
8
+ return yargs.command(
9
+ "open [url]",
10
+ "Launch browser and open URL (headed by default)",
11
+ (cmd) => cmd.option("headed", {
12
+ type: "boolean",
13
+ default: false
14
+ }).option("headless", {
15
+ type: "boolean",
16
+ default: false
17
+ }),
18
+ async (argv) => {
19
+ const hasHeadedFlag = Boolean(argv.headed);
20
+ const hasHeadlessFlag = Boolean(argv.headless);
21
+ if (hasHeadedFlag && hasHeadlessFlag) {
22
+ throw new Error("Cannot pass both --headed and --headless.");
23
+ }
24
+ const headed = hasHeadedFlag || !hasHeadlessFlag;
25
+ const url = argv.url;
26
+ if (!url) {
27
+ throw new Error(
28
+ "Usage: libretto-cli open <url> [--headless] [--session <name>]"
29
+ );
30
+ }
31
+ await runOpen(url, headed, String(argv.session), logger);
32
+ }
33
+ ).command(
34
+ "save [urlOrDomain]",
35
+ "Save current browser session",
36
+ (cmd) => cmd,
37
+ async (argv) => {
38
+ const urlOrDomain = argv.urlOrDomain;
39
+ if (!urlOrDomain) {
40
+ throw new Error("Usage: libretto-cli save <url|domain> [--session <name>]");
41
+ }
42
+ await runSave(urlOrDomain, String(argv.session), logger);
43
+ }
44
+ ).command("close", "Close the browser", (cmd) => cmd, async (argv) => {
45
+ await runCloseWithLogger(String(argv.session), logger);
46
+ });
47
+ }
48
+ async function runClose(session) {
49
+ await withSessionLogger(session, async (logger) => {
50
+ await runCloseWithLogger(session, logger);
51
+ });
52
+ }
53
+ export {
54
+ registerBrowserCommands,
55
+ runClose
56
+ };