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,13 +1,13 @@
1
1
  import { z } from "zod";
2
- import { SimpleCLI } from "../framework/simple-cli.js";
2
+ import { SimpleCLI } from "affordance";
3
3
  import {
4
4
  ApiCallError,
5
5
  betterAuthCall,
6
- HOSTED_API_URL,
7
6
  NOT_AUTHENTICATED_MESSAGE,
8
7
  orpcCall,
9
8
  pickCredential,
10
- resolveApiUrl
9
+ resolveApiUrl,
10
+ resolveHostedApiUrl
11
11
  } from "../core/auth-fetch.js";
12
12
  import {
13
13
  authStatePath,
@@ -88,10 +88,9 @@ async function issueApiKey(apiUrl, name, credential) {
88
88
  return data;
89
89
  }
90
90
  const signupCommand = SimpleCLI.command({
91
- description: "Create a new hosted-platform account and organization",
92
- experimental: true
91
+ description: "Create a new hosted-platform account and organization"
93
92
  }).input(SimpleCLI.input({ positionals: [], named: {} })).handle(async () => {
94
- const apiUrl = HOSTED_API_URL;
93
+ const apiUrl = resolveHostedApiUrl();
95
94
  console.log("Sign up for libretto cloud");
96
95
  console.log();
97
96
  console.log("Heads up: a libretto user can only belong to one organization.");
@@ -103,7 +102,7 @@ const signupCommand = SimpleCLI.command({
103
102
  const name = await prompt("Your name:");
104
103
  if (name.toLowerCase() === "q" || name.length === 0) {
105
104
  console.log(
106
- "OK \u2014 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."
105
+ "OK \u2014 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."
107
106
  );
108
107
  return;
109
108
  }
@@ -162,14 +161,13 @@ const signupCommand = SimpleCLI.command({
162
161
  console.log(`Session saved to ${authStatePath()}`);
163
162
  console.log();
164
163
  console.log("To generate an API key, run:");
165
- console.log(" libretto experimental auth api-key issue --label <label>");
164
+ console.log(" libretto cloud auth api-key issue --label <label>");
166
165
  console.log("Then add LIBRETTO_API_KEY=<key> to your project's .env file.");
167
166
  });
168
167
  const loginCommand = SimpleCLI.command({
169
- description: "Sign in to an existing hosted-platform account",
170
- experimental: true
168
+ description: "Sign in to an existing hosted-platform account"
171
169
  }).input(SimpleCLI.input({ positionals: [], named: {} })).handle(async () => {
172
- const apiUrl = HOSTED_API_URL;
170
+ const apiUrl = resolveHostedApiUrl();
173
171
  const email = await prompt("Email:");
174
172
  const password = await promptPassword("Password:");
175
173
  const { data, setCookie } = await betterAuthCall({
@@ -218,8 +216,7 @@ const loginCommand = SimpleCLI.command({
218
216
  }
219
217
  });
220
218
  const logoutCommand = SimpleCLI.command({
221
- description: "Clear local libretto credentials",
222
- experimental: true
219
+ description: "Clear local libretto credentials"
223
220
  }).handle(async () => {
224
221
  const state = await readAuthState();
225
222
  if (state?.session?.cookie) {
@@ -236,8 +233,7 @@ const logoutCommand = SimpleCLI.command({
236
233
  console.log("Logged out.");
237
234
  });
238
235
  const inviteCommand = SimpleCLI.command({
239
- description: "Invite a teammate to your active organization",
240
- experimental: true
236
+ description: "Invite a teammate to your active organization"
241
237
  }).input(
242
238
  SimpleCLI.input({
243
239
  positionals: [
@@ -287,12 +283,11 @@ const inviteCommand = SimpleCLI.command({
287
283
  console.log();
288
284
  console.log("Tell them to run:");
289
285
  console.log(
290
- ` libretto experimental auth accept-invite ${orgSlug} ${data.id}`
286
+ ` libretto cloud auth accept-invite ${orgSlug} ${data.id}`
291
287
  );
292
288
  });
293
289
  const acceptInviteCommand = SimpleCLI.command({
294
- description: "Accept an organization invitation",
295
- experimental: true
290
+ description: "Accept an organization invitation"
296
291
  }).input(
297
292
  SimpleCLI.input({
298
293
  positionals: [
@@ -313,7 +308,7 @@ const acceptInviteCommand = SimpleCLI.command({
313
308
  })
314
309
  ).handle(async ({ input }) => {
315
310
  const stored = await readAuthState();
316
- const apiUrl = HOSTED_API_URL;
311
+ const apiUrl = resolveHostedApiUrl();
317
312
  const credential = pickCredential(stored);
318
313
  const expectedTenantSlug = input.tenantSlug;
319
314
  if (credential.source !== "none") {
@@ -328,7 +323,7 @@ const acceptInviteCommand = SimpleCLI.command({
328
323
  [
329
324
  "You're already a member of an organization.",
330
325
  "A libretto user can only belong to one organization at a time.",
331
- "To accept this invite: log out, delete the existing account, and re-run `auth accept-invite` with a new account (or a fresh email)."
326
+ "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)."
332
327
  ].join("\n")
333
328
  );
334
329
  }
@@ -381,12 +376,11 @@ const acceptInviteCommand = SimpleCLI.command({
381
376
  console.log();
382
377
  console.log("Email verified. You're logged in and a member of the organization.");
383
378
  console.log("To generate an API key, run:");
384
- console.log(" libretto experimental auth api-key issue --label <label>");
379
+ console.log(" libretto cloud auth api-key issue --label <label>");
385
380
  console.log("Then add LIBRETTO_API_KEY=<key> to your project's .env file.");
386
381
  });
387
382
  const apiKeyIssueCommand = SimpleCLI.command({
388
- description: "Issue a new API key for the active organization",
389
- experimental: true
383
+ description: "Issue a new API key for the active organization"
390
384
  }).input(
391
385
  SimpleCLI.input({
392
386
  positionals: [],
@@ -416,8 +410,7 @@ const apiKeyIssueCommand = SimpleCLI.command({
416
410
  );
417
411
  });
418
412
  const apiKeyListCommand = SimpleCLI.command({
419
- description: "List API keys for the active organization",
420
- experimental: true
413
+ description: "List API keys for the active organization"
421
414
  }).handle(async () => {
422
415
  const stored = await readAuthState();
423
416
  const apiUrl = resolveApiUrl(stored);
@@ -444,13 +437,12 @@ const apiKeyListCommand = SimpleCLI.command({
444
437
  }
445
438
  });
446
439
  const apiKeyRevokeCommand = SimpleCLI.command({
447
- description: "Revoke an API key by id",
448
- experimental: true
440
+ description: "Revoke an API key by id"
449
441
  }).input(
450
442
  SimpleCLI.input({
451
443
  positionals: [
452
444
  SimpleCLI.positional("id", z.string().min(1), {
453
- help: "API key id (from `auth api-key list`)."
445
+ help: "API key id (from `libretto cloud auth api-key list`)."
454
446
  })
455
447
  ],
456
448
  named: {}
@@ -470,24 +462,23 @@ const apiKeyRevokeCommand = SimpleCLI.command({
470
462
  });
471
463
  console.log(`API key ${input.id} revoked.`);
472
464
  console.log(
473
- "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>`."
465
+ "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>`."
474
466
  );
475
467
  });
476
468
  const whoamiCommand = SimpleCLI.command({
477
- description: "Print the active session and credential source",
478
- experimental: true
469
+ description: "Print the active session and credential source"
479
470
  }).handle(async () => {
480
471
  const stored = await readAuthState();
481
472
  const credential = pickCredential(stored);
482
473
  const envKey = process.env.LIBRETTO_API_KEY?.trim();
483
474
  if (credential.source === "none") {
484
475
  console.log(
485
- "Not authenticated. Run `libretto experimental auth signup`, `login`, or set LIBRETTO_API_KEY in your env."
476
+ "Not authenticated. Run `libretto cloud auth signup`, `libretto cloud auth login`, or set LIBRETTO_API_KEY in your env."
486
477
  );
487
478
  return;
488
479
  }
489
480
  console.log(`Auth source: ${credential.source}`);
490
- console.log(`API URL: ${HOSTED_API_URL}`);
481
+ console.log(`API URL: ${resolveHostedApiUrl()}`);
491
482
  console.log(
492
483
  `LIBRETTO_API_KEY: ${envKey ? `set in env (${envKey.slice(0, 6)}\u2026)` : "not set in env"}`
493
484
  );
@@ -1,4 +1,4 @@
1
- import { SimpleCLI } from "../framework/simple-cli.js";
1
+ import { SimpleCLI } from "affordance";
2
2
  import {
3
3
  NOT_AUTHENTICATED_MESSAGE,
4
4
  orpcCall,
@@ -20,8 +20,7 @@ function formatLimit(limit) {
20
20
  return limit === null ? "\u221E" : String(limit);
21
21
  }
22
22
  const billingPortalCommand = SimpleCLI.command({
23
- description: "Open the libretto plans page (current plan + switch options)",
24
- experimental: true
23
+ description: "Open the libretto plans page (current plan + switch options)"
25
24
  }).handle(async () => {
26
25
  const { apiUrl, credential } = await requireAuth();
27
26
  const { url } = await orpcCall({
@@ -40,8 +39,7 @@ const billingPortalCommand = SimpleCLI.command({
40
39
  );
41
40
  });
42
41
  const billingStatusCommand = SimpleCLI.command({
43
- description: "Print the current plan, status, and browser-hour usage",
44
- experimental: true
42
+ description: "Print the current plan, status, and browser-hour usage"
45
43
  }).handle(async () => {
46
44
  const { apiUrl, credential } = await requireAuth();
47
45
  const sub = await orpcCall({
@@ -19,7 +19,7 @@ import {
19
19
  validateSessionName
20
20
  } from "../core/session.js";
21
21
  import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
22
- import { SimpleCLI } from "../framework/simple-cli.js";
22
+ import { SimpleCLI } from "affordance";
23
23
  import {
24
24
  sessionOption,
25
25
  withAutoSession,
@@ -51,8 +51,8 @@ function resolveRequestedSessionMode(readOnly, writeAccess) {
51
51
  }
52
52
  const openInput = SimpleCLI.input({
53
53
  positionals: [
54
- SimpleCLI.positional("url", z.string().optional(), {
55
- help: "URL to open"
54
+ SimpleCLI.positional("url", z.string().default("about:blank"), {
55
+ help: "URL to open (defaults to about:blank)"
56
56
  })
57
57
  ],
58
58
  named: {
@@ -80,9 +80,6 @@ const openInput = SimpleCLI.input({
80
80
  })
81
81
  }
82
82
  }).refine(
83
- (input) => Boolean(input.url),
84
- `Usage: ${librettoCommand("open <url> [--headless] [--read-only|--write-access] [--auth-profile <domain>] [--viewport WxH] [--session <name>]")}`
85
- ).refine(
86
83
  (input) => !(input.headed && input.headless),
87
84
  "Cannot pass both --headed and --headless."
88
85
  ).refine(
@@ -1,31 +1,51 @@
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
  function generateDeploymentName() {
7
11
  return `deploy-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
8
12
  }
9
- function getConfig() {
10
- const apiKey = process.env.LIBRETTO_API_KEY;
13
+ function deployApiKeyRequiredMessage(hasStoredSession) {
14
+ if (hasStoredSession) {
15
+ return [
16
+ "LIBRETTO_API_KEY is required to deploy to Libretto Cloud.",
17
+ "You are logged in locally, but deploy endpoints require API-key auth.",
18
+ " \u2022 Generate a key: run `libretto cloud auth api-key issue --label <label>`.",
19
+ " \u2022 Add it to your project .env file: `LIBRETTO_API_KEY=<issued-key>`."
20
+ ].join("\n");
21
+ }
22
+ return [
23
+ "LIBRETTO_API_KEY is required to deploy to Libretto Cloud.",
24
+ "No local cloud session was found.",
25
+ " \u2022 New account: run `libretto cloud auth signup`, then verify your email.",
26
+ " \u2022 Existing account: run `libretto cloud auth login`.",
27
+ " \u2022 Generate a key: run `libretto cloud auth api-key issue --label <label>`.",
28
+ " \u2022 Add it to your project .env file: `LIBRETTO_API_KEY=<issued-key>`."
29
+ ].join("\n");
30
+ }
31
+ async function requireDeployApiKey() {
32
+ const apiKey = process.env.LIBRETTO_API_KEY?.trim();
11
33
  if (!apiKey) {
12
- throw new Error(
13
- "LIBRETTO_API_KEY environment variable is required."
14
- );
34
+ throw new Error(deployApiKeyRequiredMessage(await hasStoredCloudSession()));
15
35
  }
16
- return { apiUrl: HOSTED_API_URL, apiKey };
36
+ return {
37
+ apiUrl: resolveApiUrl(null),
38
+ credential: { source: "env-api-key", apiKey }
39
+ };
17
40
  }
18
- async function postJson(apiUrl, apiKey, path, input = {}) {
19
- return fetch(`${apiUrl}${path}`, {
20
- method: "POST",
21
- headers: {
22
- "x-api-key": apiKey,
23
- "Content-Type": "application/json"
24
- },
25
- body: JSON.stringify({ json: input })
26
- });
41
+ async function hasStoredCloudSession() {
42
+ try {
43
+ return Boolean((await readAuthState())?.session);
44
+ } catch {
45
+ return false;
46
+ }
27
47
  }
28
- async function pollDeployment(apiUrl, apiKey, deploymentId, pollIntervalMs, maxWaitMs) {
48
+ async function pollDeployment(apiUrl, credential, deploymentId, pollIntervalMs, maxWaitMs) {
29
49
  const start = Date.now();
30
50
  const workflowWaitMs = 6e4;
31
51
  let status = "building";
@@ -37,18 +57,14 @@ async function pollDeployment(apiUrl, apiKey, deploymentId, pollIntervalMs, maxW
37
57
  if (status === "ready" && workflows?.length) break;
38
58
  if (status === "ready" && readyAt && Date.now() - readyAt > workflowWaitMs) break;
39
59
  await new Promise((r) => setTimeout(r, pollIntervalMs));
40
- const res = await postJson(apiUrl, apiKey, "/v1/deployments/sync", {
41
- id: deploymentId
60
+ deployment = await orpcCall({
61
+ apiUrl,
62
+ path: "/v1/deployments/sync",
63
+ input: { id: deploymentId },
64
+ credential
42
65
  });
43
- const body = await res.json();
44
- if (res.status !== 200) {
45
- throw new Error(
46
- `Failed to sync deployment status (${res.status}): ${JSON.stringify(body)}`
47
- );
48
- }
49
- status = body.json.status;
50
- workflows = body.json.workflows;
51
- deployment = body.json;
66
+ status = deployment.status;
67
+ workflows = deployment.workflows;
52
68
  if (status === "ready" && readyAt === null) readyAt = Date.now();
53
69
  process.stdout.write(".");
54
70
  }
@@ -88,10 +104,9 @@ const deployInput = SimpleCLI.input({
88
104
  }
89
105
  });
90
106
  const deployCommand = SimpleCLI.command({
91
- description: "Deploy workflows to the hosted platform",
92
- experimental: true
107
+ description: "Deploy workflows to the hosted platform"
93
108
  }).input(deployInput).handle(async ({ input }) => {
94
- const { apiUrl, apiKey } = getConfig();
109
+ const { apiUrl, credential } = await requireDeployApiKey();
95
110
  const deploymentName = generateDeploymentName();
96
111
  console.log("Bundling hosted deployment artifact...");
97
112
  const { entryPoint, source } = await buildHostedDeployTarball({
@@ -106,26 +121,20 @@ const deployCommand = SimpleCLI.command({
106
121
  };
107
122
  if (input.description) createPayload.description = input.description;
108
123
  console.log("Uploading deployment...");
109
- const res = await postJson(
124
+ const body = await orpcCall({
110
125
  apiUrl,
111
- apiKey,
112
- "/v1/deployments/create",
113
- createPayload
114
- );
115
- const body = await res.json();
116
- if (res.status !== 200) {
117
- throw new Error(
118
- `Failed to create deployment (${res.status}): ${JSON.stringify(body)}`
119
- );
120
- }
121
- const { deployment_id, status } = body.json;
126
+ path: "/v1/deployments/create",
127
+ input: createPayload,
128
+ credential
129
+ });
130
+ const { deployment_id, status } = body;
122
131
  console.log(`Deployment created: ${deployment_id}`);
123
132
  console.log(`Status: ${status}`);
124
133
  if (status === "building") {
125
134
  process.stdout.write("Waiting for build");
126
135
  const deployment = await pollDeployment(
127
136
  apiUrl,
128
- apiKey,
137
+ credential,
129
138
  deployment_id,
130
139
  1e4,
131
140
  5 * 60 * 1e3
@@ -27,7 +27,10 @@ import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
27
27
  import { readLibrettoConfig } from "../core/config.js";
28
28
  import { librettoCommand } from "../../shared/package-manager.js";
29
29
  import { renderSnapshotDiff } from "../../shared/snapshot/diff-snapshots.js";
30
- import { resolveProviderName } from "../core/providers/index.js";
30
+ import {
31
+ getProviderStartupTimeoutMs,
32
+ resolveProviderName
33
+ } from "../core/providers/index.js";
31
34
  import { getAbsoluteIntegrationPath } from "../core/workflow-runtime.js";
32
35
  import {
33
36
  compileExecFunction,
@@ -42,7 +45,7 @@ import {
42
45
  readNetworkLog,
43
46
  wrapPageForActionLogging
44
47
  } from "../core/telemetry.js";
45
- import { SimpleCLI } from "../framework/simple-cli.js";
48
+ import { SimpleCLI } from "affordance";
46
49
  import {
47
50
  pageOption,
48
51
  sessionOption,
@@ -459,7 +462,7 @@ async function runIntegrationFromFile(args, logger) {
459
462
  },
460
463
  logger,
461
464
  logPath: runLogPath,
462
- startupTimeoutMs: 6e4,
465
+ startupTimeoutMs: getProviderStartupTimeoutMs(args.providerName),
463
466
  handlers
464
467
  });
465
468
  writeSessionState(
@@ -6,7 +6,7 @@ import {
6
6
  resolveExperiments,
7
7
  setExperimentEnabled
8
8
  } from "../core/experiments.js";
9
- import { SimpleCLI } from "../framework/simple-cli.js";
9
+ import { SimpleCLI } from "affordance";
10
10
  const experimentNames = Object.keys(EXPERIMENTS);
11
11
  const experimentsUsage = [
12
12
  "Usage:",
@@ -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
  function installBrowsers() {
13
13
  console.log("Installing Playwright Chromium...");
14
14
  const result = spawnSync("npx", ["playwright", "install", "chromium"], {
@@ -8,7 +8,7 @@ import {
8
8
  } from "../core/session.js";
9
9
  import {
10
10
  SimpleCLI
11
- } from "../framework/simple-cli.js";
11
+ } from "affordance";
12
12
  function sessionOption(help = "Session name") {
13
13
  return SimpleCLI.option(z.string().optional(), { help });
14
14
  }
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { readSessionState } from "../core/session.js";
3
- import { SimpleCLI } from "../framework/simple-cli.js";
3
+ import { SimpleCLI } from "affordance";
4
4
  import {
5
5
  pageOption,
6
6
  sessionOption,
@@ -1,5 +1,5 @@
1
1
  import { listRunningSessions } from "../core/session.js";
2
- import { SimpleCLI } from "../framework/simple-cli.js";
2
+ import { SimpleCLI } from "affordance";
3
3
  function printOpenSessions(sessions) {
4
4
  console.log("\nOpen sessions:");
5
5
  if (sessions.length === 0) {
@@ -1,9 +1,13 @@
1
1
  import { readAuthState, writeAuthState } from "./auth-storage.js";
2
- const HOSTED_API_URL = "https://api.libretto.sh";
2
+ const DEFAULT_HOSTED_API_URL = "https://api.libretto.sh";
3
+ function resolveHostedApiUrl() {
4
+ return process.env.LIBRETTO_API_URL?.trim() || DEFAULT_HOSTED_API_URL;
5
+ }
3
6
  const NOT_AUTHENTICATED_MESSAGE = [
4
7
  "Not authenticated.",
5
- " \u2022 Cookie expired or never set: run `libretto experimental auth login` to refresh it.",
6
- " \u2022 Or set LIBRETTO_API_KEY in your .env (issue one with `libretto experimental auth api-key issue --label <label>` after logging in)."
8
+ " \u2022 New account: run `libretto cloud auth signup`.",
9
+ " \u2022 Existing account: run `libretto cloud auth login`.",
10
+ " \u2022 Automation: set LIBRETTO_API_KEY in your env (issue one with `libretto cloud auth api-key issue --label <label>` after signing in)."
7
11
  ].join("\n");
8
12
  function pickCredential(state) {
9
13
  const envKey = process.env.LIBRETTO_API_KEY?.trim();
@@ -14,7 +18,7 @@ function pickCredential(state) {
14
18
  return { source: "none" };
15
19
  }
16
20
  function resolveApiUrl(_state) {
17
- return HOSTED_API_URL;
21
+ return resolveHostedApiUrl();
18
22
  }
19
23
  async function authFetch(options) {
20
24
  const headers = {
@@ -184,12 +188,13 @@ async function ensureAuthState(apiUrl) {
184
188
  }
185
189
  export {
186
190
  ApiCallError,
187
- HOSTED_API_URL,
191
+ DEFAULT_HOSTED_API_URL,
188
192
  NOT_AUTHENTICATED_MESSAGE,
189
193
  authFetch,
190
194
  betterAuthCall,
191
195
  ensureAuthState,
192
196
  orpcCall,
193
197
  pickCredential,
194
- resolveApiUrl
198
+ resolveApiUrl,
199
+ resolveHostedApiUrl
195
200
  };
@@ -5,10 +5,14 @@ import { existsSync, readFileSync, unlinkSync } from "node:fs";
5
5
  import { mkdir, writeFile } from "node:fs/promises";
6
6
  import { dirname, join } from "node:path";
7
7
  import { createServer } from "node:net";
8
+ import { isWindowsNamedPipePath } from "../../shared/ipc/socket-transport.js";
8
9
  import { getSessionProviderClosePath, PROFILES_DIR } from "./context.js";
9
10
  import { readLibrettoConfig } from "./config.js";
10
11
  import { librettoCommand } from "../../shared/package-manager.js";
11
- import { getCloudProviderApi } from "./providers/index.js";
12
+ import {
13
+ getCloudProviderApi,
14
+ getProviderStartupTimeoutMs
15
+ } from "./providers/index.js";
12
16
  import {
13
17
  assertSessionAvailableForStart,
14
18
  clearSessionState,
@@ -64,14 +68,14 @@ function normalizeUrl(url) {
64
68
  if (!parsedUrl) {
65
69
  return new URL(`https://${url}`);
66
70
  }
67
- if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:" || parsedUrl.protocol === "file:") {
71
+ if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:" || parsedUrl.protocol === "file:" || parsedUrl.href === "about:blank") {
68
72
  return parsedUrl;
69
73
  }
70
74
  if (isLikelyHostWithPort(parsedUrl, url)) {
71
75
  return new URL(`https://${url}`);
72
76
  }
73
77
  throw new Error(
74
- `Unsupported URL protocol: ${parsedUrl.protocol}. Use http://, https://, or file://.`
78
+ `Unsupported URL protocol: ${parsedUrl.protocol}. Use http://, https://, file://, or about:blank.`
75
79
  );
76
80
  }
77
81
  function normalizeDomain(url) {
@@ -398,8 +402,8 @@ async function runOpenWithProvider(rawUrl, providerName, session, logger, access
398
402
  },
399
403
  logger,
400
404
  logPath: runLogPath,
401
- // Remote CDP connection + navigation; must cover both.
402
- startupTimeoutMs: 6e4
405
+ // Remote provider creation can wait for cloud capacity before CDP exists.
406
+ startupTimeoutMs: getProviderStartupTimeoutMs(providerName)
403
407
  });
404
408
  client.destroy();
405
409
  if (!providerSession) {
@@ -727,6 +731,7 @@ function resolveClosableSessions(logger) {
727
731
  }
728
732
  function unlinkDaemonSocket(socketPath, logger, session) {
729
733
  if (!socketPath) return;
734
+ if (isWindowsNamedPipePath(socketPath)) return;
730
735
  try {
731
736
  unlinkSync(socketPath);
732
737
  } catch (err) {