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
@@ -21,6 +21,7 @@ import {
21
21
  import { warnIfLibrettoVersionsDiffer } from "../core/skill-version.js";
22
22
  import { SimpleCLI } from "affordance";
23
23
  import {
24
+ type SessionContext,
24
25
  sessionOption,
25
26
  withAutoSession,
26
27
  withExperiments,
@@ -80,7 +81,7 @@ export const openInput = SimpleCLI.input({
80
81
  }),
81
82
  authProfile: SimpleCLI.option(z.string().optional(), {
82
83
  name: "auth-profile",
83
- help: "Override the domain used for auth profile lookup (e.g. use login.example.com's profile when opening app.example.com)",
84
+ help: "Named auth profile to load before opening the browser",
84
85
  }),
85
86
  viewport: SimpleCLI.option(z.string().optional(), {
86
87
  help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)",
@@ -105,7 +106,7 @@ export const openCommand = SimpleCLI.command({
105
106
  })
106
107
  .input(openInput)
107
108
  .use(withAutoSession())
108
- .use(withExperiments())
109
+ .use(withExperiments<SessionContext>())
109
110
  .handle(async ({ input, ctx }) => {
110
111
  warnIfLibrettoVersionsDiffer();
111
112
  assertSessionAvailableForStart(ctx.session, ctx.logger);
@@ -119,10 +120,15 @@ export const openCommand = SimpleCLI.command({
119
120
  input.readOnly,
120
121
  input.writeAccess,
121
122
  ),
122
- authProfileDomain: input.authProfile,
123
+ authProfileName: input.authProfile,
123
124
  experiments: ctx.experiments,
124
125
  });
125
126
  } else {
127
+ if (input.authProfile) {
128
+ throw new Error(
129
+ "--auth-profile is only supported for local browser sessions. Hosted provider sessions use workflow-declared authProfile settings.",
130
+ );
131
+ }
126
132
  await runOpenWithProvider(
127
133
  input.url,
128
134
  providerName,
@@ -166,7 +172,7 @@ export const connectCommand = SimpleCLI.command({
166
172
  })
167
173
  .input(connectInput)
168
174
  .use(withAutoSession())
169
- .use(withExperiments())
175
+ .use(withExperiments<SessionContext>())
170
176
  .handle(async ({ input, ctx }) => {
171
177
  warnIfLibrettoVersionsDiffer();
172
178
  await runConnectWithLogger(
@@ -180,17 +186,17 @@ export const connectCommand = SimpleCLI.command({
180
186
 
181
187
  export const saveInput = SimpleCLI.input({
182
188
  positionals: [
183
- SimpleCLI.positional("urlOrDomain", z.string().optional(), {
184
- help: "URL or domain to save",
189
+ SimpleCLI.positional("profileName", z.string(), {
190
+ help: "Profile name to save",
185
191
  }),
186
192
  ],
187
193
  named: {
188
194
  session: sessionOption(),
195
+ sites: SimpleCLI.option(z.string(), {
196
+ help: "Comma-separated sites whose auth state should be saved",
197
+ }),
189
198
  },
190
- }).refine(
191
- (input) => Boolean(input.urlOrDomain),
192
- `Usage: libretto save <url|domain> --session <name>`,
193
- );
199
+ });
194
200
 
195
201
  export const saveCommand = SimpleCLI.command({
196
202
  description: "Save current browser session",
@@ -198,7 +204,9 @@ export const saveCommand = SimpleCLI.command({
198
204
  .input(saveInput)
199
205
  .use(withRequiredSession())
200
206
  .handle(async ({ input, ctx }) => {
201
- await runSave(input.urlOrDomain!, ctx.session, ctx.logger);
207
+ await runSave(input.profileName, ctx.session, ctx.logger, {
208
+ sites: input.sites,
209
+ });
202
210
  });
203
211
 
204
212
  export const pagesInput = SimpleCLI.input({
@@ -0,0 +1,82 @@
1
+ import { SimpleCLI } from "affordance";
2
+ import { orpcCall, resolveApiUrl } from "../core/auth-fetch.js";
3
+
4
+ const CLOUD_CREDENTIAL_ENV_PREFIX = "LIBRETTO_CLOUD_";
5
+
6
+ type UpsertCredentialResponse = {
7
+ success: true;
8
+ credential_id: string;
9
+ overwritten: boolean;
10
+ message: string;
11
+ };
12
+
13
+ function requireApiKeyCredential() {
14
+ const apiKey = process.env.LIBRETTO_API_KEY?.trim();
15
+ if (!apiKey) {
16
+ throw new Error(
17
+ "LIBRETTO_API_KEY is required to manage Libretto Cloud credentials. Issue one with `libretto cloud auth api-key issue --label <label>`.",
18
+ );
19
+ }
20
+ return {
21
+ apiUrl: resolveApiUrl(null),
22
+ credential: { source: "env-api-key" as const, apiKey },
23
+ };
24
+ }
25
+
26
+ function parseEnvCredentials(prefix: string): Array<{ name: string; value: string }> {
27
+ const credentials: Record<string, string> = {};
28
+ for (const [key, value] of Object.entries(process.env)) {
29
+ if (!key.startsWith(prefix) || value === undefined) continue;
30
+ if (value.trim().length === 0) continue;
31
+ const fieldName = key.slice(prefix.length).toLowerCase();
32
+ if (!fieldName || fieldName === "api_key") continue;
33
+ credentials[fieldName] = value;
34
+ }
35
+ return Object.entries(credentials)
36
+ .map(([name, value]) => ({ name, value }))
37
+ .sort((left, right) => left.name.localeCompare(right.name));
38
+ }
39
+
40
+ export const pushCredentialCommand = SimpleCLI.command({
41
+ description: "Push LIBRETTO_CLOUD-prefixed env credentials to Libretto Cloud",
42
+ })
43
+ .input(SimpleCLI.input({
44
+ positionals: [],
45
+ named: {},
46
+ }))
47
+ .handle(async () => {
48
+ const credentials = parseEnvCredentials(CLOUD_CREDENTIAL_ENV_PREFIX);
49
+ if (credentials.length === 0) {
50
+ throw new Error(
51
+ `No non-empty env vars found with prefix ${CLOUD_CREDENTIAL_ENV_PREFIX}.`,
52
+ );
53
+ }
54
+ const { apiUrl, credential } = requireApiKeyCredential();
55
+ let created = 0;
56
+ let updated = 0;
57
+ for (const item of credentials) {
58
+ const response = await orpcCall<UpsertCredentialResponse>({
59
+ apiUrl,
60
+ path: "/v1/credentials/upsert",
61
+ input: item,
62
+ credential,
63
+ });
64
+ if (response.overwritten) {
65
+ updated += 1;
66
+ console.log(`Updated cloud credential: ${item.name}`);
67
+ } else {
68
+ created += 1;
69
+ console.log(`Created cloud credential: ${item.name}`);
70
+ }
71
+ }
72
+ console.log(
73
+ `Pushed ${credentials.length} cloud ${credentials.length === 1 ? "credential" : "credentials"} (${created} created, ${updated} updated).`,
74
+ );
75
+ });
76
+
77
+ export const cloudCredentialCommands = SimpleCLI.group({
78
+ description: "Manage hosted credentials",
79
+ routes: {
80
+ push: pushCredentialCommand,
81
+ },
82
+ });
@@ -4,7 +4,10 @@ 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
+ type WorkflowDeployMetadata,
10
+ } from "../core/deploy-artifact.js";
8
11
  import { readAuthState } from "../core/auth-storage.js";
9
12
  import { SimpleCLI } from "affordance";
10
13
 
@@ -19,6 +22,13 @@ type DeploymentResponse = {
19
22
  };
20
23
  };
21
24
 
25
+ type EnsureProfileResponse = {
26
+ success: true;
27
+ profile_id: string;
28
+ name: string;
29
+ created: boolean;
30
+ };
31
+
22
32
  function generateDeploymentName(): string {
23
33
  return `deploy-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
24
34
  }
@@ -111,6 +121,33 @@ async function pollDeployment(
111
121
  return deployment;
112
122
  }
113
123
 
124
+ async function ensureWorkflowAuthProfiles(args: {
125
+ apiUrl: string;
126
+ credential: { source: "env-api-key"; apiKey: string };
127
+ workflows: readonly WorkflowDeployMetadata[];
128
+ }): Promise<void> {
129
+ const profileNames = [
130
+ ...new Set(
131
+ args.workflows
132
+ .map((workflow) => workflow.authProfileName?.trim())
133
+ .filter((name): name is string => Boolean(name)),
134
+ ),
135
+ ];
136
+ if (profileNames.length === 0) return;
137
+
138
+ console.log(
139
+ `Ensuring cloud auth ${profileNames.length === 1 ? "profile" : "profiles"}: ${profileNames.join(", ")}`,
140
+ );
141
+ for (const name of profileNames) {
142
+ await orpcCall<EnsureProfileResponse>({
143
+ apiUrl: args.apiUrl,
144
+ path: "/v1/browserProfiles/ensure",
145
+ input: { name },
146
+ credential: args.credential,
147
+ });
148
+ }
149
+ }
150
+
114
151
  export const deployInput = SimpleCLI.input({
115
152
  positionals: [
116
153
  SimpleCLI.positional("sourceDir", z.string().default("."), {
@@ -159,13 +196,15 @@ export const deployCommand = SimpleCLI.command({
159
196
  // a minimal manifest. Bundled code is embedded in the generated files;
160
197
  // external packages are listed in the manifest for installation.
161
198
  console.log("Bundling hosted deployment artifact...");
162
- const { entryPoint, source } = await buildHostedDeployTarball({
199
+ const { entryPoint, source, workflows } = await buildHostedDeployTarball({
163
200
  additionalExternals: input.external,
164
201
  deploymentName,
165
202
  entryPoint: input.entryPoint,
166
203
  sourceDir: input.sourceDir,
167
204
  });
168
205
 
206
+ await ensureWorkflowAuthProfiles({ apiUrl, credential, workflows });
207
+
169
208
  const createPayload: Record<string, unknown> = {
170
209
  source,
171
210
  entry_point: entryPoint,
@@ -6,10 +6,6 @@ import type { LoggerApi } from "../../shared/logger/index.js";
6
6
  import {
7
7
  connect,
8
8
  disconnectBrowser,
9
- getProfilePath,
10
- hasProfile,
11
- normalizeDomain,
12
- normalizeUrl,
13
9
  runClose,
14
10
  resolveViewport,
15
11
  } from "../core/browser.js";
@@ -32,7 +28,9 @@ import {
32
28
  getProviderStartupTimeoutMs,
33
29
  resolveProviderName,
34
30
  } from "../core/providers/index.js";
35
- import { getAbsoluteIntegrationPath } from "../core/workflow-runtime.js";
31
+ import {
32
+ getAbsoluteIntegrationPath,
33
+ } from "../core/workflow-runtime.js";
36
34
  import {
37
35
  compileExecFunction,
38
36
  stripEmptyCatchHandlers,
@@ -48,12 +46,13 @@ import {
48
46
  readActionLog,
49
47
  readNetworkLog,
50
48
  wrapPageForActionLogging,
51
- } from "../core/telemetry.js";
49
+ } from "../core/session-logs.js";
52
50
  import type { SessionAccessMode } from "../../shared/state/index.js";
53
51
  import type { Experiments } from "../core/experiments.js";
54
52
  import { SimpleCLI } from "affordance";
55
53
  import {
56
54
  pageOption,
55
+ type SessionContext,
57
56
  sessionOption,
58
57
  withAutoSession,
59
58
  withExperiments,
@@ -68,7 +67,6 @@ type RunIntegrationCommandRequest = {
68
67
  visualize: boolean;
69
68
  viewport?: { width: number; height: number };
70
69
  accessMode: SessionAccessMode;
71
- authProfileDomain?: string;
72
70
  providerName?: string;
73
71
  stayOpenOnSuccess: boolean;
74
72
  tsconfigPath?: string;
@@ -564,22 +562,6 @@ async function runIntegrationFromFile(
564
562
  const absoluteIntegrationPath = getAbsoluteIntegrationPath(
565
563
  args.integrationPath,
566
564
  );
567
- if (args.authProfileDomain) {
568
- const normalizedDomain = normalizeDomain(normalizeUrl(args.authProfileDomain));
569
- if (!hasProfile(normalizedDomain)) {
570
- const profilePath = getProfilePath(normalizedDomain);
571
- throw new Error(
572
- [
573
- `Local auth profile not found for domain "${normalizedDomain}".`,
574
- `Expected profile file: ${profilePath}`,
575
- "To create it:",
576
- ` 1. libretto open https://${normalizedDomain} --headed --session ${args.session}`,
577
- " 2. Log in manually in the browser window.",
578
- ` 3. libretto save ${normalizedDomain} --session ${args.session}`,
579
- ].join("\n"),
580
- );
581
- }
582
- }
583
565
 
584
566
  const runLogPath = logFileForSession(args.session);
585
567
  const workflowOutcome = createDeferred<WorkflowOutcome>();
@@ -594,7 +576,10 @@ async function runIntegrationFromFile(
594
576
  session: args.session,
595
577
  experiments: args.experiments,
596
578
  browser: args.providerName
597
- ? { kind: "provider", providerName: args.providerName }
579
+ ? {
580
+ kind: "provider",
581
+ providerName: args.providerName,
582
+ }
598
583
  : {
599
584
  kind: "launch",
600
585
  headed: !args.headless,
@@ -606,7 +591,6 @@ async function runIntegrationFromFile(
606
591
  visualize: args.visualize,
607
592
  stayOpenOnSuccess: args.stayOpenOnSuccess,
608
593
  tsconfigPath: args.tsconfigPath,
609
- authProfileDomain: args.authProfileDomain,
610
594
  },
611
595
  },
612
596
  logger,
@@ -809,10 +793,6 @@ export const runInput = SimpleCLI.input({
809
793
  name: "stay-open-on-success",
810
794
  help: "Keep the browser session open after the workflow completes successfully",
811
795
  }),
812
- authProfile: SimpleCLI.option(z.string().optional(), {
813
- name: "auth-profile",
814
- help: "Domain for local auth profile (e.g. apps.example.com)",
815
- }),
816
796
  viewport: SimpleCLI.option(z.string().optional(), {
817
797
  help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)",
818
798
  }),
@@ -865,7 +845,7 @@ export const runCommand = SimpleCLI.command({
865
845
  })
866
846
  .input(runInput)
867
847
  .use(withAutoSession())
868
- .use(withExperiments())
848
+ .use(withExperiments<SessionContext>())
869
849
  .handle(async ({ input, ctx }) => {
870
850
  warnIfLibrettoVersionsDiffer();
871
851
  await stopExistingFailedRunSession(ctx.session, ctx.logger);
@@ -903,7 +883,6 @@ export const runCommand = SimpleCLI.command({
903
883
  tsconfigPath: input.tsconfig,
904
884
  headless: daemonProviderName ? true : (headlessMode ?? false),
905
885
  visualize,
906
- authProfileDomain: input.authProfile,
907
886
  viewport,
908
887
  accessMode: input.readOnly ? "read-only" : input.writeAccess ? "write-access" : (readLibrettoConfig().sessionMode ?? "write-access"),
909
888
  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
+
7
+ export const importChromeProfilesCommand = SimpleCLI.command({
8
+ description: "Fetch scoped auth state from a Chrome CDP session into a local profile",
9
+ })
10
+ .input(SimpleCLI.input({
11
+ positionals: [
12
+ SimpleCLI.positional("profileName", z.string(), {
13
+ help: "Profile name to save",
14
+ }),
15
+ ],
16
+ named: {
17
+ cdpUrl: SimpleCLI.option(z.string(), {
18
+ name: "cdp-url",
19
+ help: "Chrome DevTools Protocol endpoint for the Chrome instance",
20
+ }),
21
+ sites: SimpleCLI.option(z.string(), {
22
+ help: "Comma-separated sites whose auth state should be imported",
23
+ }),
24
+ yes: SimpleCLI.flag({
25
+ help: "Skip confirmation before attaching to and disconnecting from Chrome",
26
+ }),
27
+ },
28
+ }))
29
+ .handle(async ({ input }) => {
30
+ const logger = createLoggerForSession(`profile-fetch-${Date.now()}`);
31
+ try {
32
+ if (!input.yes) {
33
+ const confirmed = await promptConfirm(
34
+ "Importing from an existing Chrome CDP session may cause that Chrome window to close or relaunch when Libretto disconnects. Continue?",
35
+ );
36
+ if (!confirmed) {
37
+ throw new Error("Aborted Chrome profile import.");
38
+ }
39
+ }
40
+ await runFetchChromeProfile(input.profileName, input.cdpUrl, logger, {
41
+ sites: input.sites,
42
+ });
43
+ } finally {
44
+ await logger.close();
45
+ }
46
+ });
@@ -0,0 +1,90 @@
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
+
6
+ type ListProfilesResponse = {
7
+ profiles: Array<{
8
+ profile_id: string;
9
+ name: string;
10
+ providers: string[];
11
+ updated_at: string;
12
+ }>;
13
+ };
14
+
15
+ type DeleteProfileResponse = {
16
+ success: boolean;
17
+ deleted_count: number;
18
+ };
19
+
20
+ function requireApiKeyCredential() {
21
+ const apiKey = process.env.LIBRETTO_API_KEY?.trim();
22
+ if (!apiKey) {
23
+ throw new Error(
24
+ "LIBRETTO_API_KEY is required to manage Libretto Cloud profiles. Issue one with `libretto cloud auth api-key issue --label <label>`.",
25
+ );
26
+ }
27
+ return {
28
+ apiUrl: resolveApiUrl(null),
29
+ credential: { source: "env-api-key" as const, apiKey },
30
+ };
31
+ }
32
+
33
+ export const listProfilesCommand = SimpleCLI.command({
34
+ description: "List Libretto Cloud auth profiles",
35
+ })
36
+ .input(SimpleCLI.input({ positionals: [], named: {} }))
37
+ .handle(async () => {
38
+ const { apiUrl, credential } = requireApiKeyCredential();
39
+ const response = await orpcCall<ListProfilesResponse>({
40
+ apiUrl,
41
+ path: "/v1/browserProfiles/list",
42
+ input: {},
43
+ credential,
44
+ });
45
+ if (response.profiles.length === 0) {
46
+ console.log("No cloud profiles found.");
47
+ return;
48
+ }
49
+ for (const profile of response.profiles) {
50
+ const providers = profile.providers.length
51
+ ? ` (${profile.providers.join(", ")})`
52
+ : "";
53
+ console.log(`${profile.name}${providers}`);
54
+ }
55
+ });
56
+
57
+ export const deleteProfileCommand = SimpleCLI.command({
58
+ description: "Delete a Libretto Cloud auth profile",
59
+ })
60
+ .input(SimpleCLI.input({
61
+ positionals: [
62
+ SimpleCLI.positional("profileName", z.string(), {
63
+ help: "Cloud profile name to delete",
64
+ }),
65
+ ],
66
+ named: {},
67
+ }))
68
+ .handle(async ({ input }) => {
69
+ const profileName = normalizeProfileName(input.profileName);
70
+ const { apiUrl, credential } = requireApiKeyCredential();
71
+ const response = await orpcCall<DeleteProfileResponse>({
72
+ apiUrl,
73
+ path: "/v1/browserProfiles/delete",
74
+ input: { name: profileName },
75
+ credential,
76
+ });
77
+ if (!response.success || response.deleted_count === 0) {
78
+ console.log(`No cloud profile found for ${profileName}.`);
79
+ return;
80
+ }
81
+ console.log(`Deleted cloud profile: ${profileName}`);
82
+ });
83
+
84
+ export const profileCommands = SimpleCLI.group({
85
+ description: "Manage hosted browser auth profiles",
86
+ routes: {
87
+ list: listProfilesCommand,
88
+ delete: deleteProfileCommand,
89
+ },
90
+ });
@@ -11,7 +11,6 @@ import {
11
11
  } from "../core/session.js";
12
12
  import {
13
13
  SimpleCLI,
14
- type SimpleCLIMiddlewareArgs,
15
14
  type SimpleCLIContext,
16
15
  type SimpleCLIMiddleware,
17
16
  } from "affordance";
@@ -41,13 +40,10 @@ export type ExperimentsContext = {
41
40
  experiments: Experiments;
42
41
  };
43
42
 
44
- export function withExperiments() {
45
- return async <TInput, TContext extends SimpleCLIContext>({
46
- ctx,
47
- }: SimpleCLIMiddlewareArgs<
48
- TInput,
49
- TContext
50
- >): Promise<TContext & ExperimentsContext> => ({
43
+ export function withExperiments<
44
+ TContext extends SimpleCLIContext,
45
+ >(): SimpleCLIMiddleware<unknown, TContext, TContext & ExperimentsContext> {
46
+ return async ({ ctx }) => ({
51
47
  ...ctx,
52
48
  experiments: resolveExperiments(),
53
49
  });