libretto 0.6.24 → 0.6.25

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 (63) hide show
  1. package/README.md +9 -1
  2. package/README.template.md +9 -1
  3. package/dist/cli/commands/browser.js +17 -10
  4. package/dist/cli/commands/cloud-credentials.js +70 -0
  5. package/dist/cli/commands/deploy.js +24 -2
  6. package/dist/cli/commands/execution.js +9 -30
  7. package/dist/cli/commands/import-chrome-profiles.js +46 -0
  8. package/dist/cli/commands/profiles.js +71 -0
  9. package/dist/cli/commands/shared.js +1 -3
  10. package/dist/cli/core/browser.js +89 -75
  11. package/dist/cli/core/daemon/daemon.js +47 -35
  12. package/dist/cli/core/daemon/ipc.js +3 -0
  13. package/dist/cli/core/deploy-artifact.js +85 -22
  14. package/dist/cli/core/profiles.js +47 -0
  15. package/dist/cli/core/prompt.js +9 -0
  16. package/dist/cli/core/providers/libretto-cloud.js +6 -2
  17. package/dist/cli/core/session-logs.js +325 -0
  18. package/dist/cli/core/telemetry.js +83 -313
  19. package/dist/cli/core/workflow-runner/runner.js +65 -0
  20. package/dist/cli/router.js +9 -1
  21. package/dist/index.d.ts +2 -0
  22. package/dist/index.js +12 -0
  23. package/dist/shared/workflow/auth-profile-name.d.ts +3 -0
  24. package/dist/shared/workflow/auth-profile-name.js +29 -0
  25. package/dist/shared/workflow/auth-profile-state.d.ts +20 -0
  26. package/dist/shared/workflow/auth-profile-state.js +105 -0
  27. package/dist/shared/workflow/authenticate.d.ts +17 -0
  28. package/dist/shared/workflow/authenticate.js +37 -0
  29. package/dist/shared/workflow/credentials.d.ts +5 -0
  30. package/dist/shared/workflow/credentials.js +68 -0
  31. package/dist/shared/workflow/workflow.d.ts +16 -1
  32. package/dist/shared/workflow/workflow.js +56 -4
  33. package/package.json +1 -1
  34. package/skills/libretto/SKILL.md +3 -4
  35. package/skills/libretto/references/auth-profiles.md +61 -11
  36. package/skills/libretto/references/code-generation-rules.md +31 -1
  37. package/skills/libretto-readonly/SKILL.md +1 -1
  38. package/src/cli/commands/browser.ts +19 -11
  39. package/src/cli/commands/cloud-credentials.ts +82 -0
  40. package/src/cli/commands/deploy.ts +41 -2
  41. package/src/cli/commands/execution.ts +10 -31
  42. package/src/cli/commands/import-chrome-profiles.ts +46 -0
  43. package/src/cli/commands/profiles.ts +90 -0
  44. package/src/cli/commands/shared.ts +4 -8
  45. package/src/cli/core/browser.ts +102 -91
  46. package/src/cli/core/daemon/config.ts +4 -1
  47. package/src/cli/core/daemon/daemon.ts +52 -44
  48. package/src/cli/core/daemon/ipc.ts +15 -0
  49. package/src/cli/core/deploy-artifact.ts +131 -32
  50. package/src/cli/core/profiles.ts +53 -0
  51. package/src/cli/core/prompt.ts +15 -0
  52. package/src/cli/core/providers/libretto-cloud.ts +6 -2
  53. package/src/cli/core/providers/types.ts +4 -1
  54. package/src/cli/core/session-logs.ts +445 -0
  55. package/src/cli/core/telemetry.ts +105 -422
  56. package/src/cli/core/workflow-runner/runner.ts +86 -1
  57. package/src/cli/router.ts +8 -0
  58. package/src/index.ts +10 -0
  59. package/src/shared/workflow/auth-profile-name.ts +27 -0
  60. package/src/shared/workflow/auth-profile-state.ts +144 -0
  61. package/src/shared/workflow/authenticate.ts +63 -0
  62. package/src/shared/workflow/credentials.ts +91 -0
  63. package/src/shared/workflow/workflow.ts +89 -4
@@ -0,0 +1,105 @@
1
+ function parseAuthProfileSites(value) {
2
+ const sites = value.split(",").map((entry) => normalizeAuthProfileSite(entry)).filter((entry) => Boolean(entry));
3
+ return [...new Set(sites)];
4
+ }
5
+ function normalizeAuthProfileSite(value) {
6
+ const trimmed = value.trim();
7
+ if (!trimmed) return null;
8
+ try {
9
+ const url = trimmed.includes("://") ? new URL(trimmed) : new URL(`https://${trimmed}`);
10
+ return normalizeHost(url.hostname);
11
+ } catch {
12
+ const normalized = normalizeHost(trimmed);
13
+ return normalized || null;
14
+ }
15
+ }
16
+ async function captureAuthProfileStorageState(context, sites) {
17
+ const normalizedSites = [...new Set(
18
+ sites.map((site) => normalizeAuthProfileSite(site)).filter((site) => Boolean(site))
19
+ )];
20
+ if (normalizedSites.length === 0) {
21
+ throw new Error("At least one auth profile site is required.");
22
+ }
23
+ const state = await context.storageState({ indexedDB: true });
24
+ return {
25
+ sites: normalizedSites,
26
+ cookies: state.cookies.filter(
27
+ (cookie) => cookieDomainMatchesSites(cookie.domain, normalizedSites)
28
+ ),
29
+ origins: state.origins.filter(
30
+ (origin) => originMatchesSites(origin.origin, normalizedSites)
31
+ )
32
+ };
33
+ }
34
+ function mergeAuthProfileStorageState(existing, latest, sites) {
35
+ const normalizedSites = [...new Set(
36
+ sites.map((site) => normalizeAuthProfileSite(site)).filter((site) => Boolean(site))
37
+ )];
38
+ if (normalizedSites.length === 0) {
39
+ return existing;
40
+ }
41
+ const existingCookies = existing.cookies ?? [];
42
+ const latestCookies = latest.cookies ?? [];
43
+ const existingOrigins = existing.origins ?? [];
44
+ const latestOrigins = latest.origins ?? [];
45
+ const mergedSites = [
46
+ .../* @__PURE__ */ new Set([
47
+ ...existing.sites ?? [],
48
+ ...normalizedSites
49
+ ])
50
+ ];
51
+ return {
52
+ ...existing,
53
+ sites: mergedSites,
54
+ cookies: [
55
+ ...existingCookies.filter(
56
+ (cookie) => !cookieMatchesSites(cookie, normalizedSites)
57
+ ),
58
+ ...latestCookies.filter(
59
+ (cookie) => cookieMatchesSites(cookie, normalizedSites)
60
+ )
61
+ ],
62
+ origins: [
63
+ ...existingOrigins.filter(
64
+ (origin) => !originMatchesSites(origin.origin, normalizedSites)
65
+ ),
66
+ ...latestOrigins.filter(
67
+ (origin) => originMatchesSites(origin.origin, normalizedSites)
68
+ )
69
+ ]
70
+ };
71
+ }
72
+ function normalizeHost(value) {
73
+ let host = value.trim().toLowerCase();
74
+ while (host.startsWith(".")) host = host.slice(1);
75
+ while (host.endsWith(".")) host = host.slice(0, -1);
76
+ return host;
77
+ }
78
+ function cookieMatchesSites(cookie, sites) {
79
+ if (!cookie || typeof cookie !== "object" || !("domain" in cookie)) {
80
+ return false;
81
+ }
82
+ const domain = cookie.domain;
83
+ return typeof domain === "string" && cookieDomainMatchesSites(domain, sites);
84
+ }
85
+ function cookieDomainMatchesSites(cookieDomain, sites) {
86
+ const domain = normalizeHost(cookieDomain);
87
+ if (!domain) return false;
88
+ return sites.some(
89
+ (site) => domain === site || domain.endsWith(`.${site}`) || site.endsWith(`.${domain}`)
90
+ );
91
+ }
92
+ function originMatchesSites(origin, sites) {
93
+ try {
94
+ const host = normalizeHost(new URL(origin).hostname);
95
+ return sites.some((site) => host === site || host.endsWith(`.${site}`));
96
+ } catch {
97
+ return false;
98
+ }
99
+ }
100
+ export {
101
+ captureAuthProfileStorageState,
102
+ mergeAuthProfileStorageState,
103
+ normalizeAuthProfileSite,
104
+ parseAuthProfileSites
105
+ };
@@ -0,0 +1,17 @@
1
+ import { LibrettoWorkflowContext } from './workflow.js';
2
+ import 'playwright';
3
+ import 'zod';
4
+ import '../../runtime/recovery/page-fallbacks.js';
5
+ import 'ai';
6
+
7
+ type LibrettoAuthenticateOptions = {
8
+ validate: (ctx: LibrettoWorkflowContext) => Promise<boolean> | boolean;
9
+ fallback: (ctx: LibrettoWorkflowContext, credentials: Record<string, string>) => Promise<void> | void;
10
+ credentials?: Record<string, unknown>;
11
+ envPrefix?: string;
12
+ };
13
+ declare function librettoAuthenticate(ctx: LibrettoWorkflowContext, options: LibrettoAuthenticateOptions): Promise<{
14
+ usedProfile: boolean;
15
+ }>;
16
+
17
+ export { type LibrettoAuthenticateOptions, librettoAuthenticate };
@@ -0,0 +1,37 @@
1
+ async function librettoAuthenticate(ctx, options) {
2
+ if (await options.validate(ctx)) {
3
+ return { usedProfile: true };
4
+ }
5
+ const credentials = normalizeCredentials(
6
+ options.credentials ?? readCredentialsFromEnv(options.envPrefix)
7
+ );
8
+ await options.fallback(ctx, credentials);
9
+ if (!await options.validate(ctx)) {
10
+ throw new Error("Authentication fallback completed, but validation still failed.");
11
+ }
12
+ return { usedProfile: false };
13
+ }
14
+ function normalizeCredentials(credentials) {
15
+ const normalized = {};
16
+ for (const [key, value] of Object.entries(credentials)) {
17
+ if (typeof value === "string") normalized[key] = value;
18
+ }
19
+ return normalized;
20
+ }
21
+ function readCredentialsFromEnv(envPrefix = "LIBRETTO_") {
22
+ const credentials = {};
23
+ for (const [key, value] of Object.entries(process.env)) {
24
+ if (key.startsWith(envPrefix) && value !== void 0) {
25
+ const credentialName = key.slice(envPrefix.length).toLowerCase();
26
+ if (isLibrettoControlCredential(envPrefix, credentialName)) continue;
27
+ credentials[credentialName] = value;
28
+ }
29
+ }
30
+ return credentials;
31
+ }
32
+ function isLibrettoControlCredential(envPrefix, credentialName) {
33
+ return envPrefix === "LIBRETTO_" && ["api_key", "api_url", "timeout_seconds"].includes(credentialName);
34
+ }
35
+ export {
36
+ librettoAuthenticate
37
+ };
@@ -0,0 +1,5 @@
1
+ declare function readCredentialInputsFromEnv(env?: NodeJS.ProcessEnv): Record<string, string>;
2
+ declare function normalizeCredentialNames(names: readonly string[] | undefined): string[];
3
+ declare function mergeCredentialsIntoInput(input: unknown, credentialNames?: readonly string[], env?: NodeJS.ProcessEnv): unknown;
4
+
5
+ export { mergeCredentialsIntoInput, normalizeCredentialNames, readCredentialInputsFromEnv };
@@ -0,0 +1,68 @@
1
+ function asStringMap(value) {
2
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3
+ return {};
4
+ }
5
+ const result = {};
6
+ for (const [key, rawValue] of Object.entries(value)) {
7
+ if (typeof rawValue === "string") result[key] = rawValue;
8
+ }
9
+ return result;
10
+ }
11
+ function readHostedCredentials(input) {
12
+ if (!input || typeof input !== "object" || Array.isArray(input)) {
13
+ return {};
14
+ }
15
+ const record = input;
16
+ return asStringMap(record.credentials);
17
+ }
18
+ function readCredentialInputsFromEnv(env = process.env) {
19
+ const credentials = {};
20
+ for (const [key, value] of Object.entries(env)) {
21
+ if (!key.startsWith("LIBRETTO_CLOUD_") || value === void 0) continue;
22
+ if (value.trim().length === 0) continue;
23
+ const name = key.slice("LIBRETTO_CLOUD_".length).toLowerCase();
24
+ if (!name || name === "api_key") continue;
25
+ credentials[name] = value;
26
+ }
27
+ return credentials;
28
+ }
29
+ function normalizeCredentialNames(names) {
30
+ if (!names) return [];
31
+ const normalized = /* @__PURE__ */ new Set();
32
+ for (const name of names) {
33
+ const value = name.trim().toLowerCase();
34
+ if (value.length > 0) normalized.add(value);
35
+ }
36
+ return [...normalized];
37
+ }
38
+ function filterCredentialMap(credentials, names) {
39
+ const result = {};
40
+ for (const name of names) {
41
+ if (credentials[name] !== void 0) result[name] = credentials[name];
42
+ }
43
+ return result;
44
+ }
45
+ function shouldReadCredentialInputsFromEnv(env) {
46
+ return (env.LIBRETTO_HOSTED_RUNTIME?.trim().length ?? 0) === 0;
47
+ }
48
+ function mergeCredentialsIntoInput(input, credentialNames, env = process.env) {
49
+ const normalizedNames = normalizeCredentialNames(credentialNames);
50
+ if (normalizedNames.length === 0) return input;
51
+ const existingCredentials = readHostedCredentials(input);
52
+ const envCredentials = shouldReadCredentialInputsFromEnv(env) ? readCredentialInputsFromEnv(env) : {};
53
+ const mergedCredentials = {
54
+ ...filterCredentialMap(envCredentials, normalizedNames),
55
+ ...filterCredentialMap(existingCredentials, normalizedNames)
56
+ };
57
+ if (Object.keys(mergedCredentials).length === 0) return input;
58
+ const base = input && typeof input === "object" && !Array.isArray(input) ? { ...input } : {};
59
+ return {
60
+ ...base,
61
+ credentials: mergedCredentials
62
+ };
63
+ }
64
+ export {
65
+ mergeCredentialsIntoInput,
66
+ normalizeCredentialNames,
67
+ readCredentialInputsFromEnv
68
+ };
@@ -9,9 +9,15 @@ type LibrettoWorkflowContext = {
9
9
  page: Page;
10
10
  };
11
11
  type LibrettoWorkflowHandler<Input = unknown, Output = unknown> = (ctx: LibrettoWorkflowContext, input: Input) => Promise<Output>;
12
+ type LibrettoWorkflowAuthProfile = string | {
13
+ name: string;
14
+ refresh?: boolean;
15
+ };
12
16
  type LibrettoWorkflowDefinition<InputSchema extends z.ZodType = z.ZodType<unknown>, OutputSchema extends z.ZodType = z.ZodType<unknown>> = {
13
17
  input?: InputSchema;
14
18
  output?: OutputSchema;
19
+ credentials?: readonly string[];
20
+ authProfile?: LibrettoWorkflowAuthProfile;
15
21
  recoveryAction?: RecoveryAction;
16
22
  };
17
23
  type LibrettoWorkflowOptions<InputSchema extends z.ZodType = z.ZodType<unknown>, OutputSchema extends z.ZodType = z.ZodType<unknown>> = LibrettoWorkflowDefinition<InputSchema, OutputSchema> & {
@@ -32,11 +38,17 @@ declare class LibrettoWorkflow<InputSchema extends z.ZodType = z.ZodType<unknown
32
38
  readonly name: string;
33
39
  readonly inputSchema?: InputSchema;
34
40
  readonly outputSchema?: OutputSchema;
41
+ readonly credentialNames: readonly string[];
42
+ readonly authProfileName?: string;
43
+ readonly authProfileRefresh?: boolean;
35
44
  readonly recoveryAction?: RecoveryAction;
36
45
  private readonly handler;
37
46
  constructor(name: string, options: {
38
47
  inputSchema?: InputSchema;
39
48
  outputSchema?: OutputSchema;
49
+ credentialNames?: readonly string[];
50
+ authProfileName?: string;
51
+ authProfileRefresh?: boolean;
40
52
  recoveryAction?: RecoveryAction;
41
53
  } | undefined, handler: LibrettoWorkflowHandler<z.infer<InputSchema>, z.infer<OutputSchema>>);
42
54
  run(ctx: LibrettoWorkflowContext, input: unknown): Promise<z.infer<OutputSchema>>;
@@ -46,6 +58,9 @@ type ExportedLibrettoWorkflow = {
46
58
  readonly name: string;
47
59
  readonly inputSchema?: z.ZodType;
48
60
  readonly outputSchema?: z.ZodType;
61
+ readonly credentialNames: readonly string[];
62
+ readonly authProfileName?: string;
63
+ readonly authProfileRefresh?: boolean;
49
64
  readonly recoveryAction?: RecoveryAction;
50
65
  run: (ctx: LibrettoWorkflowContext, input: unknown) => Promise<unknown>;
51
66
  };
@@ -58,4 +73,4 @@ declare function workflow<InputSchema extends z.ZodType = z.ZodType<unknown>, Ou
58
73
  declare function workflow<InputSchema extends z.ZodType = z.ZodType<unknown>, OutputSchema extends z.ZodType = z.ZodType<unknown>>(name: string, options: LibrettoWorkflowOptions<InputSchema, OutputSchema>): LibrettoWorkflow<InputSchema, OutputSchema>;
59
74
  declare function workflow<Input = unknown, Output = unknown>(name: string, handler: LibrettoWorkflowHandler<Input, Output>): LibrettoWorkflow<z.ZodType<Input>, z.ZodType<Output>>;
60
75
 
61
- export { type ExportedLibrettoWorkflow, LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, type LibrettoWorkflowContext, type LibrettoWorkflowDefinition, type LibrettoWorkflowHandler, LibrettoWorkflowInputError, type LibrettoWorkflowOptions, type WorkflowInputValidator, getDefaultWorkflowFromModuleExports, getWorkflowFromModuleExports, getWorkflowsFromModuleExports, isLibrettoWorkflow, validateWorkflowInput, workflow };
76
+ export { type ExportedLibrettoWorkflow, LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, type LibrettoWorkflowAuthProfile, type LibrettoWorkflowContext, type LibrettoWorkflowDefinition, type LibrettoWorkflowHandler, LibrettoWorkflowInputError, type LibrettoWorkflowOptions, type WorkflowInputValidator, getDefaultWorkflowFromModuleExports, getWorkflowFromModuleExports, getWorkflowsFromModuleExports, isLibrettoWorkflow, validateWorkflowInput, workflow };
@@ -1,6 +1,11 @@
1
1
  import {
2
2
  createRecoveryPage
3
3
  } from "../../runtime/recovery/page-fallbacks.js";
4
+ import { normalizeProfileName } from "./auth-profile-name.js";
5
+ import {
6
+ mergeCredentialsIntoInput,
7
+ normalizeCredentialNames
8
+ } from "./credentials.js";
4
9
  const LIBRETTO_WORKFLOW_BRAND = /* @__PURE__ */ Symbol.for("libretto.workflow");
5
10
  class LibrettoWorkflowInputError extends Error {
6
11
  workflowName;
@@ -25,10 +30,34 @@ function formatZodErrorMessage(workflowName, zodError) {
25
30
  function parseWorkflowInput(workflowName, inputSchema, input) {
26
31
  if (!inputSchema) return input;
27
32
  const result = inputSchema.safeParse(input);
28
- if (!result.success) {
29
- throw new LibrettoWorkflowInputError(workflowName, result.error);
33
+ if (result.success) {
34
+ return reattachCredentialInput(result.data, input);
35
+ }
36
+ const stripped = stripCredentialInput(input);
37
+ if (stripped !== input) {
38
+ const strippedResult = inputSchema.safeParse(stripped);
39
+ if (strippedResult.success) {
40
+ return reattachCredentialInput(strippedResult.data, input);
41
+ }
30
42
  }
31
- return result.data;
43
+ throw new LibrettoWorkflowInputError(workflowName, result.error);
44
+ }
45
+ function stripCredentialInput(input) {
46
+ if (!input || typeof input !== "object" || Array.isArray(input)) return input;
47
+ const { credentials: _credentials, ...rest } = input;
48
+ if (!("credentials" in input)) return input;
49
+ return rest;
50
+ }
51
+ function reattachCredentialInput(parsed, raw) {
52
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return parsed;
53
+ const rawRecord = raw;
54
+ const rawCredentials = rawRecord.credentials && typeof rawRecord.credentials === "object" ? rawRecord.credentials : void 0;
55
+ if (!rawCredentials) return parsed;
56
+ const parsedRecord = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? { ...parsed } : {};
57
+ return {
58
+ ...parsedRecord,
59
+ credentials: rawCredentials
60
+ };
32
61
  }
33
62
  function validateWorkflowInput(workflow2, input) {
34
63
  parseWorkflowInput(workflow2.name, workflow2.inputSchema, input);
@@ -44,17 +73,27 @@ class LibrettoWorkflow {
44
73
  // this schema to JSON Schema at build time and exposes it via
45
74
  // /v1/workflows/get so API consumers know the workflow's output shape.
46
75
  outputSchema;
76
+ credentialNames;
77
+ authProfileName;
78
+ authProfileRefresh;
47
79
  recoveryAction;
48
80
  handler;
49
81
  constructor(name, options, handler) {
50
82
  this.name = name;
51
83
  this.inputSchema = options?.inputSchema;
52
84
  this.outputSchema = options?.outputSchema;
85
+ this.credentialNames = options?.credentialNames ?? [];
86
+ this.authProfileName = options?.authProfileName;
87
+ this.authProfileRefresh = options?.authProfileRefresh;
53
88
  this.recoveryAction = options?.recoveryAction;
54
89
  this.handler = handler;
55
90
  }
56
91
  async run(ctx, input) {
57
- const parsed = parseWorkflowInput(this.name, this.inputSchema, input);
92
+ const parsed = parseWorkflowInput(
93
+ this.name,
94
+ this.inputSchema,
95
+ mergeCredentialsIntoInput(input, this.credentialNames)
96
+ );
58
97
  const workflowContext = !this.recoveryAction ? ctx : {
59
98
  ...ctx,
60
99
  page: createRecoveryPage(ctx.page, {
@@ -113,9 +152,13 @@ function getWorkflowFromModuleExports(moduleExports, workflowName) {
113
152
  return null;
114
153
  }
115
154
  function getWorkflowConstructorOptions(options) {
155
+ const authProfile = normalizeWorkflowAuthProfile(options.authProfile);
116
156
  return {
117
157
  inputSchema: options.input,
118
158
  outputSchema: options.output,
159
+ credentialNames: normalizeCredentialNames(options.credentials),
160
+ authProfileName: authProfile?.name,
161
+ authProfileRefresh: authProfile?.refresh,
119
162
  recoveryAction: options.recoveryAction
120
163
  };
121
164
  }
@@ -141,6 +184,15 @@ function workflow(name, definitionOrHandler, maybeHandler) {
141
184
  maybeHandler
142
185
  );
143
186
  }
187
+ function normalizeWorkflowAuthProfile(value) {
188
+ if (!value) return void 0;
189
+ if (typeof value === "string") return { name: normalizeProfileName(value) };
190
+ const name = normalizeProfileName(value.name);
191
+ return {
192
+ name,
193
+ ...value.refresh === void 0 ? {} : { refresh: value.refresh }
194
+ };
195
+ }
144
196
  export {
145
197
  LIBRETTO_WORKFLOW_BRAND,
146
198
  LibrettoWorkflow,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libretto",
3
- "version": "0.6.24",
3
+ "version": "0.6.25",
4
4
  "description": "AI-powered browser automation library and CLI built on Playwright",
5
5
  "license": "MIT",
6
6
  "homepage": "https://libretto.sh",
@@ -4,7 +4,7 @@ description: "Browser automation CLI for building, maintaining, and running brow
4
4
  license: MIT
5
5
  metadata:
6
6
  author: saffron-health
7
- version: "0.6.24"
7
+ version: "0.6.25"
8
8
  ---
9
9
 
10
10
  ## How Libretto Works
@@ -23,7 +23,7 @@ Full documentation is published at [libretto.sh](https://libretto.sh). Available
23
23
  - Fundamentals: [core concepts](https://libretto.sh/docs/understand-libretto/core-concepts), [how workflow generation works](https://libretto.sh/docs/understand-libretto/how-workflow-generation-works), [automation and bot detection](https://libretto.sh/docs/understand-libretto/automation-and-bot-detection), [website authentication](https://libretto.sh/docs/understand-libretto/website-authentication)
24
24
  - Workflow guides: [one-shot generation](https://libretto.sh/docs/guides/one-shot-workflow-generation), [interactive building](https://libretto.sh/docs/guides/interactive-workflow-building), [debugging workflows](https://libretto.sh/docs/guides/debugging-workflows), [convert to network requests](https://libretto.sh/docs/guides/convert-to-network-requests)
25
25
  - CLI reference: [open and connect](https://libretto.sh/docs/reference/cli/open-and-connect), [sessions](https://libretto.sh/docs/reference/cli/sessions), [profiles](https://libretto.sh/docs/reference/cli/profiles), [snapshot](https://libretto.sh/docs/reference/cli/snapshot), [exec](https://libretto.sh/docs/reference/cli/exec), [run and resume](https://libretto.sh/docs/reference/cli/run-and-resume), [session logs](https://libretto.sh/docs/reference/cli/session-logs), [pages](https://libretto.sh/docs/reference/cli/pages)
26
- - Library API: [workflow](https://libretto.sh/docs/reference/runtime/workflow), [AI extraction](https://libretto.sh/docs/reference/runtime/ai-extraction), [network requests](https://libretto.sh/docs/reference/runtime/network-requests), [file downloads](https://libretto.sh/docs/reference/runtime/file-downloads)
26
+ - Library API: [workflow](https://libretto.sh/docs/reference/runtime/workflow), [network requests](https://libretto.sh/docs/reference/runtime/network-requests), [file downloads](https://libretto.sh/docs/reference/runtime/file-downloads)
27
27
  - Libretto Cloud Hosting: [overview](https://libretto.sh/docs/libretto-cloud-hosting/overview), [authentication](https://libretto.sh/docs/libretto-cloud-hosting/authentication), [deployments](https://libretto.sh/docs/libretto-cloud-hosting/deployments), [stealth](https://libretto.sh/docs/libretto-cloud-hosting/stealth)
28
28
  - Alternative providers: [overview](https://libretto.sh/docs/alternative-providers/overview), [Kernel](https://libretto.sh/docs/alternative-providers/kernel), [Browserbase](https://libretto.sh/docs/alternative-providers/browserbase), [Steel](https://libretto.sh/docs/alternative-providers/steel), [GCP](https://libretto.sh/docs/alternative-providers/gcp), [AWS](https://libretto.sh/docs/alternative-providers/aws)
29
29
 
@@ -61,7 +61,7 @@ Prefer to enter sites at a user-facing URL (homepage, login, etc.) on the first
61
61
  - Defer repo/code review until you begin generating code, unless the user explicitly asks for it earlier.
62
62
  - Read and follow guidelines in `references/code-generation-rules.md` before generating or editing production workflow code.
63
63
  - Validation requires a successful clean `run` with confirmation of the actual returned output, not just process success. Use the same headed or headless mode that the workflow run is already using.
64
- - After validation, always show the user: (1) the output/results from the validation run, and (2) the same command so they can re-run it themselves. Include any `--params`, `--headed`, `--headless`, or `--auth-profile` flags the workflow needs.
64
+ - After validation, always show the user: (1) the output/results from the validation run, and (2) the same command so they can re-run it themselves. Include any `--params`, `--headed`, or `--headless` flags the workflow needs.
65
65
  - Treat exploration sessions as disposable unless the user explicitly wants one kept open.
66
66
  - Get explicit user confirmation before mutating actions or replaying network requests that may have side effects.
67
67
  - Never run multiple `exec` commands at the same time.
@@ -164,7 +164,6 @@ npx libretto exec --session debug-example --page <page-id> "await page.url()"
164
164
  npx libretto run ./integration.ts --params '{"status":"open"}'
165
165
  npx libretto run ./integration.ts --read-only
166
166
  npx libretto run ./integration.ts --stay-open-on-success
167
- npx libretto run ./integration.ts --auth-profile app.example.com
168
167
  ```
169
168
 
170
169
  ### `resume`
@@ -1,29 +1,79 @@
1
1
  # Auth Profiles
2
2
 
3
- Use this reference only when the user explicitly asks to save or reuse local authenticated browser state.
3
+ Use this reference when generating or maintaining workflows that need a logged-in website session.
4
4
 
5
5
  ## When to Use This
6
6
 
7
- - The site requires manual login.
8
- - The user is running workflows locally.
7
+ - The user wants to persist authentication across runs.
8
+ - The workflow should recover when saved login state is stale.
9
9
 
10
10
  ## Workflow
11
11
 
12
12
  - Open the site in headed mode.
13
13
  - Ask the user to log in manually.
14
- - Save the current session as a profile.
15
- - Reopen the site or run the workflow with that profile.
14
+ - Save the current session as a named, site-scoped profile.
15
+ - Run a workflow that declares the profile and includes fallback login logic.
16
16
 
17
17
  ## Commands
18
18
 
19
19
  ```bash
20
- npx libretto open https://app.example.com --headed
21
- npx libretto save app.example.com
22
- npx libretto run ./integration.ts --auth-profile app.example.com
20
+ # Save scoped auth state from the current Libretto session.
21
+ npx libretto save example-app --session login --sites app.example.com,auth.example.com
22
+
23
+ # List or delete hosted auth profile names.
24
+ npx libretto cloud profiles list
25
+ npx libretto cloud profiles delete example-app
26
+ ```
27
+
28
+ ## Workflow Definition
29
+
30
+ Use `authProfile` to reuse a named login profile: local runs load
31
+ `.libretto/profiles/<name>.json`, while hosted runs use provider-native profiles
32
+ that `libretto cloud deploy` registers by name without uploading local files.
33
+ Use `{ name, refresh: true }` when successful runs should persist updated
34
+ browser state back to the profile. Pair profile use with `librettoAuthenticate`
35
+ so stale local or hosted sessions can fall back to login with declared
36
+ credentials before the workflow continues.
37
+
38
+ ```typescript
39
+ import { librettoAuthenticate, workflow } from "libretto";
40
+
41
+ export default workflow("accountWorkflow", {
42
+ authProfile: {
43
+ name: "example-account",
44
+ refresh: true,
45
+ },
46
+ credentials: ["username", "password"],
47
+ async handler(ctx, input) {
48
+ const { page } = ctx;
49
+
50
+ await page.goto("https://app.example.com/dashboard");
51
+
52
+ await librettoAuthenticate(ctx, {
53
+ credentials: input.credentials,
54
+ validate: async ({ page }) =>
55
+ await page.getByRole("heading", { name: "Dashboard" })
56
+ .isVisible()
57
+ .catch(() => false),
58
+ fallback: async ({ page }, credentials) => {
59
+ await page.goto("https://app.example.com/login");
60
+ await page.getByLabel("Email").fill(credentials.username);
61
+ await page.getByLabel("Password").fill(credentials.password);
62
+ await page.getByRole("button", { name: "Sign in" }).click();
63
+ await page.getByRole("heading", { name: "Dashboard" }).waitFor();
64
+ },
65
+ });
66
+
67
+ // Continue with the signed-in workflow steps.
68
+ },
69
+ });
23
70
  ```
24
71
 
25
72
  ## Notes
26
73
 
27
- - Profiles are local to the current machine.
28
- - Sessions can expire. If the profile stops working, repeat the login and save flow.
29
- - Keep auth profiles as a brief operational detail in the main skill, not a full workflow pattern.
74
+ - Saving a profile captures cookies, localStorage, and IndexedDB only for the comma-separated `--sites` list.
75
+ - If the user explicitly wants to import from Chrome, ask which Chrome/profile
76
+ to launch or attach to and get consent before attaching because disconnecting
77
+ can close or relaunch that Chrome window. Chrome may require copying the
78
+ selected profile to a temporary user-data directory before running
79
+ `npx libretto import-chrome-profiles example-app --cdp-url http://127.0.0.1:9222 --sites app.example.com`.
@@ -40,7 +40,7 @@ export default workflow("myWorkflow", {
40
40
 
41
41
  Key points:
42
42
 
43
- - `workflow(name, { input, output, recoveryAction, handler })` takes a unique workflow name, optional Zod input/output schemas, an optional recovery action, and the async handler. The handler's `input` parameter is inferred from the input schema — do not redeclare it with a separate `type Input = ...`.
43
+ - `workflow(name, { input, output, credentials, authProfile, recoveryAction, handler })` takes a unique workflow name, optional Zod input/output schemas, optional declared credential names, an optional auth profile, an optional recovery action, and the async handler. The handler's `input` parameter is inferred from the input schema — do not redeclare it with a separate `type Input = ...`.
44
44
  - At run time, Libretto validates `input` against `inputSchema` before calling the handler. Invalid input throws a clear error listing each failing field; the workflow handler never sees malformed input.
45
45
  - `npx libretto run ./file.ts` executes the file's default-exported workflow, so always use `export default workflow(...)`.
46
46
  - `ctx` provides `session` and `page`. Use `console.log`/`console.warn`/`console.error` for logging — the runtime wraps these with structured metadata automatically.
@@ -49,6 +49,36 @@ Key points:
49
49
  - 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.
50
50
  - The browser is launched and closed automatically by the CLI. Do not launch or close it in the handler.
51
51
 
52
+ ## Workflow Credentials And Auth Profiles
53
+
54
+ Declare `credentials` for runtime secrets instead of putting them in the Zod input schema or `--params`. Only declared names are injected into `input.credentials`; local runs read matching `LIBRETTO_CLOUD_` variables from `.env`, and hosted runs read matching Libretto Cloud credentials. For example, `LIBRETTO_CLOUD_OPENAI_API_KEY` becomes `input.credentials.openai_api_key`.
55
+
56
+ ```typescript
57
+ export default workflow("sentimentWorkflow", {
58
+ credentials: ["openai_api_key"],
59
+ input: z.object({
60
+ pageUrl: z.string().url(),
61
+ }),
62
+ output: z.object({
63
+ sentiment: z.string(),
64
+ }),
65
+ async handler(ctx, input) {
66
+ const apiKey = input.credentials.openai_api_key;
67
+ // Use apiKey for server-side API calls.
68
+ return { sentiment: "neutral" };
69
+ },
70
+ });
71
+ ```
72
+
73
+ For logged-in website workflows, unless the user says otherwise, generate both:
74
+
75
+ - `authProfile: { name, refresh: true }`
76
+ - `librettoAuthenticate(...)` fallback login logic using declared credentials such as `credentials: ["username", "password"]`
77
+
78
+ After generating or validating a credentialed workflow, include the local `.env` variable names the user must set, and never print secret values.
79
+
80
+ The fallback should validate the signed-in state, log in when validation fails, and validate again before continuing. Follow `references/auth-profiles.md` for profile naming, refresh behavior, hosted profile behavior, and the fallback-login pattern.
81
+
52
82
  ## Workflow Error Handling
53
83
 
54
84
  Do not add runtime recovery by default. Add `recoveryAction` only when exploration shows nondeterministic blockers such as popups, cookie banners, modals, or overlays.
@@ -4,7 +4,7 @@ description: "Read-only Libretto workflow for diagnosing live browser state with
4
4
  license: MIT
5
5
  metadata:
6
6
  author: saffron-health
7
- version: "0.6.24"
7
+ version: "0.6.25"
8
8
  ---
9
9
 
10
10
  ## How Libretto Read-Only Works