libretto 0.5.4 → 0.5.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.
- package/dist/cli/core/browser.js +42 -16
- package/dist/cli/workers/run-integration-runtime.js +12 -12
- package/dist/shared/workflow/workflow.d.ts +0 -2
- package/package.json +15 -17
- package/skills/libretto/references/code-generation-rules.md +3 -3
- package/src/cli/core/browser.ts +62 -19
- package/src/cli/workers/run-integration-runtime.ts +20 -15
- package/src/shared/workflow/workflow.ts +0 -2
package/dist/cli/core/browser.js
CHANGED
|
@@ -47,19 +47,44 @@ async function pickFreePort() {
|
|
|
47
47
|
server.on("error", reject);
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
+
function tryParseAbsoluteUrl(url) {
|
|
51
|
+
try {
|
|
52
|
+
return new URL(url);
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function isLikelyHostWithPort(parsedUrl, rawUrl) {
|
|
58
|
+
const remainder = rawUrl.slice(parsedUrl.protocol.length);
|
|
59
|
+
if (remainder.length === 0) return false;
|
|
60
|
+
let index = 0;
|
|
61
|
+
while (index < remainder.length) {
|
|
62
|
+
const charCode = remainder.charCodeAt(index);
|
|
63
|
+
if (charCode < 48 || charCode > 57) break;
|
|
64
|
+
index += 1;
|
|
65
|
+
}
|
|
66
|
+
if (index === 0) return false;
|
|
67
|
+
if (index === remainder.length) return true;
|
|
68
|
+
const nextChar = remainder[index];
|
|
69
|
+
return nextChar === "/" || nextChar === "?" || nextChar === "#";
|
|
70
|
+
}
|
|
50
71
|
function normalizeUrl(url) {
|
|
51
|
-
|
|
52
|
-
|
|
72
|
+
const parsedUrl = tryParseAbsoluteUrl(url);
|
|
73
|
+
if (!parsedUrl) {
|
|
74
|
+
return new URL(`https://${url}`);
|
|
53
75
|
}
|
|
54
|
-
|
|
76
|
+
if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:" || parsedUrl.protocol === "file:") {
|
|
77
|
+
return parsedUrl;
|
|
78
|
+
}
|
|
79
|
+
if (isLikelyHostWithPort(parsedUrl, url)) {
|
|
80
|
+
return new URL(`https://${url}`);
|
|
81
|
+
}
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Unsupported URL protocol: ${parsedUrl.protocol}. Use http://, https://, or file://.`
|
|
84
|
+
);
|
|
55
85
|
}
|
|
56
86
|
function normalizeDomain(url) {
|
|
57
|
-
|
|
58
|
-
const u = new URL(normalizeUrl(url));
|
|
59
|
-
return u.hostname.replace(/^www\./, "");
|
|
60
|
-
} catch {
|
|
61
|
-
return url.replace(/^www\./, "");
|
|
62
|
-
}
|
|
87
|
+
return url.hostname.replace(/^www\./, "");
|
|
63
88
|
}
|
|
64
89
|
function getProfilePath(domain) {
|
|
65
90
|
return join(PROFILES_DIR, `${domain}.json`);
|
|
@@ -265,7 +290,8 @@ function resolveWindowPosition(logger) {
|
|
|
265
290
|
return void 0;
|
|
266
291
|
}
|
|
267
292
|
async function runOpen(rawUrl, headed, session, logger, options) {
|
|
268
|
-
const
|
|
293
|
+
const parsedUrl = normalizeUrl(rawUrl);
|
|
294
|
+
const url = parsedUrl.href;
|
|
269
295
|
const viewport = resolveViewport(options?.viewport, logger);
|
|
270
296
|
const windowPosition = headed ? resolveWindowPosition(logger) : void 0;
|
|
271
297
|
logger.info("open-start", { url, headed, session, viewport, windowPosition });
|
|
@@ -275,9 +301,10 @@ async function runOpen(rawUrl, headed, session, logger, options) {
|
|
|
275
301
|
const networkLogPath = getSessionNetworkLogPath(session);
|
|
276
302
|
const actionsLogPath = getSessionActionsLogPath(session);
|
|
277
303
|
const browserMode = headed ? "headed" : "headless";
|
|
278
|
-
const
|
|
279
|
-
const
|
|
280
|
-
const
|
|
304
|
+
const supportsSavedProfile = parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:";
|
|
305
|
+
const domain = supportsSavedProfile ? normalizeDomain(parsedUrl) : void 0;
|
|
306
|
+
const profilePath = domain ? getProfilePath(domain) : void 0;
|
|
307
|
+
const useProfile = domain ? hasProfile(domain) : false;
|
|
281
308
|
logger.info("open-launching", {
|
|
282
309
|
url,
|
|
283
310
|
mode: browserMode,
|
|
@@ -291,9 +318,8 @@ async function runOpen(rawUrl, headed, session, logger, options) {
|
|
|
291
318
|
console.log(`Loading saved profile for ${domain}`);
|
|
292
319
|
}
|
|
293
320
|
console.log(`Launching ${browserMode} browser (session: ${session})...`);
|
|
294
|
-
const escapedProfilePath = profilePath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
295
321
|
const escapedUrl = url.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
296
|
-
const storageStateCode = useProfile ? `storageState: '${
|
|
322
|
+
const storageStateCode = useProfile ? `storageState: '${profilePath.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}',` : "";
|
|
297
323
|
const escapedLogPath = runLogPath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
298
324
|
const escapedNetworkLogPath = networkLogPath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
299
325
|
const escapedActionsLogPath = actionsLogPath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
@@ -528,7 +554,7 @@ async function runSave(urlOrDomain, session, logger) {
|
|
|
528
554
|
const { browser, context, page } = await connect(session, logger);
|
|
529
555
|
try {
|
|
530
556
|
await new Promise((r) => setTimeout(r, 500));
|
|
531
|
-
const domain = normalizeDomain(urlOrDomain);
|
|
557
|
+
const domain = normalizeDomain(normalizeUrl(urlOrDomain));
|
|
532
558
|
const profilePath = getProfilePath(domain);
|
|
533
559
|
const cdpSession = await context.newCDPSession(page);
|
|
534
560
|
const { cookies: rawCookies } = await cdpSession.send(
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
launchBrowser
|
|
11
11
|
} from "../../index.js";
|
|
12
12
|
import { parseSessionStateContent } from "../../shared/state/index.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getProfilePath,
|
|
15
|
+
normalizeDomain,
|
|
16
|
+
normalizeUrl
|
|
17
|
+
} from "../core/browser.js";
|
|
14
18
|
import {
|
|
15
19
|
getSessionActionsLogPath,
|
|
16
20
|
getSessionNetworkLogPath,
|
|
@@ -68,18 +72,14 @@ async function waitForFailureSessionRelease(args) {
|
|
|
68
72
|
);
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
|
-
function resolveLocalAuthProfilePath(domain) {
|
|
72
|
-
return getProfilePath(normalizeDomain(domain));
|
|
73
|
-
}
|
|
74
75
|
function getMissingLocalAuthProfileError(args) {
|
|
75
|
-
const normalizedDomain = normalizeDomain(args.domain);
|
|
76
76
|
return [
|
|
77
|
-
`Local auth profile not found for domain "${normalizedDomain}".`,
|
|
77
|
+
`Local auth profile not found for domain "${args.normalizedDomain}".`,
|
|
78
78
|
`Expected profile file: ${args.profilePath}`,
|
|
79
79
|
"To create it:",
|
|
80
|
-
` 1. libretto open https://${normalizedDomain} --headed --session ${args.session}`,
|
|
80
|
+
` 1. libretto open https://${args.normalizedDomain} --headed --session ${args.session}`,
|
|
81
81
|
" 2. Log in manually in the browser window.",
|
|
82
|
-
` 3. libretto save ${normalizedDomain} --session ${args.session}`
|
|
82
|
+
` 3. libretto save ${args.normalizedDomain} --session ${args.session}`
|
|
83
83
|
].join("\n");
|
|
84
84
|
}
|
|
85
85
|
function getAbsoluteIntegrationPath(integrationPath) {
|
|
@@ -138,11 +138,12 @@ async function runIntegrationInternal(args, options) {
|
|
|
138
138
|
session: args.session
|
|
139
139
|
});
|
|
140
140
|
const authProfileDomain = args.authProfileDomain;
|
|
141
|
-
const
|
|
142
|
-
|
|
141
|
+
const normalizedAuthProfileDomain = authProfileDomain ? normalizeDomain(normalizeUrl(authProfileDomain)) : void 0;
|
|
142
|
+
const storageStatePath = normalizedAuthProfileDomain ? getProfilePath(normalizedAuthProfileDomain) : void 0;
|
|
143
|
+
if (normalizedAuthProfileDomain && storageStatePath && !existsSync(storageStatePath)) {
|
|
143
144
|
throw new Error(
|
|
144
145
|
getMissingLocalAuthProfileError({
|
|
145
|
-
|
|
146
|
+
normalizedDomain: normalizedAuthProfileDomain,
|
|
146
147
|
profilePath: storageStatePath,
|
|
147
148
|
session: args.session
|
|
148
149
|
})
|
|
@@ -175,7 +176,6 @@ async function runIntegrationInternal(args, options) {
|
|
|
175
176
|
});
|
|
176
177
|
const workflowContext = {
|
|
177
178
|
session: args.session,
|
|
178
|
-
logger: integrationLogger,
|
|
179
179
|
page: browserSession.page
|
|
180
180
|
};
|
|
181
181
|
try {
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Page } from 'playwright';
|
|
2
|
-
import { MinimalLogger } from '../logger/logger.js';
|
|
3
2
|
|
|
4
3
|
declare const LIBRETTO_WORKFLOW_BRAND: unique symbol;
|
|
5
4
|
type LibrettoWorkflowContext = {
|
|
6
5
|
session: string;
|
|
7
6
|
page: Page;
|
|
8
|
-
logger: MinimalLogger;
|
|
9
7
|
};
|
|
10
8
|
type LibrettoWorkflowHandler<Input = unknown, Output = unknown> = (ctx: LibrettoWorkflowContext, input: Input) => Promise<Output>;
|
|
11
9
|
declare class LibrettoWorkflow<Input = unknown, Output = unknown> {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libretto",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "AI-powered browser automation library and CLI built on Playwright",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
"url": "https://github.com/saffron-health/libretto"
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
|
-
"packageManager": "pnpm@9.15.4",
|
|
12
11
|
"publishConfig": {
|
|
13
12
|
"access": "public"
|
|
14
13
|
},
|
|
@@ -29,20 +28,6 @@
|
|
|
29
28
|
"default": "./dist/index.js"
|
|
30
29
|
}
|
|
31
30
|
},
|
|
32
|
-
"scripts": {
|
|
33
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
34
|
-
"sync:mirrors": "node ../dev-tools/scripts/sync-mirrors.mjs",
|
|
35
|
-
"check:mirrors": "node ../dev-tools/scripts/check-mirrors-sync.mjs",
|
|
36
|
-
"sync-skills": "pnpm run sync:mirrors",
|
|
37
|
-
"check:skills": "pnpm run check:mirrors",
|
|
38
|
-
"build": "tsup --config tsup.config.ts",
|
|
39
|
-
"type-check": "tsc --noEmit",
|
|
40
|
-
"test": "pnpm run build && vitest run",
|
|
41
|
-
"test:watch": "vitest",
|
|
42
|
-
"cli": "node dist/index.js",
|
|
43
|
-
"generate-changelog": "tsx scripts/generate-changelog.ts",
|
|
44
|
-
"prepack": "pnpm run build"
|
|
45
|
-
},
|
|
46
31
|
"peerDependencies": {
|
|
47
32
|
"@ai-sdk/anthropic": "^3.0.58",
|
|
48
33
|
"@ai-sdk/google": "^3.0.51",
|
|
@@ -87,5 +72,18 @@
|
|
|
87
72
|
"playwright": "^1.58.2",
|
|
88
73
|
"tsx": "^4.21.0",
|
|
89
74
|
"zod": "^4.3.6"
|
|
75
|
+
},
|
|
76
|
+
"scripts": {
|
|
77
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
78
|
+
"sync:mirrors": "node ../dev-tools/scripts/sync-mirrors.mjs",
|
|
79
|
+
"check:mirrors": "node ../dev-tools/scripts/check-mirrors-sync.mjs",
|
|
80
|
+
"sync-skills": "pnpm run sync:mirrors",
|
|
81
|
+
"check:skills": "pnpm run check:mirrors",
|
|
82
|
+
"build": "tsup --config tsup.config.ts",
|
|
83
|
+
"type-check": "tsc --noEmit",
|
|
84
|
+
"test": "pnpm run build && vitest run",
|
|
85
|
+
"test:watch": "vitest",
|
|
86
|
+
"cli": "node dist/index.js",
|
|
87
|
+
"generate-changelog": "tsx scripts/generate-changelog.ts"
|
|
90
88
|
}
|
|
91
|
-
}
|
|
89
|
+
}
|
|
@@ -25,9 +25,9 @@ type Output = {
|
|
|
25
25
|
export const myWorkflow = workflow<Input, Output>(
|
|
26
26
|
"myWorkflow",
|
|
27
27
|
async (ctx: LibrettoWorkflowContext, input): Promise<Output> => {
|
|
28
|
-
const { session, page
|
|
28
|
+
const { session, page } = ctx;
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
console.log("workflow-start", { session, query: input.query });
|
|
31
31
|
await page.goto("https://example.com");
|
|
32
32
|
await pause(session);
|
|
33
33
|
|
|
@@ -40,7 +40,7 @@ Key points:
|
|
|
40
40
|
|
|
41
41
|
- `workflow(name, handler)` takes a unique workflow name and returns the workflow object that Libretto can run.
|
|
42
42
|
- `npx libretto run ./file.ts myWorkflow` resolves `myWorkflow` from the workflows exported by `./file.ts`, so export or re-export the workflow from that file directly or through a `workflows` object, and make sure the run argument matches the name passed to `workflow("myWorkflow", ...)`.
|
|
43
|
-
- `ctx` provides `session
|
|
43
|
+
- `ctx` provides `session` and `page`. Use `console.log`/`console.warn`/`console.error` for logging — the runtime wraps these with structured metadata automatically.
|
|
44
44
|
- `input` comes from `--params '{"query":"foo"}'` or `--params-file params.json` on the CLI
|
|
45
45
|
- Use `await pause(ctx.session)` (or `await pause(session)`) to pause the workflow for debugging. It is a no-op in production.
|
|
46
46
|
- After validation is complete and the workflow is confirmed working end to end, remove all `pause()` calls and pause-only workflow params unless the user explicitly says to keep them.
|
package/src/cli/core/browser.ts
CHANGED
|
@@ -56,20 +56,61 @@ async function pickFreePort(): Promise<number> {
|
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return
|
|
59
|
+
function tryParseAbsoluteUrl(url: string): URL | null {
|
|
60
|
+
try {
|
|
61
|
+
return new URL(url);
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
62
64
|
}
|
|
63
|
-
return url;
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
function isLikelyHostWithPort(parsedUrl: URL, rawUrl: string): boolean {
|
|
68
|
+
// `new URL("localhost:3000")` parses successfully, but treats `localhost:`
|
|
69
|
+
// as a custom scheme instead of a bare host with port. Detect that shape so
|
|
70
|
+
// CLI shorthand like `libretto open localhost:3000` still normalizes to
|
|
71
|
+
// `https://localhost:3000/`.
|
|
72
|
+
const remainder = rawUrl.slice(parsedUrl.protocol.length);
|
|
73
|
+
if (remainder.length === 0) return false;
|
|
74
|
+
|
|
75
|
+
let index = 0;
|
|
76
|
+
while (index < remainder.length) {
|
|
77
|
+
const charCode = remainder.charCodeAt(index);
|
|
78
|
+
if (charCode < 48 || charCode > 57) break;
|
|
79
|
+
index += 1;
|
|
72
80
|
}
|
|
81
|
+
|
|
82
|
+
if (index === 0) return false;
|
|
83
|
+
if (index === remainder.length) return true;
|
|
84
|
+
|
|
85
|
+
const nextChar = remainder[index];
|
|
86
|
+
return nextChar === "/" || nextChar === "?" || nextChar === "#";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function normalizeUrl(url: string): URL {
|
|
90
|
+
const parsedUrl = tryParseAbsoluteUrl(url);
|
|
91
|
+
if (!parsedUrl) {
|
|
92
|
+
return new URL(`https://${url}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (
|
|
96
|
+
parsedUrl.protocol === "http:" ||
|
|
97
|
+
parsedUrl.protocol === "https:" ||
|
|
98
|
+
parsedUrl.protocol === "file:"
|
|
99
|
+
) {
|
|
100
|
+
return parsedUrl;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (isLikelyHostWithPort(parsedUrl, url)) {
|
|
104
|
+
return new URL(`https://${url}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Unsupported URL protocol: ${parsedUrl.protocol}. Use http://, https://, or file://.`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function normalizeDomain(url: URL): string {
|
|
113
|
+
return url.hostname.replace(/^www\./, "");
|
|
73
114
|
}
|
|
74
115
|
|
|
75
116
|
export function getProfilePath(domain: string): string {
|
|
@@ -359,7 +400,8 @@ export async function runOpen(
|
|
|
359
400
|
logger: LoggerApi,
|
|
360
401
|
options?: { viewport?: { width: number; height: number } },
|
|
361
402
|
): Promise<void> {
|
|
362
|
-
const
|
|
403
|
+
const parsedUrl = normalizeUrl(rawUrl);
|
|
404
|
+
const url = parsedUrl.href;
|
|
363
405
|
const viewport = resolveViewport(options?.viewport, logger);
|
|
364
406
|
const windowPosition = headed ? resolveWindowPosition(logger) : undefined;
|
|
365
407
|
logger.info("open-start", { url, headed, session, viewport, windowPosition });
|
|
@@ -371,9 +413,11 @@ export async function runOpen(
|
|
|
371
413
|
const actionsLogPath = getSessionActionsLogPath(session);
|
|
372
414
|
|
|
373
415
|
const browserMode = headed ? "headed" : "headless";
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
const
|
|
416
|
+
const supportsSavedProfile =
|
|
417
|
+
parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:";
|
|
418
|
+
const domain = supportsSavedProfile ? normalizeDomain(parsedUrl) : undefined;
|
|
419
|
+
const profilePath = domain ? getProfilePath(domain) : undefined;
|
|
420
|
+
const useProfile = domain ? hasProfile(domain) : false;
|
|
377
421
|
|
|
378
422
|
logger.info("open-launching", {
|
|
379
423
|
url,
|
|
@@ -390,12 +434,11 @@ export async function runOpen(
|
|
|
390
434
|
}
|
|
391
435
|
console.log(`Launching ${browserMode} browser (session: ${session})...`);
|
|
392
436
|
|
|
393
|
-
const escapedProfilePath = profilePath
|
|
394
|
-
.replace(/\\/g, "\\\\")
|
|
395
|
-
.replace(/'/g, "\\'");
|
|
396
437
|
const escapedUrl = url.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
397
438
|
const storageStateCode = useProfile
|
|
398
|
-
? `storageState: '${
|
|
439
|
+
? `storageState: '${profilePath!
|
|
440
|
+
.replace(/\\/g, "\\\\")
|
|
441
|
+
.replace(/'/g, "\\'")}',`
|
|
399
442
|
: "";
|
|
400
443
|
|
|
401
444
|
const escapedLogPath = runLogPath.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
@@ -671,7 +714,7 @@ export async function runSave(
|
|
|
671
714
|
try {
|
|
672
715
|
await new Promise((r) => setTimeout(r, 500));
|
|
673
716
|
|
|
674
|
-
const domain = normalizeDomain(urlOrDomain);
|
|
717
|
+
const domain = normalizeDomain(normalizeUrl(urlOrDomain));
|
|
675
718
|
const profilePath = getProfilePath(domain);
|
|
676
719
|
|
|
677
720
|
const cdpSession = await context.newCDPSession(page);
|
|
@@ -14,7 +14,11 @@ import {
|
|
|
14
14
|
} from "../../index.js";
|
|
15
15
|
import type { LoggerApi } from "../../shared/logger/index.js";
|
|
16
16
|
import { parseSessionStateContent } from "../../shared/state/index.js";
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
getProfilePath,
|
|
19
|
+
normalizeDomain,
|
|
20
|
+
normalizeUrl,
|
|
21
|
+
} from "../core/browser.js";
|
|
18
22
|
import {
|
|
19
23
|
getSessionActionsLogPath,
|
|
20
24
|
getSessionDir,
|
|
@@ -109,23 +113,18 @@ async function waitForFailureSessionRelease(args: {
|
|
|
109
113
|
}
|
|
110
114
|
}
|
|
111
115
|
|
|
112
|
-
function resolveLocalAuthProfilePath(domain: string): string {
|
|
113
|
-
return getProfilePath(normalizeDomain(domain));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
116
|
function getMissingLocalAuthProfileError(args: {
|
|
117
|
-
|
|
117
|
+
normalizedDomain: string;
|
|
118
118
|
profilePath: string;
|
|
119
119
|
session: string;
|
|
120
120
|
}): string {
|
|
121
|
-
const normalizedDomain = normalizeDomain(args.domain);
|
|
122
121
|
return [
|
|
123
|
-
`Local auth profile not found for domain "${normalizedDomain}".`,
|
|
122
|
+
`Local auth profile not found for domain "${args.normalizedDomain}".`,
|
|
124
123
|
`Expected profile file: ${args.profilePath}`,
|
|
125
124
|
"To create it:",
|
|
126
|
-
` 1. libretto open https://${normalizedDomain} --headed --session ${args.session}`,
|
|
125
|
+
` 1. libretto open https://${args.normalizedDomain} --headed --session ${args.session}`,
|
|
127
126
|
" 2. Log in manually in the browser window.",
|
|
128
|
-
` 3. libretto save ${normalizedDomain} --session ${args.session}`,
|
|
127
|
+
` 3. libretto save ${args.normalizedDomain} --session ${args.session}`,
|
|
129
128
|
].join("\n");
|
|
130
129
|
}
|
|
131
130
|
|
|
@@ -215,13 +214,20 @@ async function runIntegrationInternal(
|
|
|
215
214
|
|
|
216
215
|
// Resolve auth profile from CLI flag (--auth-profile <domain>)
|
|
217
216
|
const authProfileDomain = args.authProfileDomain;
|
|
218
|
-
const
|
|
219
|
-
?
|
|
217
|
+
const normalizedAuthProfileDomain = authProfileDomain
|
|
218
|
+
? normalizeDomain(normalizeUrl(authProfileDomain))
|
|
219
|
+
: undefined;
|
|
220
|
+
const storageStatePath = normalizedAuthProfileDomain
|
|
221
|
+
? getProfilePath(normalizedAuthProfileDomain)
|
|
220
222
|
: undefined;
|
|
221
|
-
if (
|
|
223
|
+
if (
|
|
224
|
+
normalizedAuthProfileDomain &&
|
|
225
|
+
storageStatePath &&
|
|
226
|
+
!existsSync(storageStatePath)
|
|
227
|
+
) {
|
|
222
228
|
throw new Error(
|
|
223
229
|
getMissingLocalAuthProfileError({
|
|
224
|
-
|
|
230
|
+
normalizedDomain: normalizedAuthProfileDomain,
|
|
225
231
|
profilePath: storageStatePath,
|
|
226
232
|
session: args.session,
|
|
227
233
|
}),
|
|
@@ -255,7 +261,6 @@ async function runIntegrationInternal(
|
|
|
255
261
|
|
|
256
262
|
const workflowContext: LibrettoWorkflowContext = {
|
|
257
263
|
session: args.session,
|
|
258
|
-
logger: integrationLogger,
|
|
259
264
|
page: browserSession.page,
|
|
260
265
|
};
|
|
261
266
|
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import type { Page } from "playwright";
|
|
2
|
-
import type { MinimalLogger } from "../logger/logger.js";
|
|
3
2
|
|
|
4
3
|
export const LIBRETTO_WORKFLOW_BRAND = Symbol.for("libretto.workflow");
|
|
5
4
|
|
|
6
5
|
export type LibrettoWorkflowContext = {
|
|
7
6
|
session: string;
|
|
8
7
|
page: Page;
|
|
9
|
-
logger: MinimalLogger;
|
|
10
8
|
};
|
|
11
9
|
|
|
12
10
|
export type LibrettoWorkflowHandler<Input = unknown, Output = unknown> = (
|