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
package/src/cli/router.ts
CHANGED
|
@@ -1,28 +1,22 @@
|
|
|
1
|
-
import type { Logger } from "../shared/logger/index.js";
|
|
2
1
|
import { aiCommands } from "./commands/ai.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { browserCommands } from "./commands/browser.js";
|
|
3
|
+
import { deployCommand } from "./commands/deploy.js";
|
|
4
|
+
import { executionCommands } from "./commands/execution.js";
|
|
5
5
|
import { initCommand } from "./commands/init.js";
|
|
6
6
|
import { logCommands } from "./commands/logs.js";
|
|
7
|
-
import {
|
|
8
|
-
import { createSnapshotCommand } from "./commands/snapshot.js";
|
|
7
|
+
import { snapshotCommand } from "./commands/snapshot.js";
|
|
9
8
|
import { SimpleCLI } from "./framework/simple-cli.js";
|
|
10
9
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
10
|
+
export const cliRoutes = {
|
|
11
|
+
...browserCommands,
|
|
12
|
+
deploy: deployCommand,
|
|
13
|
+
...executionCommands,
|
|
14
|
+
...logCommands,
|
|
15
|
+
ai: aiCommands,
|
|
16
|
+
init: initCommand,
|
|
17
|
+
snapshot: snapshotCommand,
|
|
18
|
+
};
|
|
21
19
|
|
|
22
|
-
export function createCLIApp(
|
|
23
|
-
return SimpleCLI.define("libretto",
|
|
24
|
-
globalNamed: {
|
|
25
|
-
session: sessionOption(),
|
|
26
|
-
},
|
|
27
|
-
});
|
|
20
|
+
export function createCLIApp() {
|
|
21
|
+
return SimpleCLI.define("libretto", cliRoutes);
|
|
28
22
|
}
|
|
@@ -5,8 +5,11 @@ import { cwd } from "node:process";
|
|
|
5
5
|
import { isAbsolute, resolve } from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import {
|
|
8
|
+
getWorkflowFromModuleExports,
|
|
9
|
+
getWorkflowsFromModuleExports,
|
|
8
10
|
instrumentContext,
|
|
9
11
|
launchBrowser,
|
|
12
|
+
type ExportedLibrettoWorkflow,
|
|
10
13
|
type LibrettoWorkflowContext,
|
|
11
14
|
} from "../../index.js";
|
|
12
15
|
import type { LoggerApi } from "../../shared/logger/index.js";
|
|
@@ -18,14 +21,14 @@ import {
|
|
|
18
21
|
getSessionNetworkLogPath,
|
|
19
22
|
getSessionStatePath,
|
|
20
23
|
} from "../core/context.js";
|
|
21
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
getPauseSignalPaths,
|
|
26
|
+
removeSignalIfExists,
|
|
27
|
+
} from "../core/pause-signals.js";
|
|
22
28
|
import { installSessionTelemetry } from "../core/session-telemetry.js";
|
|
23
29
|
import type { RunIntegrationWorkerRequest } from "./run-integration-worker-protocol.js";
|
|
24
30
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
type LoadedLibrettoWorkflow = {
|
|
28
|
-
metadata: {};
|
|
31
|
+
type LoadedLibrettoWorkflow = ExportedLibrettoWorkflow & {
|
|
29
32
|
run: (ctx: LibrettoWorkflowContext, input: unknown) => Promise<unknown>;
|
|
30
33
|
};
|
|
31
34
|
|
|
@@ -73,7 +76,10 @@ function readSessionStatePid(session: string): number | null {
|
|
|
73
76
|
if (!existsSync(statePath)) return null;
|
|
74
77
|
|
|
75
78
|
try {
|
|
76
|
-
return
|
|
79
|
+
return (
|
|
80
|
+
parseSessionStateContent(readFileSync(statePath, "utf8"), statePath)
|
|
81
|
+
.pid ?? null
|
|
82
|
+
);
|
|
77
83
|
} catch {
|
|
78
84
|
return null;
|
|
79
85
|
}
|
|
@@ -103,17 +109,6 @@ async function waitForFailureSessionRelease(args: {
|
|
|
103
109
|
}
|
|
104
110
|
}
|
|
105
111
|
|
|
106
|
-
function isLoadedLibrettoWorkflow(value: unknown): value is LoadedLibrettoWorkflow {
|
|
107
|
-
if (!value || typeof value !== "object") return false;
|
|
108
|
-
const candidate = value as Record<PropertyKey, unknown>;
|
|
109
|
-
return (
|
|
110
|
-
candidate[LIBRETTO_WORKFLOW_BRAND] === true &&
|
|
111
|
-
typeof candidate.run === "function" &&
|
|
112
|
-
!!candidate.metadata &&
|
|
113
|
-
typeof candidate.metadata === "object"
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
112
|
function resolveLocalAuthProfilePath(domain: string): string {
|
|
118
113
|
return getProfilePath(normalizeDomain(domain));
|
|
119
114
|
}
|
|
@@ -144,9 +139,9 @@ function getAbsoluteIntegrationPath(integrationPath: string): string {
|
|
|
144
139
|
return absolutePath;
|
|
145
140
|
}
|
|
146
141
|
|
|
147
|
-
async function
|
|
142
|
+
async function loadWorkflowByName(
|
|
148
143
|
absolutePath: string,
|
|
149
|
-
|
|
144
|
+
workflowName: string,
|
|
150
145
|
): Promise<LoadedLibrettoWorkflow> {
|
|
151
146
|
let loadedModule: Record<string, unknown>;
|
|
152
147
|
try {
|
|
@@ -162,42 +157,23 @@ async function loadWorkflowExport(
|
|
|
162
157
|
);
|
|
163
158
|
}
|
|
164
159
|
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
const detail =
|
|
169
|
-
availableExports.length > 0
|
|
170
|
-
? ` Available exports: ${availableExports.join(", ")}`
|
|
171
|
-
: " The module has no exports.";
|
|
172
|
-
throw new Error(
|
|
173
|
-
`Export "${exportName}" was not found in ${absolutePath}.${detail}`,
|
|
174
|
-
);
|
|
160
|
+
const workflow = getWorkflowFromModuleExports(loadedModule, workflowName);
|
|
161
|
+
if (workflow) {
|
|
162
|
+
return workflow as LoadedLibrettoWorkflow;
|
|
175
163
|
}
|
|
176
164
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
"",
|
|
186
|
-
` export const ${exportName} = workflow<InputType, OutputType>(`,
|
|
187
|
-
" {},",
|
|
188
|
-
" async (ctx, input) => {",
|
|
189
|
-
" // ctx.page — Playwright Page instance",
|
|
190
|
-
" // ctx.logger — MinimalLogger",
|
|
191
|
-
" // ctx.services — injected dependencies (generic, default {})",
|
|
192
|
-
" // input — JSON-serializable input matching InputType",
|
|
193
|
-
" return output; // must match OutputType",
|
|
194
|
-
" },",
|
|
195
|
-
" );",
|
|
196
|
-
].join("\n"),
|
|
197
|
-
);
|
|
198
|
-
}
|
|
165
|
+
const availableWorkflows = getWorkflowsFromModuleExports(loadedModule).map(
|
|
166
|
+
(candidate) => candidate.name,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const detail =
|
|
170
|
+
availableWorkflows.length > 0
|
|
171
|
+
? ` Available workflows: ${availableWorkflows.join(", ")}`
|
|
172
|
+
: ' No workflows found in this file. Export a workflow() instance from "libretto" directly or via `export const workflows = { ... }`.';
|
|
199
173
|
|
|
200
|
-
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Workflow "${workflowName}" not found in ${absolutePath}.${detail}`,
|
|
176
|
+
);
|
|
201
177
|
}
|
|
202
178
|
|
|
203
179
|
export async function installHeadedWorkflowVisualization(args: {
|
|
@@ -219,7 +195,7 @@ async function runIntegrationInternal(
|
|
|
219
195
|
): Promise<RunIntegrationOutcome> {
|
|
220
196
|
const { logger } = options;
|
|
221
197
|
const absolutePath = getAbsoluteIntegrationPath(args.integrationPath);
|
|
222
|
-
const workflow = await
|
|
198
|
+
const workflow = await loadWorkflowByName(absolutePath, args.workflowName);
|
|
223
199
|
const signalPaths = getPauseSignalPaths(args.session);
|
|
224
200
|
await removeSignalIfExists(signalPaths.pausedSignalPath);
|
|
225
201
|
await removeSignalIfExists(signalPaths.resumeSignalPath);
|
|
@@ -228,12 +204,12 @@ async function runIntegrationInternal(
|
|
|
228
204
|
const restoreStdout = mirrorStdoutToFile(signalPaths.outputSignalPath);
|
|
229
205
|
|
|
230
206
|
console.log(
|
|
231
|
-
`Running
|
|
207
|
+
`Running workflow "${args.workflowName}" from ${absolutePath} (${args.headless ? "headless" : "headed"})...`,
|
|
232
208
|
);
|
|
233
209
|
|
|
234
210
|
const integrationLogger = logger.withScope("integration-run", {
|
|
235
211
|
integrationPath: absolutePath,
|
|
236
|
-
|
|
212
|
+
workflowName: args.workflowName,
|
|
237
213
|
session: args.session,
|
|
238
214
|
});
|
|
239
215
|
|
|
@@ -255,6 +231,7 @@ async function runIntegrationInternal(
|
|
|
255
231
|
sessionName: args.session,
|
|
256
232
|
headless: args.headless,
|
|
257
233
|
storageStatePath,
|
|
234
|
+
viewport: args.viewport,
|
|
258
235
|
});
|
|
259
236
|
if (!args.headless && args.visualize !== false) {
|
|
260
237
|
await installHeadedWorkflowVisualization({
|
|
@@ -277,16 +254,17 @@ async function runIntegrationInternal(
|
|
|
277
254
|
});
|
|
278
255
|
|
|
279
256
|
const workflowContext: LibrettoWorkflowContext = {
|
|
257
|
+
session: args.session,
|
|
280
258
|
logger: integrationLogger,
|
|
281
259
|
page: browserSession.page,
|
|
282
|
-
services: {},
|
|
283
260
|
};
|
|
284
261
|
|
|
285
262
|
try {
|
|
286
263
|
try {
|
|
287
264
|
await workflow.run(workflowContext, args.params ?? {});
|
|
288
265
|
} catch (error) {
|
|
289
|
-
const errorMessage =
|
|
266
|
+
const errorMessage =
|
|
267
|
+
error instanceof Error ? error.message : String(error);
|
|
290
268
|
await writeFile(
|
|
291
269
|
signalPaths.failedSignalPath,
|
|
292
270
|
JSON.stringify(
|
|
@@ -2,12 +2,13 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
export const RunIntegrationWorkerRequestSchema = z.object({
|
|
4
4
|
integrationPath: z.string().min(1),
|
|
5
|
-
|
|
5
|
+
workflowName: z.string().min(1),
|
|
6
6
|
session: z.string().min(1),
|
|
7
7
|
params: z.unknown(),
|
|
8
8
|
headless: z.boolean(),
|
|
9
9
|
visualize: z.boolean().default(true),
|
|
10
10
|
authProfileDomain: z.string().optional(),
|
|
11
|
+
viewport: z.object({ width: z.number(), height: z.number() }).optional(),
|
|
11
12
|
});
|
|
12
13
|
|
|
13
14
|
export type RunIntegrationWorkerRequest = z.infer<
|
|
@@ -5,10 +5,7 @@ import {
|
|
|
5
5
|
type RunIntegrationWorkerRequest,
|
|
6
6
|
} from "./run-integration-worker-protocol.js";
|
|
7
7
|
import { runIntegrationFromFileInWorker } from "./run-integration-runtime.js";
|
|
8
|
-
import {
|
|
9
|
-
ensureLibrettoSetup,
|
|
10
|
-
withSessionLogger,
|
|
11
|
-
} from "../core/context.js";
|
|
8
|
+
import { ensureLibrettoSetup, withSessionLogger } from "../core/context.js";
|
|
12
9
|
import { getPauseSignalPaths } from "../core/pause-signals.js";
|
|
13
10
|
|
|
14
11
|
function parseWorkerRequest(argv: string[]): RunIntegrationWorkerRequest {
|
package/src/index.ts
CHANGED
|
@@ -2,119 +2,129 @@ import { resolve } from "node:path";
|
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
3
|
|
|
4
4
|
// Logger
|
|
5
|
-
export { Logger, defaultLogger, type LoggerApi, type MinimalLogger, type LoggerSink, type LogOptions } from "./shared/logger/logger.js";
|
|
6
5
|
export {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
Logger,
|
|
7
|
+
defaultLogger,
|
|
8
|
+
type LoggerApi,
|
|
9
|
+
type MinimalLogger,
|
|
10
|
+
type LoggerSink,
|
|
11
|
+
type LogOptions,
|
|
12
|
+
} from "./shared/logger/logger.js";
|
|
13
|
+
export {
|
|
14
|
+
createFileLogSink,
|
|
15
|
+
prettyConsoleSink,
|
|
16
|
+
jsonlConsoleSink,
|
|
10
17
|
} from "./shared/logger/sinks.js";
|
|
11
18
|
|
|
12
19
|
// LLM client interface
|
|
13
|
-
export type {
|
|
20
|
+
export type {
|
|
21
|
+
LLMClient,
|
|
22
|
+
Message,
|
|
23
|
+
MessageContentPart,
|
|
24
|
+
} from "./shared/llm/types.js";
|
|
14
25
|
export { createLLMClientFromModel } from "./shared/llm/ai-sdk-adapter.js";
|
|
15
26
|
export {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
SESSION_STATE_VERSION,
|
|
28
|
+
SessionStatusSchema,
|
|
29
|
+
SessionStateFileSchema,
|
|
30
|
+
parseSessionStateData,
|
|
31
|
+
parseSessionStateContent,
|
|
32
|
+
serializeSessionState,
|
|
33
|
+
type SessionStatus,
|
|
34
|
+
type SessionState,
|
|
35
|
+
type SessionStateFile,
|
|
25
36
|
} from "./shared/state/index.js";
|
|
26
37
|
|
|
27
38
|
// Recovery
|
|
28
39
|
export { executeRecoveryAgent } from "./runtime/recovery/agent.js";
|
|
29
40
|
export { attemptWithRecovery } from "./runtime/recovery/recovery.js";
|
|
30
41
|
export {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
42
|
+
detectSubmissionError,
|
|
43
|
+
type KnownSubmissionError,
|
|
44
|
+
type DetectedSubmissionError,
|
|
34
45
|
} from "./runtime/recovery/errors.js";
|
|
35
46
|
|
|
36
47
|
// AI extraction
|
|
37
|
-
export {
|
|
48
|
+
export {
|
|
49
|
+
extractFromPage,
|
|
50
|
+
type ExtractOptions,
|
|
51
|
+
} from "./runtime/extract/extract.js";
|
|
38
52
|
|
|
39
53
|
// Network helpers
|
|
40
54
|
export {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
55
|
+
pageRequest,
|
|
56
|
+
type RequestConfig,
|
|
57
|
+
type PageRequestOptions,
|
|
44
58
|
} from "./runtime/network/network.js";
|
|
45
59
|
|
|
46
60
|
// Download helpers
|
|
47
61
|
export {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
62
|
+
downloadViaClick,
|
|
63
|
+
downloadAndSave,
|
|
64
|
+
type DownloadResult,
|
|
65
|
+
type DownloadViaClickOptions,
|
|
66
|
+
type SaveDownloadOptions,
|
|
53
67
|
} from "./runtime/download/download.js";
|
|
54
68
|
|
|
55
69
|
// Debug / Pause
|
|
56
70
|
export { pause } from "./shared/debug/pause.js";
|
|
57
71
|
|
|
58
|
-
// Config
|
|
59
|
-
export {
|
|
60
|
-
isDebugMode,
|
|
61
|
-
isDryRun,
|
|
62
|
-
shouldPauseBeforeMutation,
|
|
63
|
-
} from "./shared/config/config.js";
|
|
64
|
-
|
|
65
72
|
// Instrumentation
|
|
66
73
|
export {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
instrumentPage,
|
|
75
|
+
installInstrumentation,
|
|
76
|
+
instrumentContext,
|
|
77
|
+
type InstrumentationOptions,
|
|
78
|
+
type InstrumentedPage,
|
|
72
79
|
} from "./shared/instrumentation/instrument.js";
|
|
73
80
|
|
|
74
81
|
// Visualization
|
|
75
82
|
export {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
ensureGhostCursor,
|
|
84
|
+
moveGhostCursor,
|
|
85
|
+
ghostClick,
|
|
86
|
+
hideGhostCursor,
|
|
87
|
+
type GhostCursorOptions,
|
|
81
88
|
} from "./shared/visualization/ghost-cursor.js";
|
|
82
89
|
export {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
ensureHighlightLayer,
|
|
91
|
+
showHighlight,
|
|
92
|
+
clearHighlights,
|
|
93
|
+
type HighlightOptions,
|
|
87
94
|
} from "./shared/visualization/highlight.js";
|
|
88
95
|
|
|
89
96
|
// Run helpers
|
|
90
97
|
export {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
98
|
+
launchBrowser,
|
|
99
|
+
type LaunchBrowserArgs,
|
|
100
|
+
type BrowserSession,
|
|
94
101
|
} from "./shared/run/api.js";
|
|
95
102
|
|
|
96
103
|
// Workflow helpers
|
|
97
104
|
export {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
getWorkflowFromModuleExports,
|
|
106
|
+
getWorkflowsFromModuleExports,
|
|
107
|
+
isLibrettoWorkflow,
|
|
108
|
+
LibrettoWorkflow,
|
|
109
|
+
LIBRETTO_WORKFLOW_BRAND,
|
|
110
|
+
workflow,
|
|
111
|
+
type ExportedLibrettoWorkflow,
|
|
112
|
+
type LibrettoWorkflowContext,
|
|
113
|
+
type LibrettoWorkflowHandler,
|
|
104
114
|
} from "./shared/workflow/workflow.js";
|
|
105
|
-
|
|
106
115
|
const isDirectExecution = (): boolean => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
const entryArg = process.argv[1];
|
|
117
|
+
if (!entryArg) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
return pathToFileURL(resolve(entryArg)).href === import.meta.url;
|
|
112
121
|
};
|
|
113
122
|
|
|
114
123
|
if (isDirectExecution()) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
124
|
+
void import("./cli/index.js").catch((error: unknown) => {
|
|
125
|
+
const message =
|
|
126
|
+
error instanceof Error ? (error.stack ?? error.message) : String(error);
|
|
127
|
+
process.stderr.write(`${message}\n`);
|
|
128
|
+
process.exitCode = 1;
|
|
129
|
+
});
|
|
120
130
|
}
|
|
@@ -4,16 +4,16 @@ import type { Page, Download } from "playwright";
|
|
|
4
4
|
import type { MinimalLogger } from "../../shared/logger/logger.js";
|
|
5
5
|
|
|
6
6
|
export type DownloadResult = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
/** The raw file contents. */
|
|
8
|
+
buffer: Buffer;
|
|
9
|
+
/** The filename suggested by the server (Content-Disposition header or URL). */
|
|
10
|
+
filename: string;
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
export type DownloadViaClickOptions = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
logger?: MinimalLogger;
|
|
15
|
+
/** Timeout in milliseconds for waiting on the download event. Defaults to 30 000. */
|
|
16
|
+
timeout?: number;
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -24,55 +24,55 @@ export type DownloadViaClickOptions = {
|
|
|
24
24
|
* never missed.
|
|
25
25
|
*/
|
|
26
26
|
export async function downloadViaClick(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
page: Page,
|
|
28
|
+
selector: string,
|
|
29
|
+
options?: DownloadViaClickOptions,
|
|
30
30
|
): Promise<DownloadResult> {
|
|
31
|
-
|
|
31
|
+
const { logger, timeout = 30_000 } = options ?? {};
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
const startTime = Date.now();
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// 1. Register the download listener BEFORE clicking
|
|
36
|
+
const downloadPromise = page.waitForEvent("download", { timeout });
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
// 2. Click the element that triggers the download
|
|
39
|
+
await page.locator(selector).click();
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
// 3. Await the download event
|
|
42
|
+
const download: Download = await downloadPromise;
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// 4. Get the suggested filename
|
|
45
|
+
const filename = download.suggestedFilename();
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
// 5. Read the downloaded file into a buffer
|
|
48
|
+
const readStream = await download.createReadStream();
|
|
49
|
+
if (!readStream) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Download stream unavailable for "${filename}". The browser may have been closed before the download completed.`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
const chunks: Buffer[] = [];
|
|
56
|
+
for await (const chunk of readStream) {
|
|
57
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
58
|
+
}
|
|
59
|
+
const buffer = Buffer.concat(chunks);
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
const duration = Date.now() - startTime;
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
logger?.info("download:click", {
|
|
64
|
+
selector,
|
|
65
|
+
filename,
|
|
66
|
+
size: buffer.length,
|
|
67
|
+
duration,
|
|
68
|
+
});
|
|
69
69
|
|
|
70
|
-
|
|
70
|
+
return { buffer, filename };
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export type SaveDownloadOptions = DownloadViaClickOptions & {
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
/** Absolute or relative path to save the file to. When omitted the suggested filename is used in the current working directory. */
|
|
75
|
+
savePath?: string;
|
|
76
76
|
};
|
|
77
77
|
|
|
78
78
|
/**
|
|
@@ -80,21 +80,25 @@ export type SaveDownloadOptions = DownloadViaClickOptions & {
|
|
|
80
80
|
* downloaded file to disk.
|
|
81
81
|
*/
|
|
82
82
|
export async function downloadAndSave(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
page: Page,
|
|
84
|
+
selector: string,
|
|
85
|
+
options?: SaveDownloadOptions,
|
|
86
86
|
): Promise<DownloadResult & { savedTo: string }> {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
87
|
+
const { savePath, ...downloadOpts } = options ?? {};
|
|
88
|
+
const { buffer, filename } = await downloadViaClick(
|
|
89
|
+
page,
|
|
90
|
+
selector,
|
|
91
|
+
downloadOpts,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const dest = resolve(savePath ?? filename);
|
|
95
|
+
await writeFile(dest, buffer);
|
|
96
|
+
|
|
97
|
+
options?.logger?.info("download:saved", {
|
|
98
|
+
filename,
|
|
99
|
+
savedTo: dest,
|
|
100
|
+
size: buffer.length,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
return { buffer, filename, savedTo: dest };
|
|
100
104
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
downloadViaClick,
|
|
3
|
+
downloadAndSave,
|
|
4
|
+
type DownloadResult,
|
|
5
|
+
type DownloadViaClickOptions,
|
|
6
|
+
type SaveDownloadOptions,
|
|
7
7
|
} from "./download.js";
|