libretto 0.2.5 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,17 +9,21 @@ pnpm add libretto playwright zod
9
9
  npx libretto init
10
10
  ```
11
11
 
12
- > **pnpm users:** add `onlyBuiltDependencies` to your `package.json` to allow
13
- > Playwright's postinstall script to run:
12
+ > **pnpm users:** if your workspace uses `onlyBuiltDependencies`, add both
13
+ > `libretto` and `playwright` to allow their postinstall scripts to run
14
+ > (libretto's postinstall copies skill files and installs Playwright Chromium):
14
15
  >
15
16
  > ```jsonc
16
17
  > // package.json
17
18
  > {
18
19
  > "pnpm": {
19
- > "onlyBuiltDependencies": ["playwright"]
20
+ > "onlyBuiltDependencies": ["libretto", "playwright"]
20
21
  > }
21
22
  > }
22
23
  > ```
24
+ >
25
+ > If the postinstall was skipped (e.g., `libretto` wasn't in the allowlist),
26
+ > run `npx libretto init` manually after install to complete setup.
23
27
 
24
28
  ## Quick Start
25
29
 
@@ -119,6 +123,33 @@ Run `npx libretto help` for the full list.
119
123
  | `libretto/run` | `launchBrowser` |
120
124
  | `libretto/state` | Session state serialization and parsing |
121
125
 
126
+ ## Using Recovery Helpers
127
+
128
+ The recovery module (`libretto/recovery`) provides `detectSubmissionError` and
129
+ `executeRecoveryAgent` for handling form submission errors. Both accept an
130
+ `LLMClient` — create one with `createLLMClientFromModel` and pass it directly:
131
+
132
+ ```typescript
133
+ import { detectSubmissionError, executeRecoveryAgent } from "libretto/recovery";
134
+ import { createLLMClientFromModel } from "libretto/llm";
135
+ import { openai } from "@ai-sdk/openai";
136
+
137
+ const llmClient = createLLMClientFromModel(openai("gpt-4o"));
138
+
139
+ // Detect if a submission produced an error
140
+ const error = await detectSubmissionError(
141
+ page, submissionError, "eligibility check failed", llmClient, knownErrors, logger,
142
+ );
143
+
144
+ // Or run the full recovery agent to retry with corrections
145
+ const result = await executeRecoveryAgent(
146
+ page, error, llmClient, recoveryOptions, logger,
147
+ );
148
+ ```
149
+
150
+ No need to write custom wrappers — `createLLMClientFromModel` bridges any
151
+ Vercel AI SDK provider into the `LLMClient` interface that recovery helpers expect.
152
+
122
153
  ## Links
123
154
 
124
155
  - [GitHub](https://github.com/saffron-health/libretto)
@@ -168,8 +168,27 @@ class Logger {
168
168
  options
169
169
  });
170
170
  }
171
- info(event, data, options) {
172
- this.entry({ level: "info", event, data, options });
171
+ info(event, dataOrError, options) {
172
+ const data = dataOrError instanceof Error ? {
173
+ error: {
174
+ type: dataOrError.constructor.name,
175
+ message: dataOrError.message,
176
+ stack: dataOrError.stack || null
177
+ }
178
+ } : isObject(dataOrError) && dataOrError.error instanceof Error ? {
179
+ ...dataOrError,
180
+ error: {
181
+ type: dataOrError.error.constructor.name,
182
+ message: dataOrError.error.message,
183
+ stack: dataOrError.error.stack || null
184
+ }
185
+ } : isObject(dataOrError) ? dataOrError : dataOrError !== void 0 ? { error: dataOrError } : void 0;
186
+ this.entry({
187
+ level: "info",
188
+ event,
189
+ data,
190
+ options
191
+ });
173
192
  }
174
193
  withScope(scope, context = {}) {
175
194
  return new Logger([...this.scopes, scope], this.sinks, {
@@ -7,7 +7,7 @@ type LogOptions = {
7
7
  * implement withScope, withContext, flush, etc.
8
8
  */
9
9
  type MinimalLogger = {
10
- info: (event: string, data?: Record<string, any>) => void;
10
+ info: (event: string, data?: any) => void;
11
11
  warn: (event: string, data?: any) => void;
12
12
  error: (event: string, data?: any) => any;
13
13
  };
@@ -26,7 +26,9 @@ type LoggerApi = {
26
26
  warn: (event: string, data?: Error | ({
27
27
  error: Error;
28
28
  } & Record<string, any>) | unknown, options?: LogOptions) => void;
29
- info: (event: string, data?: Record<string, any>, options?: LogOptions) => void;
29
+ info: (event: string, data?: Error | ({
30
+ error: Error;
31
+ } & Record<string, any>) | unknown, options?: LogOptions) => void;
30
32
  /**
31
33
  * Context passed in will be attached to all entries in this scope.
32
34
  */
@@ -71,7 +73,9 @@ declare class Logger implements LoggerApi {
71
73
  warn(event: string, dataOrError?: Error | ({
72
74
  error: Error;
73
75
  } & Record<string, any>) | unknown, options?: LogOptions): void;
74
- info(event: string, data?: Record<string, any>, options?: LogOptions): void;
76
+ info(event: string, dataOrError?: Error | ({
77
+ error: Error;
78
+ } & Record<string, any>) | unknown, options?: LogOptions): void;
75
79
  withScope(scope: string, context?: Record<string, any>): LoggerApi;
76
80
  withContext(context: Record<string, any>): LoggerApi;
77
81
  withSink(sink: LoggerSink): Logger;
@@ -7,7 +7,7 @@ type LogOptions = {
7
7
  * implement withScope, withContext, flush, etc.
8
8
  */
9
9
  type MinimalLogger = {
10
- info: (event: string, data?: Record<string, any>) => void;
10
+ info: (event: string, data?: any) => void;
11
11
  warn: (event: string, data?: any) => void;
12
12
  error: (event: string, data?: any) => any;
13
13
  };
@@ -26,7 +26,9 @@ type LoggerApi = {
26
26
  warn: (event: string, data?: Error | ({
27
27
  error: Error;
28
28
  } & Record<string, any>) | unknown, options?: LogOptions) => void;
29
- info: (event: string, data?: Record<string, any>, options?: LogOptions) => void;
29
+ info: (event: string, data?: Error | ({
30
+ error: Error;
31
+ } & Record<string, any>) | unknown, options?: LogOptions) => void;
30
32
  /**
31
33
  * Context passed in will be attached to all entries in this scope.
32
34
  */
@@ -71,7 +73,9 @@ declare class Logger implements LoggerApi {
71
73
  warn(event: string, dataOrError?: Error | ({
72
74
  error: Error;
73
75
  } & Record<string, any>) | unknown, options?: LogOptions): void;
74
- info(event: string, data?: Record<string, any>, options?: LogOptions): void;
76
+ info(event: string, dataOrError?: Error | ({
77
+ error: Error;
78
+ } & Record<string, any>) | unknown, options?: LogOptions): void;
75
79
  withScope(scope: string, context?: Record<string, any>): LoggerApi;
76
80
  withContext(context: Record<string, any>): LoggerApi;
77
81
  withSink(sink: LoggerSink): Logger;
@@ -144,8 +144,27 @@ class Logger {
144
144
  options
145
145
  });
146
146
  }
147
- info(event, data, options) {
148
- this.entry({ level: "info", event, data, options });
147
+ info(event, dataOrError, options) {
148
+ const data = dataOrError instanceof Error ? {
149
+ error: {
150
+ type: dataOrError.constructor.name,
151
+ message: dataOrError.message,
152
+ stack: dataOrError.stack || null
153
+ }
154
+ } : isObject(dataOrError) && dataOrError.error instanceof Error ? {
155
+ ...dataOrError,
156
+ error: {
157
+ type: dataOrError.error.constructor.name,
158
+ message: dataOrError.error.message,
159
+ stack: dataOrError.error.stack || null
160
+ }
161
+ } : isObject(dataOrError) ? dataOrError : dataOrError !== void 0 ? { error: dataOrError } : void 0;
162
+ this.entry({
163
+ level: "info",
164
+ event,
165
+ data,
166
+ options
167
+ });
149
168
  }
150
169
  withScope(scope, context = {}) {
151
170
  return new Logger([...this.scopes, scope], this.sinks, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libretto",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "AI-powered browser automation library and CLI built on Playwright",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -43,6 +43,42 @@ export const myWorkflow = workflow<Input, Output>(
43
43
  - Use `await pause()` (imported from `"libretto"`) to pause the workflow for debugging. It is a no-op in production.
44
44
  - The browser is launched and closed automatically by the CLI — do not launch or close it in the handler
45
45
 
46
+ ## Passing Application Dependencies via Services
47
+
48
+ Use the third generic on `workflow<Input, Output, Services>` to inject
49
+ dependencies that exist in your application but not in libretto's runtime
50
+ (DB transactions, API clients, caches, etc.):
51
+
52
+ ```typescript
53
+ import { type Transaction } from "./db";
54
+
55
+ type MyServices = { tx?: Transaction };
56
+
57
+ export const myWorkflow = workflow<Input, Output, MyServices>(
58
+ {},
59
+ async (ctx, input) => {
60
+ if (ctx.services.tx) {
61
+ await ctx.services.tx.insert(/* ... */);
62
+ } else {
63
+ ctx.logger.info("No DB transaction — skipping write");
64
+ }
65
+ // ... browser automation ...
66
+ },
67
+ );
68
+ ```
69
+
70
+ In production, the caller passes services when invoking `.run()`:
71
+
72
+ ```typescript
73
+ await myWorkflow.run(
74
+ { page, logger, services: { tx } },
75
+ input,
76
+ );
77
+ ```
78
+
79
+ When running standalone via `npx libretto run`, services defaults to `{}`,
80
+ so mark fields optional for anything unavailable in that context.
81
+
46
82
  ## Playwright Locators for DOM Interaction
47
83
 
48
84
  Generated code must use Playwright locator APIs for all DOM interactions. Do not use `page.evaluate()` with `document.querySelector`, `querySelectorAll`, `textContent`, `click()`, or other DOM APIs when a Playwright locator can do the same thing.