libretto 0.1.5 → 0.2.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 (168) hide show
  1. package/README.md +215 -17
  2. package/bin/libretto.mjs +18 -0
  3. package/dist/cli/cli.js +203 -0
  4. package/dist/cli/commands/ai.js +21 -0
  5. package/dist/cli/commands/browser.js +59 -0
  6. package/dist/cli/commands/execution.js +422 -0
  7. package/dist/cli/commands/logs.js +93 -0
  8. package/dist/cli/commands/snapshot.js +106 -0
  9. package/dist/cli/core/ai-config.js +149 -0
  10. package/dist/cli/core/browser.js +523 -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-telemetry.js +491 -0
  14. package/dist/cli/core/session.js +183 -0
  15. package/dist/cli/core/snapshot-analyzer.js +492 -0
  16. package/dist/cli/core/telemetry.js +362 -0
  17. package/dist/cli/index.js +13 -0
  18. package/dist/cli/workers/run-integration-runtime.js +222 -0
  19. package/dist/cli/workers/run-integration-worker-protocol.js +0 -0
  20. package/dist/cli/workers/run-integration-worker.js +83 -0
  21. package/dist/index.cjs +123 -0
  22. package/dist/index.d.cts +19 -0
  23. package/dist/index.d.ts +19 -0
  24. package/dist/index.js +107 -0
  25. package/dist/runtime/download/download.cjs +70 -0
  26. package/dist/runtime/download/download.d.cts +35 -0
  27. package/dist/runtime/download/download.d.ts +35 -0
  28. package/dist/runtime/download/download.js +45 -0
  29. package/dist/runtime/download/index.cjs +30 -0
  30. package/dist/runtime/download/index.d.cts +3 -0
  31. package/dist/runtime/download/index.d.ts +3 -0
  32. package/dist/runtime/download/index.js +8 -0
  33. package/dist/runtime/extract/extract.cjs +88 -0
  34. package/dist/runtime/extract/extract.d.cts +23 -0
  35. package/dist/runtime/extract/extract.d.ts +23 -0
  36. package/dist/runtime/extract/extract.js +64 -0
  37. package/dist/runtime/extract/index.cjs +28 -0
  38. package/dist/runtime/extract/index.d.cts +5 -0
  39. package/dist/runtime/extract/index.d.ts +5 -0
  40. package/dist/runtime/extract/index.js +4 -0
  41. package/dist/runtime/network/index.cjs +28 -0
  42. package/dist/runtime/network/index.d.cts +4 -0
  43. package/dist/runtime/network/index.d.ts +4 -0
  44. package/dist/runtime/network/index.js +6 -0
  45. package/dist/runtime/network/network.cjs +91 -0
  46. package/dist/runtime/network/network.d.cts +28 -0
  47. package/dist/runtime/network/network.d.ts +28 -0
  48. package/dist/runtime/network/network.js +67 -0
  49. package/dist/runtime/recovery/agent.cjs +223 -0
  50. package/dist/runtime/recovery/agent.d.cts +13 -0
  51. package/dist/runtime/recovery/agent.d.ts +13 -0
  52. package/dist/runtime/recovery/agent.js +199 -0
  53. package/dist/runtime/recovery/errors.cjs +124 -0
  54. package/dist/runtime/recovery/errors.d.cts +31 -0
  55. package/dist/runtime/recovery/errors.d.ts +31 -0
  56. package/dist/runtime/recovery/errors.js +100 -0
  57. package/dist/runtime/recovery/index.cjs +34 -0
  58. package/dist/runtime/recovery/index.d.cts +7 -0
  59. package/dist/runtime/recovery/index.d.ts +7 -0
  60. package/dist/runtime/recovery/index.js +10 -0
  61. package/dist/runtime/recovery/recovery.cjs +55 -0
  62. package/dist/runtime/recovery/recovery.d.cts +12 -0
  63. package/dist/runtime/recovery/recovery.d.ts +12 -0
  64. package/dist/runtime/recovery/recovery.js +31 -0
  65. package/dist/shared/config/config.cjs +44 -0
  66. package/dist/shared/config/config.d.cts +10 -0
  67. package/dist/shared/config/config.d.ts +10 -0
  68. package/dist/shared/config/config.js +18 -0
  69. package/dist/shared/config/index.cjs +32 -0
  70. package/dist/shared/config/index.d.cts +1 -0
  71. package/dist/shared/config/index.d.ts +1 -0
  72. package/dist/shared/config/index.js +10 -0
  73. package/dist/shared/debug/index.cjs +32 -0
  74. package/dist/shared/debug/index.d.cts +2 -0
  75. package/dist/shared/debug/index.d.ts +2 -0
  76. package/dist/shared/debug/index.js +10 -0
  77. package/dist/shared/debug/pause.cjs +56 -0
  78. package/dist/shared/debug/pause.d.cts +23 -0
  79. package/dist/shared/debug/pause.d.ts +23 -0
  80. package/dist/shared/debug/pause.js +30 -0
  81. package/dist/shared/instrumentation/errors.cjs +81 -0
  82. package/dist/shared/instrumentation/errors.d.cts +12 -0
  83. package/dist/shared/instrumentation/errors.d.ts +12 -0
  84. package/dist/shared/instrumentation/errors.js +57 -0
  85. package/dist/shared/instrumentation/index.cjs +35 -0
  86. package/dist/shared/instrumentation/index.d.cts +6 -0
  87. package/dist/shared/instrumentation/index.d.ts +6 -0
  88. package/dist/shared/instrumentation/index.js +12 -0
  89. package/dist/shared/instrumentation/instrument.cjs +206 -0
  90. package/dist/shared/instrumentation/instrument.d.cts +32 -0
  91. package/dist/shared/instrumentation/instrument.d.ts +32 -0
  92. package/dist/shared/instrumentation/instrument.js +190 -0
  93. package/dist/shared/llm/client.cjs +139 -0
  94. package/dist/shared/llm/client.d.cts +6 -0
  95. package/dist/shared/llm/client.d.ts +6 -0
  96. package/dist/shared/llm/client.js +115 -0
  97. package/dist/shared/llm/index.cjs +28 -0
  98. package/dist/shared/llm/index.d.cts +3 -0
  99. package/dist/shared/llm/index.d.ts +3 -0
  100. package/dist/shared/llm/index.js +4 -0
  101. package/dist/shared/llm/types.cjs +16 -0
  102. package/dist/shared/llm/types.d.cts +34 -0
  103. package/dist/shared/llm/types.d.ts +34 -0
  104. package/dist/shared/llm/types.js +0 -0
  105. package/dist/shared/logger/index.cjs +37 -0
  106. package/dist/shared/logger/index.d.cts +2 -0
  107. package/dist/shared/logger/index.d.ts +2 -0
  108. package/dist/shared/logger/index.js +13 -0
  109. package/dist/shared/logger/logger.cjs +213 -0
  110. package/dist/shared/logger/logger.d.cts +82 -0
  111. package/dist/shared/logger/logger.d.ts +82 -0
  112. package/dist/shared/logger/logger.js +188 -0
  113. package/dist/shared/logger/sinks.cjs +160 -0
  114. package/dist/shared/logger/sinks.d.cts +9 -0
  115. package/dist/shared/logger/sinks.d.ts +9 -0
  116. package/dist/shared/logger/sinks.js +124 -0
  117. package/dist/shared/paths/paths.cjs +104 -0
  118. package/dist/shared/paths/paths.d.cts +10 -0
  119. package/dist/shared/paths/paths.d.ts +10 -0
  120. package/dist/shared/paths/paths.js +73 -0
  121. package/dist/shared/run/api.cjs +35 -0
  122. package/dist/shared/run/api.d.cts +3 -0
  123. package/dist/shared/run/api.d.ts +3 -0
  124. package/dist/shared/run/api.js +12 -0
  125. package/dist/shared/run/browser.cjs +98 -0
  126. package/dist/shared/run/browser.d.cts +22 -0
  127. package/dist/shared/run/browser.d.ts +22 -0
  128. package/dist/shared/run/browser.js +74 -0
  129. package/dist/shared/state/index.cjs +38 -0
  130. package/dist/shared/state/index.d.cts +2 -0
  131. package/dist/shared/state/index.d.ts +2 -0
  132. package/dist/shared/state/index.js +16 -0
  133. package/dist/shared/state/session-state.cjs +85 -0
  134. package/dist/shared/state/session-state.d.cts +34 -0
  135. package/dist/shared/state/session-state.d.ts +34 -0
  136. package/dist/shared/state/session-state.js +56 -0
  137. package/dist/shared/visualization/ghost-cursor.cjs +174 -0
  138. package/dist/shared/visualization/ghost-cursor.d.cts +37 -0
  139. package/dist/shared/visualization/ghost-cursor.d.ts +37 -0
  140. package/dist/shared/visualization/ghost-cursor.js +145 -0
  141. package/dist/shared/visualization/highlight.cjs +134 -0
  142. package/dist/shared/visualization/highlight.d.cts +22 -0
  143. package/dist/shared/visualization/highlight.d.ts +22 -0
  144. package/dist/shared/visualization/highlight.js +108 -0
  145. package/dist/shared/visualization/index.cjs +45 -0
  146. package/dist/shared/visualization/index.d.cts +3 -0
  147. package/dist/shared/visualization/index.d.ts +3 -0
  148. package/dist/shared/visualization/index.js +24 -0
  149. package/dist/shared/workflow/workflow.cjs +47 -0
  150. package/dist/shared/workflow/workflow.d.cts +33 -0
  151. package/dist/shared/workflow/workflow.d.ts +33 -0
  152. package/dist/shared/workflow/workflow.js +21 -0
  153. package/package.json +125 -26
  154. package/.npmignore +0 -2
  155. package/bin/libretto +0 -31
  156. package/lib/connect.js +0 -34
  157. package/lib/export.js +0 -224
  158. package/lib/import.js +0 -166
  159. package/lib/index.js +0 -8
  160. package/lib/log.js +0 -9
  161. package/lib/validate.js +0 -20
  162. package/makefile +0 -8
  163. package/src/connect.coffee +0 -25
  164. package/src/export.coffee +0 -222
  165. package/src/import.coffee +0 -166
  166. package/src/index.coffee +0 -3
  167. package/src/log.coffee +0 -3
  168. package/src/validate.coffee +0 -10
package/README.md CHANGED
@@ -1,33 +1,231 @@
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
+ - **AI-powered recovery** — Vision-based agent that automatically detects and dismisses popups or obstacles using an LLM
8
+ - **Structured data extraction** — Extract typed data from web pages using AI vision + Zod schemas
9
+ - **Error detection** — Classify form/submission errors against known patterns
10
+ - **In-browser network requests** — Execute authenticated fetch calls inside the page context with optional Zod validation
11
+ - **File downloads** — Trigger and intercept file downloads via click, with optional save-to-disk
12
+ - **Dry-run mode** — Skip mutations in development without side effects
13
+ - **Pluggable LLM** — Bring your own LLM provider (Claude, GPT, etc.) via a simple interface
14
+ - **Pluggable logging** — All runtime functions accept an optional logger; defaults to console output
15
+
16
+ ## Installation
4
17
 
5
18
  ```bash
6
- libretto export /path/to/fixtures/dir --database=app-testing
19
+ pnpm add libretto playwright zod
7
20
  ```
8
21
 
9
- Importing:
22
+ `playwright` and `zod` are peer dependencies.
10
23
 
11
- ```bash
12
- libretto import "/path/to/fixtures/dir/*" --database=app-testing
24
+ ## Quick Start
25
+
26
+ ```typescript
27
+ import { chromium } from "playwright";
28
+ import { extractFromPage, attemptWithRecovery } from "libretto";
29
+
30
+ const browser = await chromium.launch();
31
+ const page = await browser.newPage();
32
+
33
+ await page.goto("https://example.com/login");
34
+ await page.fill("#email", "user@example.com");
35
+ await page.fill("#password", "secret");
36
+
37
+ // Automatically retry with AI popup recovery on failure
38
+ await attemptWithRecovery(page, () => page.click('button[type="submit"]'));
39
+
40
+ await browser.close();
41
+ ```
42
+
43
+ ## Runtime Functions
44
+
45
+ ### Recovery
46
+
47
+ #### `attemptWithRecovery(page, fn, logger?, llmClient?)`
48
+
49
+ Executes a function and, if it fails, uses AI vision to detect and dismiss popups before retrying once.
50
+
51
+ ```typescript
52
+ import { attemptWithRecovery } from "libretto";
53
+
54
+ await attemptWithRecovery(page, async () => {
55
+ await page.click('button[type="submit"]');
56
+ }, undefined, llmClient);
57
+ ```
58
+
59
+ #### `executeRecoveryAgent(page, instruction, logger?, llmClient?)`
60
+
61
+ Runs a multi-step vision-based recovery agent that takes screenshots and executes browser actions (click, type, scroll, etc.) to resolve obstacles.
62
+
63
+ ```typescript
64
+ import { executeRecoveryAgent } from "libretto";
65
+
66
+ await executeRecoveryAgent(
67
+ page,
68
+ "Close the cookie consent banner",
69
+ undefined,
70
+ llmClient,
71
+ );
72
+ ```
73
+
74
+ #### `detectSubmissionError(page, error, logContext, llmClient, knownErrors?, logger?)`
75
+
76
+ Uses a screenshot + LLM vision to detect if an error occurred during a form submission. Matches against provided known error patterns.
77
+
78
+ ```typescript
79
+ import { detectSubmissionError } from "libretto";
80
+
81
+ try {
82
+ await page.click("#submit");
83
+ } catch (error) {
84
+ const result = await detectSubmissionError(page, error, "checkout", llmClient, [
85
+ { id: "duplicate", errorPatterns: ["already exists"], userMessage: "Duplicate entry" },
86
+ ]);
87
+ console.log(result.errorId, result.message);
88
+ }
89
+ ```
90
+
91
+ ### Data Extraction
92
+
93
+ #### `extractFromPage(options)`
94
+
95
+ Extract structured data from a page using AI vision + a Zod schema.
96
+
97
+ ```typescript
98
+ import { extractFromPage } from "libretto";
99
+ import { z } from "zod";
100
+
101
+ const result = await extractFromPage({
102
+ page,
103
+ llmClient,
104
+ instruction: "Extract the product name and price",
105
+ schema: z.object({
106
+ name: z.string(),
107
+ price: z.number(),
108
+ }),
109
+ selector: ".product-card", // optional — scopes to a specific element
110
+ });
111
+ // result is typed as { name: string; price: number }
112
+ ```
113
+
114
+ ### Network
115
+
116
+ #### `pageRequest(page, config, options?)`
117
+
118
+ Executes a fetch call inside the browser context via `page.evaluate()`, inheriting the page's cookies and auth state. Supports optional Zod validation.
119
+
120
+ ```typescript
121
+ import { pageRequest } from "libretto";
122
+ import { z } from "zod";
123
+
124
+ const data = await pageRequest(
125
+ page,
126
+ {
127
+ url: "https://example.com/api/profile",
128
+ method: "GET",
129
+ responseType: "json",
130
+ },
131
+ {
132
+ schema: z.object({ name: z.string(), email: z.string() }),
133
+ },
134
+ );
135
+ ```
136
+
137
+ ### Downloads
138
+
139
+ #### `downloadViaClick(page, selector, options?)`
140
+
141
+ Triggers a file download by clicking a DOM element and intercepts the result.
142
+
143
+ ```typescript
144
+ import { downloadViaClick } from "libretto";
145
+
146
+ const { buffer, filename } = await downloadViaClick(page, "#download-btn");
13
147
  ```
14
148
 
15
- Using in tests:
149
+ #### `downloadAndSave(page, selector, options?)`
150
+
151
+ Same as `downloadViaClick` but also writes the file to disk.
16
152
 
153
+ ```typescript
154
+ import { downloadAndSave } from "libretto";
17
155
 
18
- ```javascript
19
- var exec = require("child_process").exec;
156
+ const { savedTo } = await downloadAndSave(page, "#export-csv", {
157
+ savePath: "./exports/report.csv",
158
+ });
159
+ ```
20
160
 
21
- describe("test#", function() {
161
+ ## LLM Client Interface
22
162
 
23
- //load the fixtures
24
- before(function(next) {
25
- exec("./node_modules/.bin/libretto import " + __dirname + "/fixtures/scenario1/* --database=app-testing", next);
26
- });
163
+ Provide your own implementation backed by any LLM provider:
27
164
 
28
- it("do some test", function(next) {
165
+ ```typescript
166
+ import type { LLMClient } from "libretto";
29
167
 
30
- });
168
+ const myLLMClient: LLMClient = {
169
+ async generateObject({ prompt, schema, temperature }) {
170
+ // Call your LLM, return parsed + validated result
171
+ },
172
+ async generateObjectFromMessages({ messages, schema, temperature }) {
173
+ // Call your LLM with message history, return parsed + validated result
174
+ },
175
+ };
176
+ ```
31
177
 
32
- })
178
+ ## Logging
179
+
180
+ All runtime functions accept an optional `logger` parameter. When omitted, output goes to `console.log` with `[INFO]`, `[WARN]`, `[ERROR]` prefixes.
181
+
182
+ For structured logging, use the built-in `Logger` class:
183
+
184
+ ```typescript
185
+ import { Logger, createFileLogSink, prettyConsoleSink } from "libretto";
186
+
187
+ const logger = new Logger()
188
+ .withSink(createFileLogSink({ filePath: "./app.log" }))
189
+ .withSink(prettyConsoleSink);
190
+
191
+ const scoped = logger.withScope("auth");
192
+ scoped.info("login attempt", { user: "alice" });
193
+ scoped.error("login failed", { reason: "bad password" });
194
+ ```
195
+
196
+ ## Module Exports
197
+
198
+ Libretto provides granular imports:
199
+
200
+ | Import | Contents |
201
+ | ------------------------ | --------------------------------------------------------- |
202
+ | `libretto` | Everything |
203
+ | `libretto/logger` | `Logger`, `defaultLogger`, sinks |
204
+ | `libretto/recovery` | `attemptWithRecovery`, `executeRecoveryAgent`, `detectSubmissionError` |
205
+ | `libretto/extract` | `extractFromPage` |
206
+ | `libretto/network` | `pageRequest` |
207
+ | `libretto/download` | `downloadViaClick`, `downloadAndSave` |
208
+ | `libretto/debug` | `debugPause` |
209
+ | `libretto/config` | `isDryRun`, `isDebugMode`, `shouldPauseBeforeMutation` |
210
+ | `libretto/instrumentation` | `instrumentPage`, `installInstrumentation` |
211
+ | `libretto/visualization` | Ghost cursor and highlight helpers |
212
+ | `libretto/run` | `launchBrowser` |
213
+ | `libretto/state` | Session state serialization and parsing |
214
+ | `libretto/llm` | `LLMClient` type |
215
+
216
+ ## Configuration
217
+
218
+ Runtime flags via environment variables:
219
+
220
+ | Env Variable | Effect |
221
+ | --------------------- | --------------------------------------------------- |
222
+ | `LIBRETTO_DEBUG` | Enable debug mode |
223
+ | `LIBRETTO_DRY_RUN` | Enable dry-run mode (defaults to `true` in development) |
224
+
225
+ ## Development
226
+
227
+ ```bash
228
+ pnpm install
229
+ pnpm build # compile to dist/
230
+ pnpm type-check # typecheck without emitting
33
231
  ```
@@ -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,203 @@
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
+ "pages",
27
+ "resume",
28
+ "close",
29
+ "--help",
30
+ "-h",
31
+ "help"
32
+ ]);
33
+ function printUsage() {
34
+ console.log(`Usage: libretto-cli <command> [--session <name>]
35
+
36
+ Commands:
37
+ open <url> [--headless] Launch browser and open URL (headed by default)
38
+ Automatically loads saved profile if available
39
+ 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)
40
+ ai configure [preset] [-- <command prefix...>] Configure AI runtime for analysis commands
41
+ save <url|domain> Save current browser session (cookies, localStorage, etc.)
42
+ exec <code> [--visualize] Execute Playwright typescript code (--visualize enables ghost cursor + highlight)
43
+ snapshot [--objective <text> --context <text>] Capture PNG + HTML; analyze when objective is provided (context optional)
44
+ network [--last N] [--filter regex] [--method M] [--clear] View captured network requests
45
+ actions [--last N] [--filter regex] [--action TYPE] [--source SOURCE] [--clear] View captured actions
46
+ pages List open pages in the active session
47
+ resume Resume a paused workflow in the active session
48
+ close Close the browser
49
+
50
+ Options:
51
+ --session <name> Use a named session (default: "default")
52
+ Built-in sessions: default, dev-server, browser-agent
53
+
54
+ Examples:
55
+ libretto-cli open https://linkedin.com
56
+
57
+ # ... manually log in ...
58
+ libretto-cli save linkedin.com
59
+ # Next time you open linkedin.com, you'll be logged in automatically
60
+
61
+ libretto-cli exec "await page.locator('button:has-text(\\"Sign in\\")').click()"
62
+ libretto-cli exec "await page.fill('input[name=\\"email\\"]', 'test@example.com')"
63
+ libretto-cli ai configure codex
64
+ libretto-cli ai configure claude
65
+ libretto-cli ai configure gemini
66
+ libretto-cli ai configure <codex|claude|gemini> -- <command prefix...>
67
+ libretto-cli snapshot
68
+ libretto-cli snapshot --objective "Find the submit button" --context "Submitting a referral form, already filled in patient details"
69
+ libretto-cli resume --session default
70
+ libretto-cli close
71
+
72
+ # Multiple sessions
73
+ libretto-cli open https://site1.com --session test1
74
+ libretto-cli open https://site2.com --session test2
75
+ libretto-cli exec "return await page.title()" --session test1
76
+
77
+ Available in exec:
78
+ page, context, state, browser, networkLog, actionLog
79
+
80
+ Profiles:
81
+ Profiles are saved to .libretto/profiles/<domain>.json (git-ignored)
82
+ They persist cookies, localStorage, and session data across browser launches.
83
+ Local profiles are machine-local and are not shared with other users/environments.
84
+ Sessions can expire; if run fails auth, log in again and re-save the profile.
85
+
86
+ Sessions:
87
+ Session state is stored in .libretto/sessions/<session>/state.json
88
+ CLI logs are stored in .libretto/sessions/<session>/logs.jsonl
89
+ Each session runs an isolated browser instance on a dynamic port.
90
+ `);
91
+ }
92
+ function filterSessionArgs(args) {
93
+ const result = [];
94
+ for (let i = 0; i < args.length; i++) {
95
+ if (args[i] === "--session") {
96
+ i++;
97
+ } else {
98
+ result.push(args[i]);
99
+ }
100
+ }
101
+ return result;
102
+ }
103
+ function parseSessionForLog(rawArgs) {
104
+ const idx = rawArgs.indexOf("--session");
105
+ if (idx < 0) return SESSION_DEFAULT;
106
+ const value = rawArgs[idx + 1];
107
+ if (!value || value.startsWith("--") || CLI_COMMANDS.has(value)) {
108
+ return SESSION_DEFAULT;
109
+ }
110
+ try {
111
+ validateSessionName(value);
112
+ return value;
113
+ } catch {
114
+ return SESSION_DEFAULT;
115
+ }
116
+ }
117
+ function validateLegacySessionArg(rawArgs) {
118
+ const idx = rawArgs.indexOf("--session");
119
+ if (idx < 0) return;
120
+ const value = rawArgs[idx + 1];
121
+ if (!value || value.startsWith("--") || CLI_COMMANDS.has(value)) {
122
+ throw new Error(
123
+ "Usage: libretto-cli <command> [--session <name>]\nMissing or invalid --session value."
124
+ );
125
+ }
126
+ validateSessionName(value);
127
+ }
128
+ function initializeLogger(rawArgs) {
129
+ const sessionForLog = parseSessionForLog(rawArgs);
130
+ const logger = createLoggerForSession(sessionForLog);
131
+ logger.info("cli-start", {
132
+ args: rawArgs,
133
+ cwd: process.cwd(),
134
+ session: sessionForLog
135
+ });
136
+ return logger;
137
+ }
138
+ async function withCliLogger(rawArgs, run) {
139
+ const logger = initializeLogger(rawArgs);
140
+ try {
141
+ return await run(logger);
142
+ } finally {
143
+ await closeLogger(logger);
144
+ }
145
+ }
146
+ function createParser(logger) {
147
+ let parser = yargs(hideBin(process.argv)).scriptName("libretto-cli").parserConfiguration({ "populate--": true }).option("session", {
148
+ type: "string",
149
+ default: SESSION_DEFAULT,
150
+ describe: "Use a named session",
151
+ global: true
152
+ }).middleware((argv) => {
153
+ validateSessionName(String(argv.session));
154
+ }).exitProcess(false).help(false).version(false).fail((msg, err) => {
155
+ if (err) throw err;
156
+ throw new Error(msg || "Command failed");
157
+ });
158
+ parser = registerBrowserCommands(parser, logger);
159
+ parser = registerExecutionCommands(parser, logger);
160
+ parser = registerLogCommands(parser);
161
+ parser = registerAICommands(parser);
162
+ parser = registerSnapshotCommands(parser, logger);
163
+ parser = parser.command("help", "Show usage", () => {
164
+ }, () => {
165
+ printUsage();
166
+ });
167
+ return parser;
168
+ }
169
+ async function runLibrettoCLI() {
170
+ const rawArgs = process.argv.slice(2);
171
+ let exitCode = 0;
172
+ ensureLibrettoSetup();
173
+ await withCliLogger(rawArgs, async (logger) => {
174
+ try {
175
+ validateLegacySessionArg(rawArgs);
176
+ const args = filterSessionArgs(rawArgs);
177
+ const command = args[0];
178
+ if (!command || command === "--help" || command === "-h" || command === "help") {
179
+ printUsage();
180
+ return;
181
+ }
182
+ if (!CLI_COMMANDS.has(command)) {
183
+ console.error(`Unknown command: ${command}
184
+ `);
185
+ printUsage();
186
+ exitCode = 1;
187
+ return;
188
+ }
189
+ const parser = createParser(logger);
190
+ logger.info("cli-command", { command, args });
191
+ await parser.parseAsync();
192
+ } catch (err) {
193
+ logger.error("cli-error", { error: err, args: rawArgs });
194
+ const message = err instanceof Error ? err.message : String(err);
195
+ console.error(message);
196
+ exitCode = 1;
197
+ }
198
+ });
199
+ process.exit(exitCode);
200
+ }
201
+ export {
202
+ runLibrettoCLI
203
+ };
@@ -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,59 @@
1
+ import {
2
+ runClose as runCloseWithLogger,
3
+ runOpen,
4
+ runPages,
5
+ runSave
6
+ } from "../core/browser.js";
7
+ import { withSessionLogger } from "../core/context.js";
8
+ function registerBrowserCommands(yargs, logger) {
9
+ return yargs.command(
10
+ "open [url]",
11
+ "Launch browser and open URL (headed by default)",
12
+ (cmd) => cmd.option("headed", {
13
+ type: "boolean",
14
+ default: false
15
+ }).option("headless", {
16
+ type: "boolean",
17
+ default: false
18
+ }),
19
+ async (argv) => {
20
+ const hasHeadedFlag = Boolean(argv.headed);
21
+ const hasHeadlessFlag = Boolean(argv.headless);
22
+ if (hasHeadedFlag && hasHeadlessFlag) {
23
+ throw new Error("Cannot pass both --headed and --headless.");
24
+ }
25
+ const headed = hasHeadedFlag || !hasHeadlessFlag;
26
+ const url = argv.url;
27
+ if (!url) {
28
+ throw new Error(
29
+ "Usage: libretto-cli open <url> [--headless] [--session <name>]"
30
+ );
31
+ }
32
+ await runOpen(url, headed, String(argv.session), logger);
33
+ }
34
+ ).command(
35
+ "save [urlOrDomain]",
36
+ "Save current browser session",
37
+ (cmd) => cmd,
38
+ async (argv) => {
39
+ const urlOrDomain = argv.urlOrDomain;
40
+ if (!urlOrDomain) {
41
+ throw new Error("Usage: libretto-cli save <url|domain> [--session <name>]");
42
+ }
43
+ await runSave(urlOrDomain, String(argv.session), logger);
44
+ }
45
+ ).command("pages", "List open pages in the session", (cmd) => cmd, async (argv) => {
46
+ await runPages(String(argv.session), logger);
47
+ }).command("close", "Close the browser", (cmd) => cmd, async (argv) => {
48
+ await runCloseWithLogger(String(argv.session), logger);
49
+ });
50
+ }
51
+ async function runClose(session) {
52
+ await withSessionLogger(session, async (logger) => {
53
+ await runCloseWithLogger(session, logger);
54
+ });
55
+ }
56
+ export {
57
+ registerBrowserCommands,
58
+ runClose
59
+ };