libretto 0.6.13 → 0.6.14

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 (55) hide show
  1. package/dist/cli/commands/auth.js +24 -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 +6 -3
  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/libretto-cloud.js +178 -26
  21. package/dist/cli/router.js +9 -4
  22. package/dist/shared/ipc/socket-transport.d.ts +2 -1
  23. package/dist/shared/ipc/socket-transport.js +16 -5
  24. package/dist/shared/ipc/socket-transport.spec.js +5 -0
  25. package/package.json +2 -2
  26. package/skills/libretto/SKILL.md +33 -29
  27. package/skills/libretto/references/code-generation-rules.md +6 -0
  28. package/skills/libretto/references/configuration-file-reference.md +8 -0
  29. package/skills/libretto/references/site-security-review.md +6 -6
  30. package/skills/libretto-readonly/SKILL.md +1 -1
  31. package/src/cli/commands/auth.ts +24 -33
  32. package/src/cli/commands/billing.ts +3 -5
  33. package/src/cli/commands/browser.ts +5 -9
  34. package/src/cli/commands/deploy.ts +55 -49
  35. package/src/cli/commands/execution.ts +6 -3
  36. package/src/cli/commands/experiments.ts +1 -1
  37. package/src/cli/commands/setup.ts +1 -1
  38. package/src/cli/commands/shared.ts +1 -1
  39. package/src/cli/commands/snapshot.ts +1 -1
  40. package/src/cli/commands/status.ts +1 -1
  41. package/src/cli/core/auth-fetch.ts +9 -4
  42. package/src/cli/core/browser.ts +12 -5
  43. package/src/cli/core/daemon/daemon.ts +81 -9
  44. package/src/cli/core/daemon/exec-repl.ts +189 -0
  45. package/src/cli/core/daemon/exec.ts +8 -43
  46. package/src/cli/core/daemon/ipc.spec.ts +27 -0
  47. package/src/cli/core/daemon/ipc.ts +76 -7
  48. package/src/cli/core/exec-compiler.ts +8 -3
  49. package/src/cli/core/providers/index.ts +17 -4
  50. package/src/cli/core/providers/libretto-cloud.ts +224 -36
  51. package/src/cli/router.ts +9 -4
  52. package/src/shared/ipc/socket-transport.spec.ts +6 -0
  53. package/src/shared/ipc/socket-transport.ts +20 -5
  54. package/dist/cli/framework/simple-cli.js +0 -880
  55. package/src/cli/framework/simple-cli.ts +0 -1459
@@ -1,15 +1,15 @@
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 logout
7
+ * libretto cloud auth invite <email> [--role member|admin|owner]
8
+ * libretto cloud auth accept-invite <tenantSlug> <invitationId>
9
+ * libretto cloud auth api-key issue [--label <label>]
10
+ * libretto cloud auth api-key list
11
+ * libretto cloud auth api-key revoke <id>
12
+ * libretto cloud auth whoami
13
13
  *
14
14
  * Credentials live at ~/.libretto/auth.json (mode 0600). The CLI sends either
15
15
  * the stored API key or the stored session cookie depending on what's
@@ -17,15 +17,15 @@
17
17
  */
18
18
 
19
19
  import { z } from "zod";
20
- import { SimpleCLI } from "../framework/simple-cli.js";
20
+ import { SimpleCLI } from "affordance";
21
21
  import {
22
22
  ApiCallError,
23
23
  betterAuthCall,
24
- HOSTED_API_URL,
25
24
  NOT_AUTHENTICATED_MESSAGE,
26
25
  orpcCall,
27
26
  pickCredential,
28
27
  resolveApiUrl,
28
+ resolveHostedApiUrl,
29
29
  } from "../core/auth-fetch.js";
30
30
  import {
31
31
  authStatePath,
@@ -198,11 +198,10 @@ async function issueApiKey(
198
198
 
199
199
  export const signupCommand = SimpleCLI.command({
200
200
  description: "Create a new hosted-platform account and organization",
201
- experimental: true,
202
201
  })
203
202
  .input(SimpleCLI.input({ positionals: [], named: {} }))
204
203
  .handle(async () => {
205
- const apiUrl = HOSTED_API_URL;
204
+ const apiUrl = resolveHostedApiUrl();
206
205
  console.log("Sign up for libretto cloud");
207
206
  console.log();
208
207
  console.log("Heads up: a libretto user can only belong to one organization.");
@@ -215,7 +214,7 @@ export const signupCommand = SimpleCLI.command({
215
214
  const name = await prompt("Your name:");
216
215
  if (name.toLowerCase() === "q" || name.length === 0) {
217
216
  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.",
217
+ "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
218
  );
220
219
  return;
221
220
  }
@@ -294,7 +293,7 @@ export const signupCommand = SimpleCLI.command({
294
293
  console.log(`Session saved to ${authStatePath()}`);
295
294
  console.log();
296
295
  console.log("To generate an API key, run:");
297
- console.log(" libretto experimental auth api-key issue --label <label>");
296
+ console.log(" libretto cloud auth api-key issue --label <label>");
298
297
  console.log("Then add LIBRETTO_API_KEY=<key> to your project's .env file.");
299
298
  });
300
299
 
@@ -304,11 +303,10 @@ export const signupCommand = SimpleCLI.command({
304
303
 
305
304
  export const loginCommand = SimpleCLI.command({
306
305
  description: "Sign in to an existing hosted-platform account",
307
- experimental: true,
308
306
  })
309
307
  .input(SimpleCLI.input({ positionals: [], named: {} }))
310
308
  .handle(async () => {
311
- const apiUrl = HOSTED_API_URL;
309
+ const apiUrl = resolveHostedApiUrl();
312
310
 
313
311
  const email = await prompt("Email:");
314
312
  const password = await promptPassword("Password:");
@@ -375,7 +373,6 @@ export const loginCommand = SimpleCLI.command({
375
373
 
376
374
  export const logoutCommand = SimpleCLI.command({
377
375
  description: "Clear local libretto credentials",
378
- experimental: true,
379
376
  })
380
377
  .handle(async () => {
381
378
  const state = await readAuthState();
@@ -400,7 +397,6 @@ export const logoutCommand = SimpleCLI.command({
400
397
 
401
398
  export const inviteCommand = SimpleCLI.command({
402
399
  description: "Invite a teammate to your active organization",
403
- experimental: true,
404
400
  })
405
401
  .input(
406
402
  SimpleCLI.input({
@@ -475,7 +471,7 @@ export const inviteCommand = SimpleCLI.command({
475
471
  console.log();
476
472
  console.log("Tell them to run:");
477
473
  console.log(
478
- ` libretto experimental auth accept-invite ${orgSlug} ${data.id}`,
474
+ ` libretto cloud auth accept-invite ${orgSlug} ${data.id}`,
479
475
  );
480
476
  });
481
477
 
@@ -485,7 +481,6 @@ export const inviteCommand = SimpleCLI.command({
485
481
 
486
482
  export const acceptInviteCommand = SimpleCLI.command({
487
483
  description: "Accept an organization invitation",
488
- experimental: true,
489
484
  })
490
485
  .input(
491
486
  SimpleCLI.input({
@@ -514,7 +509,7 @@ export const acceptInviteCommand = SimpleCLI.command({
514
509
  )
515
510
  .handle(async ({ input }) => {
516
511
  const stored = await readAuthState();
517
- const apiUrl = HOSTED_API_URL;
512
+ const apiUrl = resolveHostedApiUrl();
518
513
  const credential = pickCredential(stored);
519
514
  const expectedTenantSlug = input.tenantSlug;
520
515
 
@@ -535,7 +530,7 @@ export const acceptInviteCommand = SimpleCLI.command({
535
530
  [
536
531
  "You're already a member of an organization.",
537
532
  "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).",
533
+ "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
534
  ].join("\n"),
540
535
  );
541
536
  }
@@ -611,7 +606,7 @@ export const acceptInviteCommand = SimpleCLI.command({
611
606
  console.log();
612
607
  console.log("Email verified. You're logged in and a member of the organization.");
613
608
  console.log("To generate an API key, run:");
614
- console.log(" libretto experimental auth api-key issue --label <label>");
609
+ console.log(" libretto cloud auth api-key issue --label <label>");
615
610
  console.log("Then add LIBRETTO_API_KEY=<key> to your project's .env file.");
616
611
  });
617
612
 
@@ -621,7 +616,6 @@ export const acceptInviteCommand = SimpleCLI.command({
621
616
 
622
617
  export const apiKeyIssueCommand = SimpleCLI.command({
623
618
  description: "Issue a new API key for the active organization",
624
- experimental: true,
625
619
  })
626
620
  .input(
627
621
  SimpleCLI.input({
@@ -658,7 +652,6 @@ export const apiKeyIssueCommand = SimpleCLI.command({
658
652
 
659
653
  export const apiKeyListCommand = SimpleCLI.command({
660
654
  description: "List API keys for the active organization",
661
- experimental: true,
662
655
  })
663
656
  .handle(async () => {
664
657
  const stored = await readAuthState();
@@ -691,13 +684,12 @@ export const apiKeyListCommand = SimpleCLI.command({
691
684
 
692
685
  export const apiKeyRevokeCommand = SimpleCLI.command({
693
686
  description: "Revoke an API key by id",
694
- experimental: true,
695
687
  })
696
688
  .input(
697
689
  SimpleCLI.input({
698
690
  positionals: [
699
691
  SimpleCLI.positional("id", z.string().min(1), {
700
- help: "API key id (from `auth api-key list`).",
692
+ help: "API key id (from `libretto cloud auth api-key list`).",
701
693
  }),
702
694
  ],
703
695
  named: {},
@@ -720,7 +712,7 @@ export const apiKeyRevokeCommand = SimpleCLI.command({
720
712
 
721
713
  console.log(`API key ${input.id} revoked.`);
722
714
  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>`.",
715
+ "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
716
  );
725
717
  });
726
718
 
@@ -730,7 +722,6 @@ export const apiKeyRevokeCommand = SimpleCLI.command({
730
722
 
731
723
  export const whoamiCommand = SimpleCLI.command({
732
724
  description: "Print the active session and credential source",
733
- experimental: true,
734
725
  })
735
726
  .handle(async () => {
736
727
  const stored = await readAuthState();
@@ -740,13 +731,13 @@ export const whoamiCommand = SimpleCLI.command({
740
731
 
741
732
  if (credential.source === "none") {
742
733
  console.log(
743
- "Not authenticated. Run `libretto experimental auth signup`, `login`, or set LIBRETTO_API_KEY in your env.",
734
+ "Not authenticated. Run `libretto cloud auth signup`, `libretto cloud auth login`, or set LIBRETTO_API_KEY in your env.",
744
735
  );
745
736
  return;
746
737
  }
747
738
 
748
739
  console.log(`Auth source: ${credential.source}`);
749
- console.log(`API URL: ${HOSTED_API_URL}`);
740
+ console.log(`API URL: ${resolveHostedApiUrl()}`);
750
741
  console.log(
751
742
  `LIBRETTO_API_KEY: ${envKey ? `set in env (${envKey.slice(0, 6)}…)` : "not set in env"}`,
752
743
  );
@@ -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
 
@@ -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) {