libretto 0.5.3-experimental.5 → 0.5.3
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 +114 -37
- package/README.template.md +160 -0
- package/dist/cli/cli.js +22 -97
- package/dist/cli/commands/browser.js +86 -59
- package/dist/cli/commands/deploy.js +148 -0
- package/dist/cli/commands/execution.js +218 -96
- package/dist/cli/commands/init.js +34 -29
- package/dist/cli/commands/logs.js +4 -5
- package/dist/cli/commands/shared.js +30 -29
- package/dist/cli/commands/snapshot.js +26 -39
- package/dist/cli/core/ai-config.js +21 -4
- package/dist/cli/core/api-snapshot-analyzer.js +15 -5
- package/dist/cli/core/browser.js +207 -37
- package/dist/cli/core/context.js +4 -1
- package/dist/cli/core/deploy-artifact.js +687 -0
- package/dist/cli/core/session-telemetry.js +434 -174
- package/dist/cli/core/session.js +21 -8
- package/dist/cli/core/snapshot-analyzer.js +14 -31
- package/dist/cli/core/snapshot-api-config.js +2 -6
- package/dist/cli/core/telemetry.js +20 -4
- package/dist/cli/framework/simple-cli.js +144 -43
- package/dist/cli/router.js +16 -21
- package/dist/cli/workers/run-integration-runtime.js +25 -45
- package/dist/cli/workers/run-integration-worker-protocol.js +3 -2
- package/dist/cli/workers/run-integration-worker.js +1 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.js +13 -10
- package/dist/runtime/download/download.js +5 -1
- package/dist/runtime/extract/extract.js +11 -2
- package/dist/runtime/network/network.js +8 -1
- package/dist/runtime/recovery/agent.js +6 -2
- package/dist/runtime/recovery/errors.js +3 -1
- package/dist/runtime/recovery/recovery.js +3 -1
- package/dist/shared/condense-dom/condense-dom.js +17 -69
- package/dist/shared/config/config.d.ts +1 -9
- package/dist/shared/config/config.js +0 -18
- package/dist/shared/config/index.d.ts +2 -1
- package/dist/shared/config/index.js +0 -10
- package/dist/shared/debug/pause.js +9 -3
- package/dist/shared/dom-semantics.d.ts +8 -0
- package/dist/shared/dom-semantics.js +69 -0
- package/dist/shared/instrumentation/instrument.js +101 -5
- package/dist/shared/llm/ai-sdk-adapter.js +3 -1
- package/dist/shared/llm/client.js +3 -1
- package/dist/shared/logger/index.js +4 -1
- package/dist/shared/run/api.js +3 -1
- package/dist/shared/run/browser.js +47 -3
- package/dist/shared/state/session-state.d.ts +2 -1
- package/dist/shared/state/session-state.js +5 -2
- package/dist/shared/visualization/ghost-cursor.js +36 -14
- package/dist/shared/visualization/highlight.js +9 -6
- package/dist/shared/workflow/workflow.d.ts +18 -10
- package/dist/shared/workflow/workflow.js +50 -5
- package/package.json +14 -6
- package/scripts/generate-changelog.ts +132 -0
- package/scripts/postinstall.mjs +4 -3
- package/scripts/skills-libretto.mjs +2 -88
- package/scripts/summarize-evals.mjs +32 -10
- package/skills/libretto/SKILL.md +132 -62
- package/skills/libretto/references/action-logs.md +101 -0
- package/skills/libretto/references/auth-profiles.md +1 -2
- package/skills/libretto/references/code-generation-rules.md +176 -0
- package/skills/libretto/references/configuration-file-reference.md +53 -0
- package/skills/libretto/references/pages-and-page-targeting.md +1 -1
- package/skills/libretto/references/site-security-review.md +143 -0
- package/src/cli/cli.ts +23 -110
- package/src/cli/commands/browser.ts +94 -70
- package/src/cli/commands/deploy.ts +198 -0
- package/src/cli/commands/execution.ts +251 -111
- package/src/cli/commands/init.ts +37 -33
- package/src/cli/commands/logs.ts +7 -7
- package/src/cli/commands/shared.ts +36 -37
- package/src/cli/commands/snapshot.ts +44 -59
- package/src/cli/core/ai-config.ts +24 -4
- package/src/cli/core/api-snapshot-analyzer.ts +17 -6
- package/src/cli/core/browser.ts +260 -49
- package/src/cli/core/context.ts +7 -2
- package/src/cli/core/deploy-artifact.ts +938 -0
- package/src/cli/core/session-telemetry.ts +449 -197
- package/src/cli/core/session.ts +21 -7
- package/src/cli/core/snapshot-analyzer.ts +26 -46
- package/src/cli/core/snapshot-api-config.ts +170 -175
- package/src/cli/core/telemetry.ts +39 -4
- package/src/cli/framework/simple-cli.ts +281 -98
- package/src/cli/router.ts +15 -21
- package/src/cli/workers/run-integration-runtime.ts +35 -57
- package/src/cli/workers/run-integration-worker-protocol.ts +2 -1
- package/src/cli/workers/run-integration-worker.ts +1 -4
- package/src/index.ts +77 -67
- package/src/runtime/download/download.ts +62 -58
- package/src/runtime/download/index.ts +5 -5
- package/src/runtime/extract/extract.ts +71 -61
- package/src/runtime/network/index.ts +3 -3
- package/src/runtime/network/network.ts +99 -93
- package/src/runtime/recovery/agent.ts +217 -212
- package/src/runtime/recovery/errors.ts +107 -104
- package/src/runtime/recovery/index.ts +3 -3
- package/src/runtime/recovery/recovery.ts +38 -35
- package/src/shared/condense-dom/condense-dom.ts +27 -82
- package/src/shared/config/config.ts +0 -19
- package/src/shared/config/index.ts +0 -5
- package/src/shared/debug/pause.ts +57 -51
- package/src/shared/dom-semantics.ts +68 -0
- package/src/shared/instrumentation/errors.ts +64 -62
- package/src/shared/instrumentation/index.ts +5 -5
- package/src/shared/instrumentation/instrument.ts +339 -209
- package/src/shared/llm/ai-sdk-adapter.ts +58 -55
- package/src/shared/llm/client.ts +181 -174
- package/src/shared/llm/types.ts +39 -39
- package/src/shared/logger/index.ts +11 -4
- package/src/shared/logger/logger.ts +312 -306
- package/src/shared/logger/sinks.ts +118 -114
- package/src/shared/paths/paths.ts +50 -49
- package/src/shared/paths/repo-root.ts +17 -17
- package/src/shared/run/api.ts +5 -1
- package/src/shared/run/browser.ts +65 -3
- package/src/shared/state/index.ts +9 -9
- package/src/shared/state/session-state.ts +46 -43
- package/src/shared/visualization/ghost-cursor.ts +180 -149
- package/src/shared/visualization/highlight.ts +89 -86
- package/src/shared/visualization/index.ts +13 -13
- package/src/shared/workflow/workflow.ts +107 -30
- package/scripts/check-skills-sync.mjs +0 -23
- package/scripts/prepare-release.sh +0 -97
- package/skills/libretto/references/reverse-engineering-network-requests.md +0 -75
- package/skills/libretto/references/user-action-log.md +0 -31
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import type { Page } from "playwright";
|
|
2
2
|
import type z from "zod";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type MinimalLogger,
|
|
5
|
+
defaultLogger,
|
|
6
|
+
} from "../../shared/logger/logger.js";
|
|
4
7
|
import type { LLMClient } from "../../shared/llm/types.js";
|
|
5
8
|
|
|
6
9
|
export type ExtractOptions<T extends z.ZodType> = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
page: Page;
|
|
11
|
+
instruction: string;
|
|
12
|
+
schema: T;
|
|
13
|
+
llmClient: LLMClient;
|
|
14
|
+
logger?: MinimalLogger;
|
|
15
|
+
/** Optional CSS selector to scope extraction to a specific element. */
|
|
16
|
+
selector?: string;
|
|
14
17
|
};
|
|
15
18
|
|
|
16
19
|
/**
|
|
@@ -20,48 +23,55 @@ export type ExtractOptions<T extends z.ZodType> = {
|
|
|
20
23
|
* matching the provided Zod schema.
|
|
21
24
|
*/
|
|
22
25
|
export async function extractFromPage<T extends z.ZodType>(
|
|
23
|
-
|
|
26
|
+
options: ExtractOptions<T>,
|
|
24
27
|
): Promise<z.infer<T>> {
|
|
25
|
-
|
|
28
|
+
const {
|
|
29
|
+
page,
|
|
30
|
+
instruction,
|
|
31
|
+
schema,
|
|
32
|
+
selector,
|
|
33
|
+
logger = defaultLogger,
|
|
34
|
+
llmClient,
|
|
35
|
+
} = options;
|
|
26
36
|
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
let screenshot: string;
|
|
38
|
+
let domContent: string | undefined;
|
|
29
39
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
40
|
+
if (selector) {
|
|
41
|
+
const element = page.locator(selector);
|
|
42
|
+
await element.waitFor({ state: "visible", timeout: 10_000 });
|
|
33
43
|
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
const screenshotBuffer = await element.screenshot();
|
|
45
|
+
screenshot = screenshotBuffer.toString("base64");
|
|
36
46
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
try {
|
|
48
|
+
domContent = await element.innerHTML();
|
|
49
|
+
if (domContent.length > 30000) {
|
|
50
|
+
domContent = domContent.slice(0, 30000) + "\n... [truncated]";
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
domContent = undefined;
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
const cdpClient = await page.context().newCDPSession(page);
|
|
57
|
+
await cdpClient.send("Page.enable");
|
|
58
|
+
const { data } = await cdpClient.send("Page.captureScreenshot", {
|
|
59
|
+
format: "png",
|
|
60
|
+
});
|
|
61
|
+
screenshot = data;
|
|
52
62
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
try {
|
|
64
|
+
const htmlContent = await page.content();
|
|
65
|
+
domContent =
|
|
66
|
+
htmlContent.length > 50000
|
|
67
|
+
? htmlContent.slice(0, 50000) + "\n... [truncated]"
|
|
68
|
+
: htmlContent;
|
|
69
|
+
} catch {
|
|
70
|
+
domContent = undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
63
73
|
|
|
64
|
-
|
|
74
|
+
const prompt = `You are analyzing a screenshot${selector ? " of a specific element" : ""} from a web page to extract structured data.
|
|
65
75
|
|
|
66
76
|
Instruction: ${instruction}
|
|
67
77
|
|
|
@@ -69,24 +79,24 @@ ${domContent ? `Here is the HTML content for additional context:\n<html>\n${domC
|
|
|
69
79
|
|
|
70
80
|
Extract the requested information from the screenshot and return it in the specified format. Be precise and only extract what is visible.`;
|
|
71
81
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
const result = await llmClient.generateObjectFromMessages({
|
|
83
|
+
schema,
|
|
84
|
+
messages: [
|
|
85
|
+
{
|
|
86
|
+
role: "user",
|
|
87
|
+
content: [
|
|
88
|
+
{ type: "text", text: prompt },
|
|
89
|
+
{ type: "image", image: `data:image/png;base64,${screenshot}` },
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
temperature: 0,
|
|
94
|
+
});
|
|
85
95
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
96
|
+
logger.info("extractFromPage completed", {
|
|
97
|
+
selector,
|
|
98
|
+
instruction: instruction.slice(0, 100),
|
|
99
|
+
});
|
|
90
100
|
|
|
91
|
-
|
|
101
|
+
return result;
|
|
92
102
|
}
|
|
@@ -3,25 +3,25 @@ import type z from "zod";
|
|
|
3
3
|
import type { MinimalLogger } from "../../shared/logger/logger.js";
|
|
4
4
|
|
|
5
5
|
export type RequestConfig = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
url: string;
|
|
7
|
+
method?: "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
body?: Record<string, any> | string;
|
|
10
|
+
/** How to serialize the body. Defaults to "json". */
|
|
11
|
+
bodyType?: "json" | "form";
|
|
12
|
+
/** How to parse the response. Defaults to "json". */
|
|
13
|
+
responseType?: "json" | "text" | "xml";
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
export type PageRequestOptions<T extends z.ZodType | undefined = undefined> = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
logger?: MinimalLogger;
|
|
18
|
+
/** Optional Zod schema to validate the response body. */
|
|
19
|
+
schema?: T;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
22
|
type PageRequestResult<T extends z.ZodType | undefined> = T extends z.ZodType
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
? z.infer<T>
|
|
24
|
+
: any;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Executes a fetch() call inside the browser context via page.evaluate().
|
|
@@ -29,85 +29,91 @@ type PageRequestResult<T extends z.ZodType | undefined> = T extends z.ZodType
|
|
|
29
29
|
* validation, and logging.
|
|
30
30
|
*/
|
|
31
31
|
export async function pageRequest<T extends z.ZodType | undefined = undefined>(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
page: Page,
|
|
33
|
+
config: RequestConfig,
|
|
34
|
+
options?: PageRequestOptions<T>,
|
|
35
35
|
): Promise<PageRequestResult<T>> {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
36
|
+
const {
|
|
37
|
+
url,
|
|
38
|
+
method = "GET",
|
|
39
|
+
headers = {},
|
|
40
|
+
body,
|
|
41
|
+
bodyType = "json",
|
|
42
|
+
responseType = "json",
|
|
43
|
+
} = config;
|
|
44
|
+
const { logger, schema } = options ?? {};
|
|
45
|
+
|
|
46
|
+
const startTime = Date.now();
|
|
47
|
+
|
|
48
|
+
// Build fetch options to pass into page.evaluate
|
|
49
|
+
const fetchHeaders: Record<string, string> = { ...headers };
|
|
50
|
+
let fetchBody: string | undefined;
|
|
51
|
+
|
|
52
|
+
if (body !== undefined) {
|
|
53
|
+
if (bodyType === "form") {
|
|
54
|
+
fetchHeaders["Content-Type"] = "application/x-www-form-urlencoded";
|
|
55
|
+
if (typeof body === "string") {
|
|
56
|
+
fetchBody = body;
|
|
57
|
+
} else {
|
|
58
|
+
fetchBody = new URLSearchParams(
|
|
59
|
+
Object.entries(body).map(([k, v]) => [k, String(v)]),
|
|
60
|
+
).toString();
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
fetchHeaders["Content-Type"] = "application/json";
|
|
64
|
+
fetchBody = typeof body === "string" ? body : JSON.stringify(body);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const result = await page.evaluate(
|
|
69
|
+
async ({ url, method, headers, body, responseType }) => {
|
|
70
|
+
const res = await fetch(url, {
|
|
71
|
+
method,
|
|
72
|
+
headers,
|
|
73
|
+
body: body ?? undefined,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const status = res.status;
|
|
77
|
+
const ok = res.ok;
|
|
78
|
+
let data: any;
|
|
79
|
+
|
|
80
|
+
if (responseType === "json") {
|
|
81
|
+
data = await res.json();
|
|
82
|
+
} else {
|
|
83
|
+
data = await res.text();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return { status, ok, data };
|
|
87
|
+
},
|
|
88
|
+
{ url, method, headers: fetchHeaders, body: fetchBody, responseType },
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const duration = Date.now() - startTime;
|
|
92
|
+
|
|
93
|
+
if (!result.ok) {
|
|
94
|
+
logger?.warn("network:request:error", {
|
|
95
|
+
method,
|
|
96
|
+
url,
|
|
97
|
+
status: result.status,
|
|
98
|
+
duration,
|
|
99
|
+
body:
|
|
100
|
+
typeof result.data === "string" ? result.data.slice(0, 500) : undefined,
|
|
101
|
+
});
|
|
102
|
+
throw new Error(
|
|
103
|
+
`pageRequest failed: ${method} ${url} returned ${result.status}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
logger?.info("network:request", {
|
|
108
|
+
method,
|
|
109
|
+
url,
|
|
110
|
+
status: result.status,
|
|
111
|
+
duration,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (schema) {
|
|
115
|
+
return schema.parse(result.data) as PageRequestResult<T>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return result.data as PageRequestResult<T>;
|
|
113
119
|
}
|