libretto 0.6.13 → 0.6.15

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 (57) hide show
  1. package/dist/cli/commands/auth.js +43 -33
  2. package/dist/cli/commands/billing.js +3 -5
  3. package/dist/cli/commands/browser.js +3 -6
  4. package/dist/cli/commands/deploy.js +54 -45
  5. package/dist/cli/commands/execution.js +7 -4
  6. package/dist/cli/commands/experiments.js +1 -1
  7. package/dist/cli/commands/setup.js +1 -1
  8. package/dist/cli/commands/shared.js +1 -1
  9. package/dist/cli/commands/snapshot.js +1 -1
  10. package/dist/cli/commands/status.js +1 -1
  11. package/dist/cli/core/auth-fetch.js +11 -6
  12. package/dist/cli/core/browser.js +10 -5
  13. package/dist/cli/core/daemon/daemon.js +63 -10
  14. package/dist/cli/core/daemon/exec-repl.js +133 -0
  15. package/dist/cli/core/daemon/exec.js +6 -21
  16. package/dist/cli/core/daemon/ipc.js +47 -4
  17. package/dist/cli/core/daemon/ipc.spec.js +21 -0
  18. package/dist/cli/core/exec-compiler.js +8 -3
  19. package/dist/cli/core/providers/index.js +13 -4
  20. package/dist/cli/core/providers/kernel.js +3 -3
  21. package/dist/cli/core/providers/libretto-cloud.js +178 -26
  22. package/dist/cli/router.js +9 -4
  23. package/dist/shared/ipc/socket-transport.d.ts +2 -1
  24. package/dist/shared/ipc/socket-transport.js +16 -5
  25. package/dist/shared/ipc/socket-transport.spec.js +5 -0
  26. package/package.json +2 -2
  27. package/skills/libretto/SKILL.md +33 -29
  28. package/skills/libretto/references/code-generation-rules.md +6 -0
  29. package/skills/libretto/references/configuration-file-reference.md +8 -0
  30. package/skills/libretto/references/site-security-review.md +6 -6
  31. package/skills/libretto-readonly/SKILL.md +1 -1
  32. package/src/cli/commands/auth.ts +46 -33
  33. package/src/cli/commands/billing.ts +3 -5
  34. package/src/cli/commands/browser.ts +5 -9
  35. package/src/cli/commands/deploy.ts +55 -49
  36. package/src/cli/commands/execution.ts +7 -4
  37. package/src/cli/commands/experiments.ts +1 -1
  38. package/src/cli/commands/setup.ts +1 -1
  39. package/src/cli/commands/shared.ts +1 -1
  40. package/src/cli/commands/snapshot.ts +1 -1
  41. package/src/cli/commands/status.ts +1 -1
  42. package/src/cli/core/auth-fetch.ts +9 -4
  43. package/src/cli/core/browser.ts +12 -5
  44. package/src/cli/core/daemon/daemon.ts +81 -9
  45. package/src/cli/core/daemon/exec-repl.ts +189 -0
  46. package/src/cli/core/daemon/exec.ts +8 -43
  47. package/src/cli/core/daemon/ipc.spec.ts +27 -0
  48. package/src/cli/core/daemon/ipc.ts +76 -7
  49. package/src/cli/core/exec-compiler.ts +8 -3
  50. package/src/cli/core/providers/index.ts +17 -4
  51. package/src/cli/core/providers/kernel.ts +4 -3
  52. package/src/cli/core/providers/libretto-cloud.ts +224 -36
  53. package/src/cli/router.ts +9 -4
  54. package/src/shared/ipc/socket-transport.spec.ts +6 -0
  55. package/src/shared/ipc/socket-transport.ts +20 -5
  56. package/dist/cli/framework/simple-cli.js +0 -880
  57. package/src/cli/framework/simple-cli.ts +0 -1459
@@ -1,15 +1,16 @@
1
1
  /**
2
- * Experimental auth commands for the libretto hosted platform.
2
+ * Hosted-platform auth commands.
3
3
  *
4
- * libretto experimental auth signup
5
- * libretto experimental auth login
6
- * libretto experimental auth logout
7
- * libretto experimental auth invite <email> [--role member|admin|owner]
8
- * libretto experimental auth accept-invite <tenantSlug> <invitationId>
9
- * libretto experimental auth api-key issue [--label <label>]
10
- * libretto experimental auth api-key list
11
- * libretto experimental auth api-key revoke <id>
12
- * libretto experimental auth whoami
4
+ * libretto cloud auth signup
5
+ * libretto cloud auth login
6
+ * libretto cloud auth forgot-password
7
+ * libretto cloud auth logout
8
+ * libretto cloud auth invite <email> [--role member|admin|owner]
9
+ * libretto cloud auth accept-invite <tenantSlug> <invitationId>
10
+ * libretto cloud auth api-key issue [--label <label>]
11
+ * libretto cloud auth api-key list
12
+ * libretto cloud auth api-key revoke <id>
13
+ * libretto cloud auth whoami
13
14
  *
14
15
  * Credentials live at ~/.libretto/auth.json (mode 0600). The CLI sends either
15
16
  * the stored API key or the stored session cookie depending on what's
@@ -17,15 +18,15 @@
17
18
  */
18
19
 
19
20
  import { z } from "zod";
20
- import { SimpleCLI } from "../framework/simple-cli.js";
21
+ import { SimpleCLI } from "affordance";
21
22
  import {
22
23
  ApiCallError,
23
24
  betterAuthCall,
24
- HOSTED_API_URL,
25
25
  NOT_AUTHENTICATED_MESSAGE,
26
26
  orpcCall,
27
27
  pickCredential,
28
28
  resolveApiUrl,
29
+ resolveHostedApiUrl,
29
30
  } from "../core/auth-fetch.js";
30
31
  import {
31
32
  authStatePath,
@@ -198,11 +199,10 @@ async function issueApiKey(
198
199
 
199
200
  export const signupCommand = SimpleCLI.command({
200
201
  description: "Create a new hosted-platform account and organization",
201
- experimental: true,
202
202
  })
203
203
  .input(SimpleCLI.input({ positionals: [], named: {} }))
204
204
  .handle(async () => {
205
- const apiUrl = HOSTED_API_URL;
205
+ const apiUrl = resolveHostedApiUrl();
206
206
  console.log("Sign up for libretto cloud");
207
207
  console.log();
208
208
  console.log("Heads up: a libretto user can only belong to one organization.");
@@ -215,7 +215,7 @@ export const signupCommand = SimpleCLI.command({
215
215
  const name = await prompt("Your name:");
216
216
  if (name.toLowerCase() === "q" || name.length === 0) {
217
217
  console.log(
218
- "OK — ask an existing teammate to run `libretto experimental auth invite <your-email>` and then run `libretto experimental auth accept-invite <slug> <invitation-id>` from this machine.",
218
+ "OK — ask an existing teammate to run `libretto cloud auth invite <your-email>` and then run `libretto cloud auth accept-invite <slug> <invitation-id>` from this machine.",
219
219
  );
220
220
  return;
221
221
  }
@@ -294,7 +294,7 @@ export const signupCommand = SimpleCLI.command({
294
294
  console.log(`Session saved to ${authStatePath()}`);
295
295
  console.log();
296
296
  console.log("To generate an API key, run:");
297
- console.log(" libretto experimental auth api-key issue --label <label>");
297
+ console.log(" libretto cloud auth api-key issue --label <label>");
298
298
  console.log("Then add LIBRETTO_API_KEY=<key> to your project's .env file.");
299
299
  });
300
300
 
@@ -304,11 +304,10 @@ export const signupCommand = SimpleCLI.command({
304
304
 
305
305
  export const loginCommand = SimpleCLI.command({
306
306
  description: "Sign in to an existing hosted-platform account",
307
- experimental: true,
308
307
  })
309
308
  .input(SimpleCLI.input({ positionals: [], named: {} }))
310
309
  .handle(async () => {
311
- const apiUrl = HOSTED_API_URL;
310
+ const apiUrl = resolveHostedApiUrl();
312
311
 
313
312
  const email = await prompt("Email:");
314
313
  const password = await promptPassword("Password:");
@@ -369,13 +368,32 @@ export const loginCommand = SimpleCLI.command({
369
368
  }
370
369
  });
371
370
 
371
+ export const forgotPasswordCommand = SimpleCLI.command({
372
+ description: "Send a password reset email",
373
+ })
374
+ .input(SimpleCLI.input({ positionals: [], named: {} }))
375
+ .handle(async () => {
376
+ const apiUrl = resolveHostedApiUrl();
377
+ const email = await prompt("Email:");
378
+ const result = await orpcCall<{ status: "sent" | "not_found" }>({
379
+ apiUrl,
380
+ path: "/v1/auth/requestPasswordReset",
381
+ input: { email },
382
+ unauthenticated: true,
383
+ });
384
+ if (result.status === "not_found") {
385
+ console.log(`No Libretto account exists for ${email}.`);
386
+ return;
387
+ }
388
+ console.log(`Password reset link sent to ${email}.`);
389
+ });
390
+
372
391
  // ---------------------------------------------------------------------------
373
392
  // logout
374
393
  // ---------------------------------------------------------------------------
375
394
 
376
395
  export const logoutCommand = SimpleCLI.command({
377
396
  description: "Clear local libretto credentials",
378
- experimental: true,
379
397
  })
380
398
  .handle(async () => {
381
399
  const state = await readAuthState();
@@ -400,7 +418,6 @@ export const logoutCommand = SimpleCLI.command({
400
418
 
401
419
  export const inviteCommand = SimpleCLI.command({
402
420
  description: "Invite a teammate to your active organization",
403
- experimental: true,
404
421
  })
405
422
  .input(
406
423
  SimpleCLI.input({
@@ -475,7 +492,7 @@ export const inviteCommand = SimpleCLI.command({
475
492
  console.log();
476
493
  console.log("Tell them to run:");
477
494
  console.log(
478
- ` libretto experimental auth accept-invite ${orgSlug} ${data.id}`,
495
+ ` libretto cloud auth accept-invite ${orgSlug} ${data.id}`,
479
496
  );
480
497
  });
481
498
 
@@ -485,7 +502,6 @@ export const inviteCommand = SimpleCLI.command({
485
502
 
486
503
  export const acceptInviteCommand = SimpleCLI.command({
487
504
  description: "Accept an organization invitation",
488
- experimental: true,
489
505
  })
490
506
  .input(
491
507
  SimpleCLI.input({
@@ -514,7 +530,7 @@ export const acceptInviteCommand = SimpleCLI.command({
514
530
  )
515
531
  .handle(async ({ input }) => {
516
532
  const stored = await readAuthState();
517
- const apiUrl = HOSTED_API_URL;
533
+ const apiUrl = resolveHostedApiUrl();
518
534
  const credential = pickCredential(stored);
519
535
  const expectedTenantSlug = input.tenantSlug;
520
536
 
@@ -535,7 +551,7 @@ export const acceptInviteCommand = SimpleCLI.command({
535
551
  [
536
552
  "You're already a member of an organization.",
537
553
  "A libretto user can only belong to one organization at a time.",
538
- "To accept this invite: log out, delete the existing account, and re-run `auth accept-invite` with a new account (or a fresh email).",
554
+ "To accept this invite: log out, delete the existing account, and re-run `libretto cloud auth accept-invite` with a new account (or a fresh email).",
539
555
  ].join("\n"),
540
556
  );
541
557
  }
@@ -611,7 +627,7 @@ export const acceptInviteCommand = SimpleCLI.command({
611
627
  console.log();
612
628
  console.log("Email verified. You're logged in and a member of the organization.");
613
629
  console.log("To generate an API key, run:");
614
- console.log(" libretto experimental auth api-key issue --label <label>");
630
+ console.log(" libretto cloud auth api-key issue --label <label>");
615
631
  console.log("Then add LIBRETTO_API_KEY=<key> to your project's .env file.");
616
632
  });
617
633
 
@@ -621,7 +637,6 @@ export const acceptInviteCommand = SimpleCLI.command({
621
637
 
622
638
  export const apiKeyIssueCommand = SimpleCLI.command({
623
639
  description: "Issue a new API key for the active organization",
624
- experimental: true,
625
640
  })
626
641
  .input(
627
642
  SimpleCLI.input({
@@ -658,7 +673,6 @@ export const apiKeyIssueCommand = SimpleCLI.command({
658
673
 
659
674
  export const apiKeyListCommand = SimpleCLI.command({
660
675
  description: "List API keys for the active organization",
661
- experimental: true,
662
676
  })
663
677
  .handle(async () => {
664
678
  const stored = await readAuthState();
@@ -691,13 +705,12 @@ export const apiKeyListCommand = SimpleCLI.command({
691
705
 
692
706
  export const apiKeyRevokeCommand = SimpleCLI.command({
693
707
  description: "Revoke an API key by id",
694
- experimental: true,
695
708
  })
696
709
  .input(
697
710
  SimpleCLI.input({
698
711
  positionals: [
699
712
  SimpleCLI.positional("id", z.string().min(1), {
700
- help: "API key id (from `auth api-key list`).",
713
+ help: "API key id (from `libretto cloud auth api-key list`).",
701
714
  }),
702
715
  ],
703
716
  named: {},
@@ -720,7 +733,7 @@ export const apiKeyRevokeCommand = SimpleCLI.command({
720
733
 
721
734
  console.log(`API key ${input.id} revoked.`);
722
735
  console.log(
723
- "If this key was in your .env, remove the LIBRETTO_API_KEY value and issue a new one with `auth api-key issue --label <label>`.",
736
+ "If this key was in your .env, remove the LIBRETTO_API_KEY value and issue a new one with `libretto cloud auth api-key issue --label <label>`.",
724
737
  );
725
738
  });
726
739
 
@@ -730,7 +743,6 @@ export const apiKeyRevokeCommand = SimpleCLI.command({
730
743
 
731
744
  export const whoamiCommand = SimpleCLI.command({
732
745
  description: "Print the active session and credential source",
733
- experimental: true,
734
746
  })
735
747
  .handle(async () => {
736
748
  const stored = await readAuthState();
@@ -740,13 +752,13 @@ export const whoamiCommand = SimpleCLI.command({
740
752
 
741
753
  if (credential.source === "none") {
742
754
  console.log(
743
- "Not authenticated. Run `libretto experimental auth signup`, `login`, or set LIBRETTO_API_KEY in your env.",
755
+ "Not authenticated. Run `libretto cloud auth signup`, `libretto cloud auth login`, or set LIBRETTO_API_KEY in your env.",
744
756
  );
745
757
  return;
746
758
  }
747
759
 
748
760
  console.log(`Auth source: ${credential.source}`);
749
- console.log(`API URL: ${HOSTED_API_URL}`);
761
+ console.log(`API URL: ${resolveHostedApiUrl()}`);
750
762
  console.log(
751
763
  `LIBRETTO_API_KEY: ${envKey ? `set in env (${envKey.slice(0, 6)}…)` : "not set in env"}`,
752
764
  );
@@ -771,6 +783,7 @@ export const authCommands = SimpleCLI.group({
771
783
  routes: {
772
784
  signup: signupCommand,
773
785
  login: loginCommand,
786
+ "forgot-password": forgotPasswordCommand,
774
787
  logout: logoutCommand,
775
788
  invite: inviteCommand,
776
789
  "accept-invite": acceptInviteCommand,
@@ -6,8 +6,8 @@
6
6
  * them switch between any of the configured Subscription Update
7
7
  * products (Free / Pro / Team).
8
8
  *
9
- * libretto experimental billing portal → Stripe Customer Portal
10
- * libretto experimental billing status → plan + usage + period end
9
+ * libretto cloud billing portal → Stripe Customer Portal
10
+ * libretto cloud billing status → plan + usage + period end
11
11
  *
12
12
  * `libretto init` is unchanged. New tenants start on Free automatically
13
13
  * (with a real Stripe Customer + Free Subscription created at signup).
@@ -15,7 +15,7 @@
15
15
  * Auth: requires a session cookie (or LIBRETTO_API_KEY).
16
16
  */
17
17
 
18
- import { SimpleCLI } from "../framework/simple-cli.js";
18
+ import { SimpleCLI } from "affordance";
19
19
  import {
20
20
  NOT_AUTHENTICATED_MESSAGE,
21
21
  orpcCall,
@@ -71,7 +71,6 @@ function formatLimit(limit: number | null): string {
71
71
 
72
72
  export const billingPortalCommand = SimpleCLI.command({
73
73
  description: "Open the libretto plans page (current plan + switch options)",
74
- experimental: true,
75
74
  })
76
75
  .handle(async () => {
77
76
  const { apiUrl, credential } = await requireAuth();
@@ -97,7 +96,6 @@ export const billingPortalCommand = SimpleCLI.command({
97
96
 
98
97
  export const billingStatusCommand = SimpleCLI.command({
99
98
  description: "Print the current plan, status, and browser-hour usage",
100
- experimental: true,
101
99
  })
102
100
  .handle(async () => {
103
101
  const { apiUrl, credential } = await requireAuth();
@@ -20,7 +20,7 @@ import {
20
20
  validateSessionName,
21
21
  } from "../core/session.js";
22
22
  import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
23
- import { SimpleCLI } from "../framework/simple-cli.js";
23
+ import { SimpleCLI } from "affordance";
24
24
  import {
25
25
  sessionOption,
26
26
  withAutoSession,
@@ -63,8 +63,8 @@ function resolveRequestedSessionMode(
63
63
 
64
64
  export const openInput = SimpleCLI.input({
65
65
  positionals: [
66
- SimpleCLI.positional("url", z.string().optional(), {
67
- help: "URL to open",
66
+ SimpleCLI.positional("url", z.string().default("about:blank"), {
67
+ help: "URL to open (defaults to about:blank)",
68
68
  }),
69
69
  ],
70
70
  named: {
@@ -92,10 +92,6 @@ export const openInput = SimpleCLI.input({
92
92
  }),
93
93
  },
94
94
  })
95
- .refine(
96
- (input) => Boolean(input.url),
97
- `Usage: ${librettoCommand("open <url> [--headless] [--read-only|--write-access] [--auth-profile <domain>] [--viewport WxH] [--session <name>]")}`,
98
- )
99
95
  .refine(
100
96
  (input) => !(input.headed && input.headless),
101
97
  "Cannot pass both --headed and --headless.",
@@ -119,7 +115,7 @@ export const openCommand = SimpleCLI.command({
119
115
  if (providerName === "local") {
120
116
  const headed = input.headed || !input.headless;
121
117
  const viewport = parseViewportArg(input.viewport);
122
- await runOpen(input.url!, headed, ctx.session, ctx.logger, {
118
+ await runOpen(input.url, headed, ctx.session, ctx.logger, {
123
119
  viewport,
124
120
  accessMode: resolveRequestedSessionMode(
125
121
  input.readOnly,
@@ -130,7 +126,7 @@ export const openCommand = SimpleCLI.command({
130
126
  });
131
127
  } else {
132
128
  await runOpenWithProvider(
133
- input.url!,
129
+ input.url,
134
130
  providerName,
135
131
  ctx.session,
136
132
  ctx.logger,
@@ -1,8 +1,12 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { z } from "zod";
3
- import { HOSTED_API_URL } from "../core/auth-fetch.js";
3
+ import {
4
+ orpcCall,
5
+ resolveApiUrl,
6
+ } from "../core/auth-fetch.js";
4
7
  import { buildHostedDeployTarball } from "../core/deploy-artifact.js";
5
- import { SimpleCLI } from "../framework/simple-cli.js";
8
+ import { readAuthState } from "../core/auth-storage.js";
9
+ import { SimpleCLI } from "affordance";
6
10
 
7
11
  type DeploymentStatus = "building" | "ready" | "failed";
8
12
 
@@ -19,37 +23,50 @@ function generateDeploymentName(): string {
19
23
  return `deploy-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
20
24
  }
21
25
 
22
- function getConfig() {
23
- const apiKey = process.env.LIBRETTO_API_KEY;
26
+ function deployApiKeyRequiredMessage(hasStoredSession: boolean): string {
27
+ if (hasStoredSession) {
28
+ return [
29
+ "LIBRETTO_API_KEY is required to deploy to Libretto Cloud.",
30
+ "You are logged in locally, but deploy endpoints require API-key auth.",
31
+ " • Generate a key: run `libretto cloud auth api-key issue --label <label>`.",
32
+ " • Add it to your project .env file: `LIBRETTO_API_KEY=<issued-key>`.",
33
+ ].join("\n");
34
+ }
35
+
36
+ return [
37
+ "LIBRETTO_API_KEY is required to deploy to Libretto Cloud.",
38
+ "No local cloud session was found.",
39
+ " • New account: run `libretto cloud auth signup`, then verify your email.",
40
+ " • Existing account: run `libretto cloud auth login`.",
41
+ " • Generate a key: run `libretto cloud auth api-key issue --label <label>`.",
42
+ " • Add it to your project .env file: `LIBRETTO_API_KEY=<issued-key>`.",
43
+ ].join("\n");
44
+ }
45
+
46
+ async function requireDeployApiKey() {
47
+ const apiKey = process.env.LIBRETTO_API_KEY?.trim();
24
48
 
25
49
  if (!apiKey) {
26
- throw new Error(
27
- "LIBRETTO_API_KEY environment variable is required.",
28
- );
50
+ throw new Error(deployApiKeyRequiredMessage(await hasStoredCloudSession()));
29
51
  }
30
52
 
31
- return { apiUrl: HOSTED_API_URL, apiKey };
53
+ return {
54
+ apiUrl: resolveApiUrl(null),
55
+ credential: { source: "env-api-key" as const, apiKey },
56
+ };
32
57
  }
33
58
 
34
- async function postJson(
35
- apiUrl: string,
36
- apiKey: string,
37
- path: string,
38
- input: Record<string, unknown> = {},
39
- ): Promise<Response> {
40
- return fetch(`${apiUrl}${path}`, {
41
- method: "POST",
42
- headers: {
43
- "x-api-key": apiKey,
44
- "Content-Type": "application/json",
45
- },
46
- body: JSON.stringify({ json: input }),
47
- });
59
+ async function hasStoredCloudSession(): Promise<boolean> {
60
+ try {
61
+ return Boolean((await readAuthState())?.session);
62
+ } catch {
63
+ return false;
64
+ }
48
65
  }
49
66
 
50
67
  async function pollDeployment(
51
68
  apiUrl: string,
52
- apiKey: string,
69
+ credential: { source: "env-api-key"; apiKey: string },
53
70
  deploymentId: string,
54
71
  pollIntervalMs: number,
55
72
  maxWaitMs: number,
@@ -68,18 +85,14 @@ async function pollDeployment(
68
85
 
69
86
  await new Promise((r) => setTimeout(r, pollIntervalMs));
70
87
 
71
- const res = await postJson(apiUrl, apiKey, "/v1/deployments/sync", {
72
- id: deploymentId,
88
+ deployment = await orpcCall<DeploymentResponse["json"]>({
89
+ apiUrl,
90
+ path: "/v1/deployments/sync",
91
+ input: { id: deploymentId },
92
+ credential,
73
93
  });
74
- const body = (await res.json()) as DeploymentResponse;
75
- if (res.status !== 200) {
76
- throw new Error(
77
- `Failed to sync deployment status (${res.status}): ${JSON.stringify(body)}`,
78
- );
79
- }
80
- status = body.json.status;
81
- workflows = body.json.workflows;
82
- deployment = body.json;
94
+ status = deployment.status;
95
+ workflows = deployment.workflows;
83
96
  if (status === "ready" && readyAt === null) readyAt = Date.now();
84
97
  process.stdout.write(".");
85
98
  }
@@ -132,11 +145,10 @@ export const deployInput = SimpleCLI.input({
132
145
 
133
146
  export const deployCommand = SimpleCLI.command({
134
147
  description: "Deploy workflows to the hosted platform",
135
- experimental: true,
136
148
  })
137
149
  .input(deployInput)
138
150
  .handle(async ({ input }) => {
139
- const { apiUrl, apiKey } = getConfig();
151
+ const { apiUrl, credential } = await requireDeployApiKey();
140
152
  const deploymentName = generateDeploymentName();
141
153
 
142
154
  // Hosted deploy uploads a generated artifact with a deploy entrypoint and
@@ -157,20 +169,14 @@ export const deployCommand = SimpleCLI.command({
157
169
  if (input.description) createPayload.description = input.description;
158
170
 
159
171
  console.log("Uploading deployment...");
160
- const res = await postJson(
172
+ const body = await orpcCall<DeploymentResponse["json"]>({
161
173
  apiUrl,
162
- apiKey,
163
- "/v1/deployments/create",
164
- createPayload,
165
- );
166
- const body = (await res.json()) as DeploymentResponse;
167
- if (res.status !== 200) {
168
- throw new Error(
169
- `Failed to create deployment (${res.status}): ${JSON.stringify(body)}`,
170
- );
171
- }
174
+ path: "/v1/deployments/create",
175
+ input: createPayload,
176
+ credential,
177
+ });
172
178
 
173
- const { deployment_id, status } = body.json;
179
+ const { deployment_id, status } = body;
174
180
  console.log(`Deployment created: ${deployment_id}`);
175
181
  console.log(`Status: ${status}`);
176
182
 
@@ -178,7 +184,7 @@ export const deployCommand = SimpleCLI.command({
178
184
  process.stdout.write("Waiting for build");
179
185
  const deployment = await pollDeployment(
180
186
  apiUrl,
181
- apiKey,
187
+ credential,
182
188
  deployment_id,
183
189
  10_000,
184
190
  5 * 60 * 1000,
@@ -29,7 +29,10 @@ import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
29
29
  import { readLibrettoConfig } from "../core/config.js";
30
30
  import { librettoCommand } from "../../shared/package-manager.js";
31
31
  import { renderSnapshotDiff } from "../../shared/snapshot/diff-snapshots.js";
32
- import { resolveProviderName } from "../core/providers/index.js";
32
+ import {
33
+ getProviderStartupTimeoutMs,
34
+ resolveProviderName,
35
+ } from "../core/providers/index.js";
33
36
  import { getAbsoluteIntegrationPath } from "../core/workflow-runtime.js";
34
37
  import {
35
38
  compileExecFunction,
@@ -49,7 +52,7 @@ import {
49
52
  } from "../core/telemetry.js";
50
53
  import type { SessionAccessMode } from "../../shared/state/index.js";
51
54
  import type { Experiments } from "../core/experiments.js";
52
- import { SimpleCLI } from "../framework/simple-cli.js";
55
+ import { SimpleCLI } from "affordance";
53
56
  import {
54
57
  pageOption,
55
58
  sessionOption,
@@ -609,7 +612,7 @@ async function runIntegrationFromFile(
609
612
  },
610
613
  logger,
611
614
  logPath: runLogPath,
612
- startupTimeoutMs: 60_000,
615
+ startupTimeoutMs: getProviderStartupTimeoutMs(args.providerName),
613
616
  handlers,
614
617
  });
615
618
 
@@ -762,7 +765,7 @@ export const readonlyExecCommand = SimpleCLI.command({
762
765
  });
763
766
  });
764
767
 
765
- const runUsage = `Usage: ${librettoCommand("run <integrationFile> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--read-only|--write-access] [--no-visualize] [--stay-open-on-success] [--viewport WxH]")}`;
768
+ const runUsage = `Usage: ${librettoCommand("run <integrationFile> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--read-only|--write-access] [--no-visualize] [--stay-open-on-success] [--viewport WxH] [--provider <provider>]")}`;
766
769
 
767
770
  export const runInput = SimpleCLI.input({
768
771
  positionals: [
@@ -8,7 +8,7 @@ import {
8
8
  type ExperimentName,
9
9
  type Experiments,
10
10
  } from "../core/experiments.js";
11
- import { SimpleCLI } from "../framework/simple-cli.js";
11
+ import { SimpleCLI } from "affordance";
12
12
 
13
13
  const experimentNames = Object.keys(EXPERIMENTS) as ExperimentName[];
14
14
 
@@ -8,7 +8,7 @@ import {
8
8
  REPO_ROOT,
9
9
  } from "../core/context.js";
10
10
  import { librettoCommand } from "../../shared/package-manager.js";
11
- import { SimpleCLI } from "../framework/simple-cli.js";
11
+ import { SimpleCLI } from "affordance";
12
12
 
13
13
  function installBrowsers(): void {
14
14
  console.log("Installing Playwright Chromium...");
@@ -14,7 +14,7 @@ import {
14
14
  type SimpleCLIMiddlewareArgs,
15
15
  type SimpleCLIContext,
16
16
  type SimpleCLIMiddleware,
17
- } from "../framework/simple-cli.js";
17
+ } from "affordance";
18
18
 
19
19
  export function sessionOption(help = "Session name") {
20
20
  return SimpleCLI.option(z.string().optional(), { help });
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import type { LoggerApi } from "../../shared/logger/index.js";
3
3
  import { readSessionState } from "../core/session.js";
4
- import { SimpleCLI } from "../framework/simple-cli.js";
4
+ import { SimpleCLI } from "affordance";
5
5
  import {
6
6
  pageOption,
7
7
  sessionOption,
@@ -1,5 +1,5 @@
1
1
  import { listRunningSessions, type SessionState } from "../core/session.js";
2
- import { SimpleCLI } from "../framework/simple-cli.js";
2
+ import { SimpleCLI } from "affordance";
3
3
 
4
4
  // ── Session status printing ─────────────────────────────────────────────────
5
5
 
@@ -11,7 +11,11 @@
11
11
 
12
12
  import { readAuthState, writeAuthState, type AuthState } from "./auth-storage.js";
13
13
 
14
- export const HOSTED_API_URL = "https://api.libretto.sh";
14
+ export const DEFAULT_HOSTED_API_URL = "https://api.libretto.sh";
15
+
16
+ export function resolveHostedApiUrl(): string {
17
+ return process.env.LIBRETTO_API_URL?.trim() || DEFAULT_HOSTED_API_URL;
18
+ }
15
19
 
16
20
  /**
17
21
  * Shared "you have no usable credential" message. Pointed at the two
@@ -19,8 +23,9 @@ export const HOSTED_API_URL = "https://api.libretto.sh";
19
23
  */
20
24
  export const NOT_AUTHENTICATED_MESSAGE = [
21
25
  "Not authenticated.",
22
- " • Cookie expired or never set: run `libretto experimental auth login` to refresh it.",
23
- " • Or set LIBRETTO_API_KEY in your .env (issue one with `libretto experimental auth api-key issue --label <label>` after logging in).",
26
+ " • New account: run `libretto cloud auth signup`.",
27
+ " • Existing account: run `libretto cloud auth login`.",
28
+ " • Automation: set LIBRETTO_API_KEY in your env (issue one with `libretto cloud auth api-key issue --label <label>` after signing in).",
24
29
  ].join("\n");
25
30
 
26
31
  export type CredentialSource = "env-api-key" | "cookie" | "none";
@@ -41,7 +46,7 @@ export function pickCredential(state: AuthState | null): CredentialChoice {
41
46
  }
42
47
 
43
48
  export function resolveApiUrl(_state: AuthState | null): string {
44
- return HOSTED_API_URL;
49
+ return resolveHostedApiUrl();
45
50
  }
46
51
 
47
52
  type FetchOptions = {
@@ -9,13 +9,17 @@ import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
9
9
  import { mkdir, writeFile } from "node:fs/promises";
10
10
  import { dirname, join } from "node:path";
11
11
  import { createServer } from "node:net";
12
+ import { isWindowsNamedPipePath } from "../../shared/ipc/socket-transport.js";
12
13
  import type { LoggerApi } from "../../shared/logger/index.js";
13
14
  import type { SessionAccessMode } from "../../shared/state/index.js";
14
15
  import type { Experiments } from "./experiments.js";
15
16
  import { getSessionProviderClosePath, PROFILES_DIR } from "./context.js";
16
17
  import { readLibrettoConfig } from "./config.js";
17
18
  import { librettoCommand } from "../../shared/package-manager.js";
18
- import { getCloudProviderApi } from "./providers/index.js";
19
+ import {
20
+ getCloudProviderApi,
21
+ getProviderStartupTimeoutMs,
22
+ } from "./providers/index.js";
19
23
  import {
20
24
  assertSessionAvailableForStart,
21
25
  clearSessionState,
@@ -89,7 +93,8 @@ export function normalizeUrl(url: string): URL {
89
93
  if (
90
94
  parsedUrl.protocol === "http:" ||
91
95
  parsedUrl.protocol === "https:" ||
92
- parsedUrl.protocol === "file:"
96
+ parsedUrl.protocol === "file:" ||
97
+ parsedUrl.href === "about:blank"
93
98
  ) {
94
99
  return parsedUrl;
95
100
  }
@@ -99,7 +104,7 @@ export function normalizeUrl(url: string): URL {
99
104
  }
100
105
 
101
106
  throw new Error(
102
- `Unsupported URL protocol: ${parsedUrl.protocol}. Use http://, https://, or file://.`,
107
+ `Unsupported URL protocol: ${parsedUrl.protocol}. Use http://, https://, file://, or about:blank.`,
103
108
  );
104
109
  }
105
110
 
@@ -548,8 +553,8 @@ export async function runOpenWithProvider(
548
553
  },
549
554
  logger,
550
555
  logPath: runLogPath,
551
- // Remote CDP connection + navigation; must cover both.
552
- startupTimeoutMs: 60_000,
556
+ // Remote provider creation can wait for cloud capacity before CDP exists.
557
+ startupTimeoutMs: getProviderStartupTimeoutMs(providerName),
553
558
  });
554
559
  client.destroy();
555
560
 
@@ -974,6 +979,8 @@ function unlinkDaemonSocket(
974
979
  session: string,
975
980
  ): void {
976
981
  if (!socketPath) return;
982
+ if (isWindowsNamedPipePath(socketPath)) return;
983
+
977
984
  try {
978
985
  unlinkSync(socketPath);
979
986
  } catch (err) {