libretto 0.2.3 → 0.2.5

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 (42) hide show
  1. package/README.md +78 -184
  2. package/dist/cli/cli.js +8 -2
  3. package/dist/cli/commands/browser.js +26 -3
  4. package/dist/cli/commands/execution.js +50 -11
  5. package/dist/cli/commands/init.js +95 -0
  6. package/dist/cli/core/browser.js +131 -6
  7. package/dist/cli/core/context.js +5 -0
  8. package/dist/cli/core/session.js +13 -13
  9. package/dist/cli/workers/run-integration-runtime.js +64 -59
  10. package/dist/cli/workers/run-integration-worker-protocol.js +12 -0
  11. package/dist/cli/workers/run-integration-worker.js +13 -30
  12. package/dist/index.cjs +5 -12
  13. package/dist/index.d.cts +4 -2
  14. package/dist/index.d.ts +4 -2
  15. package/dist/index.js +5 -15
  16. package/dist/shared/debug/index.cjs +4 -6
  17. package/dist/shared/debug/index.d.cts +1 -2
  18. package/dist/shared/debug/index.d.ts +1 -2
  19. package/dist/shared/debug/index.js +3 -8
  20. package/dist/shared/debug/pause.cjs +58 -24
  21. package/dist/shared/debug/pause.d.cts +13 -20
  22. package/dist/shared/debug/pause.d.ts +13 -20
  23. package/dist/shared/debug/pause.js +46 -21
  24. package/dist/shared/llm/ai-sdk-adapter.cjs +67 -0
  25. package/dist/shared/llm/ai-sdk-adapter.d.cts +22 -0
  26. package/dist/shared/llm/ai-sdk-adapter.d.ts +22 -0
  27. package/dist/shared/llm/ai-sdk-adapter.js +43 -0
  28. package/dist/shared/llm/index.cjs +5 -2
  29. package/dist/shared/llm/index.d.cts +2 -0
  30. package/dist/shared/llm/index.d.ts +2 -0
  31. package/dist/shared/llm/index.js +3 -1
  32. package/dist/shared/llm/types.d.cts +32 -0
  33. package/dist/shared/llm/types.d.ts +32 -0
  34. package/dist/shared/run/api.cjs +0 -7
  35. package/dist/shared/run/api.d.cts +0 -1
  36. package/dist/shared/run/api.d.ts +0 -1
  37. package/dist/shared/run/api.js +0 -8
  38. package/dist/shared/workflow/workflow.d.cts +11 -24
  39. package/dist/shared/workflow/workflow.d.ts +11 -24
  40. package/package.json +4 -10
  41. package/skill/SKILL.md +18 -5
  42. package/skill/code-generation-rules.md +7 -10
package/README.md CHANGED
@@ -1,231 +1,125 @@
1
1
  # Libretto
2
2
 
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
3
+ AI-powered browser automation library and CLI built on Playwright.
15
4
 
16
5
  ## Installation
17
6
 
18
7
  ```bash
19
8
  pnpm add libretto playwright zod
9
+ npx libretto init
20
10
  ```
21
11
 
22
- `playwright` and `zod` are peer dependencies.
12
+ > **pnpm users:** add `onlyBuiltDependencies` to your `package.json` to allow
13
+ > Playwright's postinstall script to run:
14
+ >
15
+ > ```jsonc
16
+ > // package.json
17
+ > {
18
+ > "pnpm": {
19
+ > "onlyBuiltDependencies": ["playwright"]
20
+ > }
21
+ > }
22
+ > ```
23
23
 
24
24
  ## Quick Start
25
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
26
+ ### 1. Configure your LLM
44
27
 
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.
28
+ The easiest way is to use the built-in Vercel AI SDK adapter with any compatible provider:
50
29
 
51
30
  ```typescript
52
- import { attemptWithRecovery } from "libretto";
31
+ import { createLLMClientFromModel } from "libretto/llm";
32
+ import { openai } from "@ai-sdk/openai";
53
33
 
54
- await attemptWithRecovery(page, async () => {
55
- await page.click('button[type="submit"]');
56
- }, undefined, llmClient);
34
+ const llmClient = createLLMClientFromModel(openai("gpt-4o"));
57
35
  ```
58
36
 
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.
37
+ Or use any other provider:
62
38
 
63
39
  ```typescript
64
- import { executeRecoveryAgent } from "libretto";
65
-
66
- await executeRecoveryAgent(
67
- page,
68
- "Close the cookie consent banner",
69
- undefined,
70
- llmClient,
71
- );
72
- ```
40
+ import { createLLMClientFromModel } from "libretto";
41
+ import { anthropic } from "@ai-sdk/anthropic";
73
42
 
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
- }
43
+ const llmClient = createLLMClientFromModel(anthropic("claude-sonnet-4-20250514"));
89
44
  ```
90
45
 
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");
147
- ```
148
-
149
- #### `downloadAndSave(page, selector, options?)`
150
-
151
- Same as `downloadViaClick` but also writes the file to disk.
152
-
153
- ```typescript
154
- import { downloadAndSave } from "libretto";
155
-
156
- const { savedTo } = await downloadAndSave(page, "#export-csv", {
157
- savePath: "./exports/report.csv",
158
- });
159
- ```
160
-
161
- ## LLM Client Interface
162
-
163
- Provide your own implementation backed by any LLM provider:
46
+ You can also implement the `LLMClient` interface directly for full control:
164
47
 
165
48
  ```typescript
166
49
  import type { LLMClient } from "libretto";
167
50
 
168
- const myLLMClient: LLMClient = {
51
+ const llmClient: LLMClient = {
169
52
  async generateObject({ prompt, schema, temperature }) {
170
53
  // Call your LLM, return parsed + validated result
171
54
  },
172
55
  async generateObjectFromMessages({ messages, schema, temperature }) {
173
- // Call your LLM with message history, return parsed + validated result
56
+ // Call your LLM with message history (may include images)
174
57
  },
175
58
  };
176
59
  ```
177
60
 
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:
61
+ ### 2. Write a workflow
183
62
 
184
63
  ```typescript
185
- import { Logger, createFileLogSink, prettyConsoleSink } from "libretto";
64
+ import { workflow } from "libretto";
65
+ import { z } from "zod";
186
66
 
187
- const logger = new Logger()
188
- .withSink(createFileLogSink({ filePath: "./app.log" }))
189
- .withSink(prettyConsoleSink);
67
+ export default workflow({
68
+ name: "extract-product",
69
+ schema: z.object({ url: z.string() }),
70
+ handler: async (ctx) => {
71
+ const page = ctx.page;
72
+ await page.goto(ctx.params.url);
190
73
 
191
- const scoped = logger.withScope("auth");
192
- scoped.info("login attempt", { user: "alice" });
193
- scoped.error("login failed", { reason: "bad password" });
194
- ```
74
+ const data = await ctx.extract({
75
+ instruction: "Extract the product name and price",
76
+ schema: z.object({ name: z.string(), price: z.number() }),
77
+ });
195
78
 
196
- ## Module Exports
79
+ return data;
80
+ },
81
+ });
82
+ ```
197
83
 
198
- Libretto provides granular imports:
84
+ ### 3. Run it
199
85
 
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 |
86
+ ```bash
87
+ npx libretto run ./workflows/extract-product.ts extractProduct \
88
+ --params '{"url": "https://example.com/product"}'
89
+ ```
215
90
 
216
- ## Configuration
91
+ ## CLI Commands
217
92
 
218
- Runtime flags via environment variables:
93
+ ```
94
+ npx libretto init # Copy skills, install Playwright Chromium
95
+ npx libretto open <url> # Launch browser and open URL
96
+ npx libretto run <file> <export> # Run a workflow
97
+ npx libretto ai configure <preset> # Configure AI runtime (codex, claude, gemini)
98
+ npx libretto snapshot # Capture page screenshot + HTML
99
+ npx libretto exec <code> # Execute Playwright code
100
+ ```
219
101
 
220
- | Env Variable | Effect |
221
- | --------------------- | --------------------------------------------------- |
222
- | `LIBRETTO_DEBUG` | Enable debug mode |
223
- | `LIBRETTO_DRY_RUN` | Enable dry-run mode (defaults to `true` in development) |
102
+ Run `npx libretto help` for the full list.
224
103
 
225
- ## Development
104
+ ## Module Exports
226
105
 
227
- ```bash
228
- pnpm install
229
- pnpm build # compile to dist/
230
- pnpm type-check # typecheck without emitting
231
- ```
106
+ | Import | Contents |
107
+ | -------------------------- | ------------------------------------------------------------- |
108
+ | `libretto` | Everything |
109
+ | `libretto/llm` | `LLMClient` type, `createLLMClient`, `createLLMClientFromModel` |
110
+ | `libretto/recovery` | `attemptWithRecovery`, `executeRecoveryAgent`, `detectSubmissionError` |
111
+ | `libretto/extract` | `extractFromPage` |
112
+ | `libretto/network` | `pageRequest` |
113
+ | `libretto/download` | `downloadViaClick`, `downloadAndSave` |
114
+ | `libretto/logger` | `Logger`, `defaultLogger`, sinks |
115
+ | `libretto/debug` | `debugPause` |
116
+ | `libretto/config` | `isDryRun`, `isDebugMode`, `shouldPauseBeforeMutation` |
117
+ | `libretto/instrumentation` | `instrumentPage`, `installInstrumentation` |
118
+ | `libretto/visualization` | Ghost cursor and highlight helpers |
119
+ | `libretto/run` | `launchBrowser` |
120
+ | `libretto/state` | Session state serialization and parsing |
121
+
122
+ ## Links
123
+
124
+ - [GitHub](https://github.com/saffron-health/libretto)
125
+ - [Issues](https://github.com/saffron-health/libretto/issues)
package/dist/cli/cli.js CHANGED
@@ -4,6 +4,7 @@ import { registerAICommands } from "./commands/ai.js";
4
4
  import { registerBrowserCommands } from "./commands/browser.js";
5
5
  import { registerExecutionCommands } from "./commands/execution.js";
6
6
  import { registerLogCommands } from "./commands/logs.js";
7
+ import { registerInitCommand } from "./commands/init.js";
7
8
  import { registerSnapshotCommands } from "./commands/snapshot.js";
8
9
  import {
9
10
  closeLogger,
@@ -26,6 +27,7 @@ const CLI_COMMANDS = /* @__PURE__ */ new Set([
26
27
  "pages",
27
28
  "resume",
28
29
  "close",
30
+ "init",
29
31
  "--help",
30
32
  "-h",
31
33
  "help"
@@ -34,9 +36,10 @@ function printUsage() {
34
36
  console.log(`Usage: libretto-cli <command> [--session <name>]
35
37
 
36
38
  Commands:
39
+ init [--skip-browsers] Initialize libretto (copy skills, install browsers)
37
40
  open <url> [--headless] Launch browser and open URL (headed by default)
38
41
  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)
42
+ run <integrationFile> <integrationExport> [--params <json> | --params-file <path>] [--headed|--headless] Run an exported Libretto workflow from a file
40
43
  ai configure [preset] [-- <command prefix...>] Configure AI runtime for analysis commands
41
44
  save <url|domain> Save current browser session (cookies, localStorage, etc.)
42
45
  exec <code> [--visualize] Execute Playwright typescript code (--visualize enables ghost cursor + highlight)
@@ -45,7 +48,7 @@ Commands:
45
48
  actions [--last N] [--filter regex] [--action TYPE] [--source SOURCE] [--clear] View captured actions
46
49
  pages List open pages in the active session
47
50
  resume Resume a paused workflow in the active session
48
- close Close the browser
51
+ close [--all] [--force] Close the browser for the session, or all tracked sessions with --all
49
52
 
50
53
  Options:
51
54
  --session <name> Use a named session (default: "default")
@@ -68,6 +71,8 @@ Examples:
68
71
  libretto-cli snapshot --objective "Find the submit button" --context "Submitting a referral form, already filled in patient details"
69
72
  libretto-cli resume --session default
70
73
  libretto-cli close
74
+ libretto-cli close --all
75
+ libretto-cli close --all --force
71
76
 
72
77
  # Multiple sessions
73
78
  libretto-cli open https://site1.com --session test1
@@ -160,6 +165,7 @@ function createParser(logger) {
160
165
  parser = registerLogCommands(parser);
161
166
  parser = registerAICommands(parser);
162
167
  parser = registerSnapshotCommands(parser, logger);
168
+ parser = registerInitCommand(parser);
163
169
  parser = parser.command("help", "Show usage", () => {
164
170
  }, () => {
165
171
  printUsage();
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  runClose as runCloseWithLogger,
3
+ runCloseAll as runCloseAllWithLogger,
3
4
  runOpen,
4
5
  runPages,
5
6
  runSave
@@ -44,9 +45,31 @@ function registerBrowserCommands(yargs, logger) {
44
45
  }
45
46
  ).command("pages", "List open pages in the session", (cmd) => cmd, async (argv) => {
46
47
  await runPages(String(argv.session), logger);
47
- }).command("close", "Close the browser", (cmd) => cmd, async (argv) => {
48
- await runCloseWithLogger(String(argv.session), logger);
49
- });
48
+ }).command(
49
+ "close",
50
+ "Close the browser",
51
+ (cmd) => cmd.option("all", {
52
+ type: "boolean",
53
+ default: false,
54
+ describe: "Close all tracked sessions in this workspace"
55
+ }).option("force", {
56
+ type: "boolean",
57
+ default: false,
58
+ describe: "Force kill sessions that ignore SIGTERM (requires --all)"
59
+ }),
60
+ async (argv) => {
61
+ const closeAll = Boolean(argv.all);
62
+ const force = Boolean(argv.force);
63
+ if (force && !closeAll) {
64
+ throw new Error("Usage: libretto-cli close --all [--force]");
65
+ }
66
+ if (closeAll) {
67
+ await runCloseAllWithLogger(logger, { force });
68
+ return;
69
+ }
70
+ await runCloseWithLogger(String(argv.session), logger);
71
+ }
72
+ );
50
73
  }
51
74
  async function runClose(session) {
52
75
  await withSessionLogger(session, async (logger) => {
@@ -1,5 +1,5 @@
1
1
  import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
2
- import { fork } from "node:child_process";
2
+ import { spawn } from "node:child_process";
3
3
  import * as moduleBuiltin from "node:module";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { installInstrumentation } from "../../shared/instrumentation/index.js";
@@ -10,6 +10,8 @@ import {
10
10
  import { getPauseSignalPaths } from "../core/pause-signals.js";
11
11
  import {
12
12
  assertSessionAvailableForStart,
13
+ clearSessionState,
14
+ readSessionState,
13
15
  readSessionStateOrThrow,
14
16
  setSessionStatus
15
17
  } from "../core/session.js";
@@ -170,6 +172,35 @@ function isProcessRunning(pid) {
170
172
  return false;
171
173
  }
172
174
  }
175
+ async function stopExistingFailedRunSession(session, logger) {
176
+ const existingState = readSessionState(session, logger);
177
+ if (!existingState || existingState.status !== "failed") {
178
+ return;
179
+ }
180
+ logger.info("run-release-existing-failed-session", {
181
+ session,
182
+ pid: existingState.pid,
183
+ port: existingState.port
184
+ });
185
+ clearSessionState(session, logger);
186
+ const stopDeadline = Date.now() + 3e3;
187
+ while (isProcessRunning(existingState.pid) && Date.now() < stopDeadline) {
188
+ await new Promise((resolveWait) => setTimeout(resolveWait, 100));
189
+ }
190
+ if (isProcessRunning(existingState.pid)) {
191
+ logger.warn("run-release-existing-failed-session-timeout", {
192
+ session,
193
+ pid: existingState.pid
194
+ });
195
+ console.warn(
196
+ `Existing failed workflow process for session "${session}" (pid ${existingState.pid}) is still shutting down; continuing.`
197
+ );
198
+ return;
199
+ }
200
+ console.log(
201
+ `Closed existing failed workflow process for session "${session}" (pid ${existingState.pid}).`
202
+ );
203
+ }
173
204
  function readJsonFileIfExists(path) {
174
205
  if (!existsSync(path)) return null;
175
206
  try {
@@ -246,7 +277,7 @@ async function runResume(session, logger) {
246
277
  } = getPauseSignalPaths(session);
247
278
  if (!existsSync(pausedSignalPath)) {
248
279
  throw new Error(
249
- `Session "${session}" is not paused. Run "libretto-cli run ... --session ${session}" and call ctx.pause() first.`
280
+ `Session "${session}" is not paused. Run "libretto-cli run ... --session ${session}" and call pause() first.`
250
281
  );
251
282
  }
252
283
  if (!isProcessRunning(state.pid)) {
@@ -297,6 +328,7 @@ async function runResume(session, logger) {
297
328
  console.log("Workflow paused.");
298
329
  }
299
330
  async function runIntegrationFromFile(args, logger) {
331
+ await stopExistingFailedRunSession(args.session, logger);
300
332
  assertSessionAvailableForStart(args.session, logger);
301
333
  const signalPaths = getPauseSignalPaths(args.session);
302
334
  clearSignalIfExists(signalPaths.pausedSignalPath);
@@ -308,12 +340,11 @@ async function runIntegrationFromFile(args, logger) {
308
340
  new URL("../workers/run-integration-worker.js", import.meta.url)
309
341
  );
310
342
  const payload = JSON.stringify(args);
311
- const worker = fork(workerEntryPath, [payload], {
343
+ const worker = spawn(process.execPath, [workerEntryPath, payload], {
312
344
  detached: true,
313
- stdio: ["ignore", "ignore", "ignore", "ipc"],
345
+ stdio: "ignore",
314
346
  env: process.env
315
347
  });
316
- worker.disconnect();
317
348
  worker.unref();
318
349
  const outcome = await waitForWorkflowOutcome({
319
350
  session: args.session,
@@ -326,7 +357,10 @@ async function runIntegrationFromFile(args, logger) {
326
357
  }
327
358
  if (outcome.status === "failed") {
328
359
  setSessionStatus(args.session, "failed", logger);
329
- throw new Error(outcome.message ?? "Workflow failed during run.");
360
+ throw new Error(
361
+ `${outcome.message ?? "Workflow failed during run."}
362
+ Browser is still open. You can use \`exec\` to inspect it. Call \`run\` to re-run the workflow.`
363
+ );
330
364
  }
331
365
  if (outcome.status === "exited") {
332
366
  setSessionStatus(args.session, "exited", logger);
@@ -360,11 +394,17 @@ function registerExecutionCommands(yargs, logger) {
360
394
  ).command(
361
395
  "run [integrationFile] [integrationExport]",
362
396
  "Run an exported Libretto workflow from a file",
363
- (cmd) => cmd.option("params", { type: "string" }).option("params-file", { type: "string" }).option("headed", { type: "boolean", default: false }).option("headless", { type: "boolean", default: false }).option("debug", { type: "boolean" }),
397
+ (cmd) => cmd.option("params", { type: "string" }).option("params-file", { type: "string" }).option("headed", { type: "boolean", default: false }).option("headless", { type: "boolean", default: false }).option("auth-profile", { type: "string", describe: "Domain for local auth profile (e.g. apps.example.com)" }),
364
398
  async (argv) => {
365
- const usage = "Usage: libretto-cli run <integrationFile> <integrationExport> [--params <json> | --params-file <path>] [--headed|--headless] [--debug]";
399
+ const usage = "Usage: libretto-cli run <integrationFile> <integrationExport> [--params <json> | --params-file <path>] [--headed|--headless]";
366
400
  const integrationPath = argv.integrationFile;
367
401
  const exportName = argv.integrationExport;
402
+ const legacyDebug = argv.debug;
403
+ if (legacyDebug !== void 0) {
404
+ throw new Error(
405
+ "The --debug flag has been removed. Run the command without --debug."
406
+ );
407
+ }
368
408
  if (!integrationPath || !exportName) {
369
409
  throw new Error(usage);
370
410
  }
@@ -397,15 +437,14 @@ function registerExecutionCommands(yargs, logger) {
397
437
  throw new Error("Cannot pass both --headed and --headless.");
398
438
  }
399
439
  const headlessMode = hasHeadedFlag ? false : hasHeadlessFlag ? true : void 0;
400
- const debugFlag = argv.debug;
401
- const debugMode = debugFlag !== void 0 ? debugFlag : process.env.LIBRETTO_DEBUG === "true";
440
+ const authProfileDomain = argv["auth-profile"];
402
441
  await runIntegrationFromFile({
403
442
  integrationPath,
404
443
  exportName,
405
444
  session,
406
445
  params,
407
446
  headless: headlessMode ?? false,
408
- debug: debugMode
447
+ authProfileDomain
409
448
  }, logger);
410
449
  }
411
450
  ).command(
@@ -0,0 +1,95 @@
1
+ import { existsSync, mkdirSync, cpSync, readdirSync } from "node:fs";
2
+ import { join, dirname } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { spawnSync } from "node:child_process";
5
+ import { REPO_ROOT } from "../core/context.js";
6
+ function getSkillSourceDir() {
7
+ const thisDir = dirname(fileURLToPath(import.meta.url));
8
+ const pkgRoot = join(thisDir, "..", "..", "..");
9
+ const skillDir = join(pkgRoot, "skill");
10
+ if (existsSync(skillDir)) return skillDir;
11
+ const skillsDir = join(pkgRoot, "skills");
12
+ if (existsSync(skillsDir)) return skillsDir;
13
+ throw new Error(
14
+ "Could not find skill/ or skills/ directory in the libretto package."
15
+ );
16
+ }
17
+ function copySkills() {
18
+ const src = getSkillSourceDir();
19
+ const files = readdirSync(src);
20
+ if (files.length === 0) {
21
+ console.log(" No skill files found to copy.");
22
+ return;
23
+ }
24
+ const targets = [
25
+ join(REPO_ROOT, ".agents", "skills", "libretto"),
26
+ join(REPO_ROOT, ".claude", "skills", "libretto")
27
+ ];
28
+ for (const target of targets) {
29
+ mkdirSync(target, { recursive: true });
30
+ cpSync(src, target, { recursive: true });
31
+ console.log(` \u2713 Copied skill files to ${target}`);
32
+ }
33
+ }
34
+ function installBrowsers() {
35
+ console.log("\nInstalling Playwright Chromium...");
36
+ const result = spawnSync("npx", ["playwright", "install", "chromium"], {
37
+ stdio: "inherit",
38
+ shell: true
39
+ });
40
+ if (result.status === 0) {
41
+ console.log(" \u2713 Playwright Chromium installed");
42
+ } else {
43
+ console.error(
44
+ " \u2717 Failed to install Playwright Chromium. Run manually: npx playwright install chromium"
45
+ );
46
+ }
47
+ }
48
+ function checkSnapshotLLM() {
49
+ const hasAnyCreds = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY;
50
+ console.log("\nSnapshot LLM configuration:");
51
+ if (hasAnyCreds) {
52
+ console.log(" \u2713 LLM credentials detected");
53
+ } else {
54
+ console.log(" \u2717 No LLM credentials found.");
55
+ console.log(" Set one of the following environment variables:");
56
+ console.log(" GOOGLE_CLOUD_PROJECT (for Vertex AI / Gemini)");
57
+ console.log(" ANTHROPIC_API_KEY (for Claude)");
58
+ console.log(" OPENAI_API_KEY (for GPT)");
59
+ console.log(
60
+ " Then configure via: npx libretto ai configure <preset>"
61
+ );
62
+ }
63
+ }
64
+ function registerInitCommand(yargs) {
65
+ return yargs.command(
66
+ "init",
67
+ "Initialize libretto in the current project",
68
+ (cmd) => cmd.option("skip-browsers", {
69
+ type: "boolean",
70
+ default: false,
71
+ describe: "Skip Playwright Chromium installation"
72
+ }),
73
+ (argv) => {
74
+ console.log("Initializing libretto...\n");
75
+ console.log("Copying skill files...");
76
+ try {
77
+ copySkills();
78
+ } catch (err) {
79
+ console.error(
80
+ ` \u2717 ${err instanceof Error ? err.message : String(err)}`
81
+ );
82
+ }
83
+ if (!argv["skip-browsers"]) {
84
+ installBrowsers();
85
+ } else {
86
+ console.log("\nSkipping browser installation (--skip-browsers)");
87
+ }
88
+ checkSnapshotLLM();
89
+ console.log("\n\u2713 libretto init complete");
90
+ }
91
+ );
92
+ }
93
+ export {
94
+ registerInitCommand
95
+ };