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
package/README.md CHANGED
@@ -93,6 +93,14 @@ Run `npx libretto help` for the full list of commands.
93
93
 
94
94
  All Libretto state lives in a `.libretto/` directory at your project root. See the [configuration docs](https://libretto.sh/docs/understand-libretto/configuration) for details on config files, sessions, and profiles.
95
95
 
96
+ ## Telemetry
97
+
98
+ Libretto records anonymous CLI telemetry to help understand CLI usage and help us prioritize improvements. Each resolved command can send only an install id, timestamp, command event name such as `libretto run`, and an error boolean. Libretto does not send command arguments, URLs, project paths, auth state, API keys, error messages or details, or user identity.
99
+
100
+ The install id is stored in the telemetry file at `~/.libretto/telemetry.json`. The implementation lives in [`src/cli/core/telemetry.ts`](src/cli/core/telemetry.ts).
101
+
102
+ To disable telemetry, set `LIBRETTO_TELEMETRY_DISABLED=1`, set `DO_NOT_TRACK=1`, run with `CI=1`, or edit `~/.libretto/telemetry.json` and set `"enabled": false`.
103
+
96
104
  ## Join the Community
97
105
 
98
106
  Join our Discord to connect with other developers, get help, and share what you've built:
@@ -119,7 +127,7 @@ pnpm test
119
127
  Source layout:
120
128
 
121
129
  - `src/cli/` — CLI commands
122
- - `src/runtime/` — browser runtime (network, recovery, downloads, extraction)
130
+ - `src/runtime/` — browser runtime (network, recovery, downloads)
123
131
  - `src/shared/` — shared utilities (config, LLM client, logging, state)
124
132
  - `test/` — test files (`*.spec.ts`)
125
133
  - `README.template.md` — source of truth for the repo and package READMEs
@@ -91,6 +91,14 @@ Run `npx libretto help` for the full list of commands.
91
91
 
92
92
  All Libretto state lives in a `.libretto/` directory at your project root. See the [configuration docs](https://libretto.sh/docs/understand-libretto/configuration) for details on config files, sessions, and profiles.
93
93
 
94
+ ## Telemetry
95
+
96
+ Libretto records anonymous CLI telemetry to help understand CLI usage and help us prioritize improvements. Each resolved command can send only an install id, timestamp, command event name such as `libretto run`, and an error boolean. Libretto does not send command arguments, URLs, project paths, auth state, API keys, error messages or details, or user identity.
97
+
98
+ The install id is stored in the telemetry file at `~/.libretto/telemetry.json`. The implementation lives in [`{{LIBRETTO_PATH_PREFIX}}src/cli/core/telemetry.ts`]({{LIBRETTO_PATH_PREFIX}}src/cli/core/telemetry.ts).
99
+
100
+ To disable telemetry, set `LIBRETTO_TELEMETRY_DISABLED=1`, set `DO_NOT_TRACK=1`, run with `CI=1`, or edit `~/.libretto/telemetry.json` and set `"enabled": false`.
101
+
94
102
  ## Join the Community
95
103
 
96
104
  Join our Discord to connect with other developers, get help, and share what you've built:
@@ -117,7 +125,7 @@ pnpm test
117
125
  Source layout:
118
126
 
119
127
  - `{{LIBRETTO_PATH_PREFIX}}src/cli/` — CLI commands
120
- - `{{LIBRETTO_PATH_PREFIX}}src/runtime/` — browser runtime (network, recovery, downloads, extraction)
128
+ - `{{LIBRETTO_PATH_PREFIX}}src/runtime/` — browser runtime (network, recovery, downloads)
121
129
  - `{{LIBRETTO_PATH_PREFIX}}src/shared/` — shared utilities (config, LLM client, logging, state)
122
130
  - `{{LIBRETTO_PATH_PREFIX}}test/` — test files (`*.spec.ts`)
123
131
  - `{{LIBRETTO_PATH_PREFIX}}README.template.md` — source of truth for the repo and package READMEs
@@ -68,7 +68,7 @@ const openInput = SimpleCLI.input({
68
68
  }),
69
69
  authProfile: SimpleCLI.option(z.string().optional(), {
70
70
  name: "auth-profile",
71
- help: "Override the domain used for auth profile lookup (e.g. use login.example.com's profile when opening app.example.com)"
71
+ help: "Named auth profile to load before opening the browser"
72
72
  }),
73
73
  viewport: SimpleCLI.option(z.string().optional(), {
74
74
  help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)"
@@ -100,10 +100,15 @@ const openCommand = SimpleCLI.command({
100
100
  input.readOnly,
101
101
  input.writeAccess
102
102
  ),
103
- authProfileDomain: input.authProfile,
103
+ authProfileName: input.authProfile,
104
104
  experiments: ctx.experiments
105
105
  });
106
106
  } else {
107
+ if (input.authProfile) {
108
+ throw new Error(
109
+ "--auth-profile is only supported for local browser sessions. Hosted provider sessions use workflow-declared authProfile settings."
110
+ );
111
+ }
107
112
  await runOpenWithProvider(
108
113
  input.url,
109
114
  providerName,
@@ -152,21 +157,23 @@ const connectCommand = SimpleCLI.command({
152
157
  });
153
158
  const saveInput = SimpleCLI.input({
154
159
  positionals: [
155
- SimpleCLI.positional("urlOrDomain", z.string().optional(), {
156
- help: "URL or domain to save"
160
+ SimpleCLI.positional("profileName", z.string(), {
161
+ help: "Profile name to save"
157
162
  })
158
163
  ],
159
164
  named: {
160
- session: sessionOption()
165
+ session: sessionOption(),
166
+ sites: SimpleCLI.option(z.string(), {
167
+ help: "Comma-separated sites whose auth state should be saved"
168
+ })
161
169
  }
162
- }).refine(
163
- (input) => Boolean(input.urlOrDomain),
164
- `Usage: libretto save <url|domain> --session <name>`
165
- );
170
+ });
166
171
  const saveCommand = SimpleCLI.command({
167
172
  description: "Save current browser session"
168
173
  }).input(saveInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
169
- await runSave(input.urlOrDomain, ctx.session, ctx.logger);
174
+ await runSave(input.profileName, ctx.session, ctx.logger, {
175
+ sites: input.sites
176
+ });
170
177
  });
171
178
  const pagesInput = SimpleCLI.input({
172
179
  positionals: [],
@@ -0,0 +1,70 @@
1
+ import { SimpleCLI } from "affordance";
2
+ import { orpcCall, resolveApiUrl } from "../core/auth-fetch.js";
3
+ const CLOUD_CREDENTIAL_ENV_PREFIX = "LIBRETTO_CLOUD_";
4
+ function requireApiKeyCredential() {
5
+ const apiKey = process.env.LIBRETTO_API_KEY?.trim();
6
+ if (!apiKey) {
7
+ throw new Error(
8
+ "LIBRETTO_API_KEY is required to manage Libretto Cloud credentials. Issue one with `libretto cloud auth api-key issue --label <label>`."
9
+ );
10
+ }
11
+ return {
12
+ apiUrl: resolveApiUrl(null),
13
+ credential: { source: "env-api-key", apiKey }
14
+ };
15
+ }
16
+ function parseEnvCredentials(prefix) {
17
+ const credentials = {};
18
+ for (const [key, value] of Object.entries(process.env)) {
19
+ if (!key.startsWith(prefix) || value === void 0) continue;
20
+ if (value.trim().length === 0) continue;
21
+ const fieldName = key.slice(prefix.length).toLowerCase();
22
+ if (!fieldName || fieldName === "api_key") continue;
23
+ credentials[fieldName] = value;
24
+ }
25
+ return Object.entries(credentials).map(([name, value]) => ({ name, value })).sort((left, right) => left.name.localeCompare(right.name));
26
+ }
27
+ const pushCredentialCommand = SimpleCLI.command({
28
+ description: "Push LIBRETTO_CLOUD-prefixed env credentials to Libretto Cloud"
29
+ }).input(SimpleCLI.input({
30
+ positionals: [],
31
+ named: {}
32
+ })).handle(async () => {
33
+ const credentials = parseEnvCredentials(CLOUD_CREDENTIAL_ENV_PREFIX);
34
+ if (credentials.length === 0) {
35
+ throw new Error(
36
+ `No non-empty env vars found with prefix ${CLOUD_CREDENTIAL_ENV_PREFIX}.`
37
+ );
38
+ }
39
+ const { apiUrl, credential } = requireApiKeyCredential();
40
+ let created = 0;
41
+ let updated = 0;
42
+ for (const item of credentials) {
43
+ const response = await orpcCall({
44
+ apiUrl,
45
+ path: "/v1/credentials/upsert",
46
+ input: item,
47
+ credential
48
+ });
49
+ if (response.overwritten) {
50
+ updated += 1;
51
+ console.log(`Updated cloud credential: ${item.name}`);
52
+ } else {
53
+ created += 1;
54
+ console.log(`Created cloud credential: ${item.name}`);
55
+ }
56
+ }
57
+ console.log(
58
+ `Pushed ${credentials.length} cloud ${credentials.length === 1 ? "credential" : "credentials"} (${created} created, ${updated} updated).`
59
+ );
60
+ });
61
+ const cloudCredentialCommands = SimpleCLI.group({
62
+ description: "Manage hosted credentials",
63
+ routes: {
64
+ push: pushCredentialCommand
65
+ }
66
+ });
67
+ export {
68
+ cloudCredentialCommands,
69
+ pushCredentialCommand
70
+ };
@@ -4,7 +4,9 @@ import {
4
4
  orpcCall,
5
5
  resolveApiUrl
6
6
  } from "../core/auth-fetch.js";
7
- import { buildHostedDeployTarball } from "../core/deploy-artifact.js";
7
+ import {
8
+ buildHostedDeployTarball
9
+ } from "../core/deploy-artifact.js";
8
10
  import { readAuthState } from "../core/auth-storage.js";
9
11
  import { SimpleCLI } from "affordance";
10
12
  function generateDeploymentName() {
@@ -79,6 +81,25 @@ async function pollDeployment(apiUrl, credential, deploymentId, pollIntervalMs,
79
81
  }
80
82
  return deployment;
81
83
  }
84
+ async function ensureWorkflowAuthProfiles(args) {
85
+ const profileNames = [
86
+ ...new Set(
87
+ args.workflows.map((workflow) => workflow.authProfileName?.trim()).filter((name) => Boolean(name))
88
+ )
89
+ ];
90
+ if (profileNames.length === 0) return;
91
+ console.log(
92
+ `Ensuring cloud auth ${profileNames.length === 1 ? "profile" : "profiles"}: ${profileNames.join(", ")}`
93
+ );
94
+ for (const name of profileNames) {
95
+ await orpcCall({
96
+ apiUrl: args.apiUrl,
97
+ path: "/v1/browserProfiles/ensure",
98
+ input: { name },
99
+ credential: args.credential
100
+ });
101
+ }
102
+ }
82
103
  const deployInput = SimpleCLI.input({
83
104
  positionals: [
84
105
  SimpleCLI.positional("sourceDir", z.string().default("."), {
@@ -113,12 +134,13 @@ const deployCommand = SimpleCLI.command({
113
134
  const { apiUrl, credential } = await requireDeployApiKey();
114
135
  const deploymentName = generateDeploymentName();
115
136
  console.log("Bundling hosted deployment artifact...");
116
- const { entryPoint, source } = await buildHostedDeployTarball({
137
+ const { entryPoint, source, workflows } = await buildHostedDeployTarball({
117
138
  additionalExternals: input.external,
118
139
  deploymentName,
119
140
  entryPoint: input.entryPoint,
120
141
  sourceDir: input.sourceDir
121
142
  });
143
+ await ensureWorkflowAuthProfiles({ apiUrl, credential, workflows });
122
144
  const createPayload = {
123
145
  source,
124
146
  entry_point: entryPoint
@@ -5,10 +5,6 @@ import { installInstrumentation } from "../../shared/instrumentation/index.js";
5
5
  import {
6
6
  connect,
7
7
  disconnectBrowser,
8
- getProfilePath,
9
- hasProfile,
10
- normalizeDomain,
11
- normalizeUrl,
12
8
  runClose,
13
9
  resolveViewport
14
10
  } from "../core/browser.js";
@@ -30,7 +26,9 @@ import {
30
26
  getProviderStartupTimeoutMs,
31
27
  resolveProviderName
32
28
  } from "../core/providers/index.js";
33
- import { getAbsoluteIntegrationPath } from "../core/workflow-runtime.js";
29
+ import {
30
+ getAbsoluteIntegrationPath
31
+ } from "../core/workflow-runtime.js";
34
32
  import {
35
33
  compileExecFunction,
36
34
  stripEmptyCatchHandlers
@@ -43,7 +41,7 @@ import {
43
41
  readActionLog,
44
42
  readNetworkLog,
45
43
  wrapPageForActionLogging
46
- } from "../core/telemetry.js";
44
+ } from "../core/session-logs.js";
47
45
  import { SimpleCLI } from "affordance";
48
46
  import {
49
47
  pageOption,
@@ -417,22 +415,6 @@ async function runIntegrationFromFile(args, logger) {
417
415
  const absoluteIntegrationPath = getAbsoluteIntegrationPath(
418
416
  args.integrationPath
419
417
  );
420
- if (args.authProfileDomain) {
421
- const normalizedDomain = normalizeDomain(normalizeUrl(args.authProfileDomain));
422
- if (!hasProfile(normalizedDomain)) {
423
- const profilePath = getProfilePath(normalizedDomain);
424
- throw new Error(
425
- [
426
- `Local auth profile not found for domain "${normalizedDomain}".`,
427
- `Expected profile file: ${profilePath}`,
428
- "To create it:",
429
- ` 1. libretto open https://${normalizedDomain} --headed --session ${args.session}`,
430
- " 2. Log in manually in the browser window.",
431
- ` 3. libretto save ${normalizedDomain} --session ${args.session}`
432
- ].join("\n")
433
- );
434
- }
435
- }
436
418
  const runLogPath = logFileForSession(args.session);
437
419
  const workflowOutcome = createDeferred();
438
420
  const handlers = createWorkflowHandlers(workflowOutcome.resolve);
@@ -445,7 +427,10 @@ async function runIntegrationFromFile(args, logger) {
445
427
  config: {
446
428
  session: args.session,
447
429
  experiments: args.experiments,
448
- browser: args.providerName ? { kind: "provider", providerName: args.providerName } : {
430
+ browser: args.providerName ? {
431
+ kind: "provider",
432
+ providerName: args.providerName
433
+ } : {
449
434
  kind: "launch",
450
435
  headed: !args.headless,
451
436
  viewport: args.viewport ?? { width: 1366, height: 768 }
@@ -455,8 +440,7 @@ async function runIntegrationFromFile(args, logger) {
455
440
  params: args.params,
456
441
  visualize: args.visualize,
457
442
  stayOpenOnSuccess: args.stayOpenOnSuccess,
458
- tsconfigPath: args.tsconfigPath,
459
- authProfileDomain: args.authProfileDomain
443
+ tsconfigPath: args.tsconfigPath
460
444
  }
461
445
  },
462
446
  logger,
@@ -644,10 +628,6 @@ const runInput = SimpleCLI.input({
644
628
  name: "stay-open-on-success",
645
629
  help: "Keep the browser session open after the workflow completes successfully"
646
630
  }),
647
- authProfile: SimpleCLI.option(z.string().optional(), {
648
- name: "auth-profile",
649
- help: "Domain for local auth profile (e.g. apps.example.com)"
650
- }),
651
631
  viewport: SimpleCLI.option(z.string().optional(), {
652
632
  help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)"
653
633
  }),
@@ -718,7 +698,6 @@ const runCommand = SimpleCLI.command({
718
698
  tsconfigPath: input.tsconfig,
719
699
  headless: daemonProviderName ? true : headlessMode ?? false,
720
700
  visualize,
721
- authProfileDomain: input.authProfile,
722
701
  viewport,
723
702
  accessMode: input.readOnly ? "read-only" : input.writeAccess ? "write-access" : readLibrettoConfig().sessionMode ?? "write-access",
724
703
  providerName: daemonProviderName,
@@ -0,0 +1,46 @@
1
+ import { z } from "zod";
2
+ import { SimpleCLI } from "affordance";
3
+ import { createLoggerForSession } from "../core/context.js";
4
+ import { runFetchChromeProfile } from "../core/browser.js";
5
+ import { promptConfirm } from "../core/prompt.js";
6
+ const importChromeProfilesCommand = SimpleCLI.command({
7
+ description: "Fetch scoped auth state from a Chrome CDP session into a local profile"
8
+ }).input(SimpleCLI.input({
9
+ positionals: [
10
+ SimpleCLI.positional("profileName", z.string(), {
11
+ help: "Profile name to save"
12
+ })
13
+ ],
14
+ named: {
15
+ cdpUrl: SimpleCLI.option(z.string(), {
16
+ name: "cdp-url",
17
+ help: "Chrome DevTools Protocol endpoint for the Chrome instance"
18
+ }),
19
+ sites: SimpleCLI.option(z.string(), {
20
+ help: "Comma-separated sites whose auth state should be imported"
21
+ }),
22
+ yes: SimpleCLI.flag({
23
+ help: "Skip confirmation before attaching to and disconnecting from Chrome"
24
+ })
25
+ }
26
+ })).handle(async ({ input }) => {
27
+ const logger = createLoggerForSession(`profile-fetch-${Date.now()}`);
28
+ try {
29
+ if (!input.yes) {
30
+ const confirmed = await promptConfirm(
31
+ "Importing from an existing Chrome CDP session may cause that Chrome window to close or relaunch when Libretto disconnects. Continue?"
32
+ );
33
+ if (!confirmed) {
34
+ throw new Error("Aborted Chrome profile import.");
35
+ }
36
+ }
37
+ await runFetchChromeProfile(input.profileName, input.cdpUrl, logger, {
38
+ sites: input.sites
39
+ });
40
+ } finally {
41
+ await logger.close();
42
+ }
43
+ });
44
+ export {
45
+ importChromeProfilesCommand
46
+ };
@@ -0,0 +1,71 @@
1
+ import { z } from "zod";
2
+ import { SimpleCLI } from "affordance";
3
+ import { orpcCall, resolveApiUrl } from "../core/auth-fetch.js";
4
+ import { normalizeProfileName } from "../core/profiles.js";
5
+ function requireApiKeyCredential() {
6
+ const apiKey = process.env.LIBRETTO_API_KEY?.trim();
7
+ if (!apiKey) {
8
+ throw new Error(
9
+ "LIBRETTO_API_KEY is required to manage Libretto Cloud profiles. Issue one with `libretto cloud auth api-key issue --label <label>`."
10
+ );
11
+ }
12
+ return {
13
+ apiUrl: resolveApiUrl(null),
14
+ credential: { source: "env-api-key", apiKey }
15
+ };
16
+ }
17
+ const listProfilesCommand = SimpleCLI.command({
18
+ description: "List Libretto Cloud auth profiles"
19
+ }).input(SimpleCLI.input({ positionals: [], named: {} })).handle(async () => {
20
+ const { apiUrl, credential } = requireApiKeyCredential();
21
+ const response = await orpcCall({
22
+ apiUrl,
23
+ path: "/v1/browserProfiles/list",
24
+ input: {},
25
+ credential
26
+ });
27
+ if (response.profiles.length === 0) {
28
+ console.log("No cloud profiles found.");
29
+ return;
30
+ }
31
+ for (const profile of response.profiles) {
32
+ const providers = profile.providers.length ? ` (${profile.providers.join(", ")})` : "";
33
+ console.log(`${profile.name}${providers}`);
34
+ }
35
+ });
36
+ const deleteProfileCommand = SimpleCLI.command({
37
+ description: "Delete a Libretto Cloud auth profile"
38
+ }).input(SimpleCLI.input({
39
+ positionals: [
40
+ SimpleCLI.positional("profileName", z.string(), {
41
+ help: "Cloud profile name to delete"
42
+ })
43
+ ],
44
+ named: {}
45
+ })).handle(async ({ input }) => {
46
+ const profileName = normalizeProfileName(input.profileName);
47
+ const { apiUrl, credential } = requireApiKeyCredential();
48
+ const response = await orpcCall({
49
+ apiUrl,
50
+ path: "/v1/browserProfiles/delete",
51
+ input: { name: profileName },
52
+ credential
53
+ });
54
+ if (!response.success || response.deleted_count === 0) {
55
+ console.log(`No cloud profile found for ${profileName}.`);
56
+ return;
57
+ }
58
+ console.log(`Deleted cloud profile: ${profileName}`);
59
+ });
60
+ const profileCommands = SimpleCLI.group({
61
+ description: "Manage hosted browser auth profiles",
62
+ routes: {
63
+ list: listProfilesCommand,
64
+ delete: deleteProfileCommand
65
+ }
66
+ });
67
+ export {
68
+ deleteProfileCommand,
69
+ listProfilesCommand,
70
+ profileCommands
71
+ };
@@ -19,9 +19,7 @@ function integerOption(help) {
19
19
  return SimpleCLI.option(z.coerce.number().int().optional(), { help });
20
20
  }
21
21
  function withExperiments() {
22
- return async ({
23
- ctx
24
- }) => ({
22
+ return async ({ ctx }) => ({
25
23
  ...ctx,
26
24
  experiments: resolveExperiments()
27
25
  });