libretto 0.6.12 → 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 (82) hide show
  1. package/README.md +3 -8
  2. package/README.template.md +3 -8
  3. package/dist/cli/cli.js +0 -23
  4. package/dist/cli/commands/auth.js +24 -33
  5. package/dist/cli/commands/billing.js +3 -5
  6. package/dist/cli/commands/browser.js +4 -13
  7. package/dist/cli/commands/deploy.js +54 -45
  8. package/dist/cli/commands/execution.js +6 -3
  9. package/dist/cli/commands/experiments.js +1 -1
  10. package/dist/cli/commands/setup.js +2 -295
  11. package/dist/cli/commands/shared.js +1 -1
  12. package/dist/cli/commands/snapshot.js +10 -100
  13. package/dist/cli/commands/status.js +2 -42
  14. package/dist/cli/core/auth-fetch.js +11 -6
  15. package/dist/cli/core/browser.js +13 -8
  16. package/dist/cli/core/config.js +3 -6
  17. package/dist/cli/core/daemon/daemon.js +88 -74
  18. package/dist/cli/core/daemon/exec-repl.js +133 -0
  19. package/dist/cli/core/daemon/exec.js +6 -21
  20. package/dist/cli/core/daemon/ipc.js +47 -4
  21. package/dist/cli/core/daemon/ipc.spec.js +21 -0
  22. package/dist/cli/core/daemon/snapshot.js +2 -29
  23. package/dist/cli/core/exec-compiler.js +8 -3
  24. package/dist/cli/core/experiments.js +1 -28
  25. package/dist/cli/core/providers/index.js +13 -4
  26. package/dist/cli/core/providers/libretto-cloud.js +178 -26
  27. package/dist/cli/index.js +0 -2
  28. package/dist/cli/router.js +9 -6
  29. package/dist/shared/instrumentation/instrument.js +4 -4
  30. package/dist/shared/ipc/socket-transport.d.ts +2 -1
  31. package/dist/shared/ipc/socket-transport.js +16 -5
  32. package/dist/shared/ipc/socket-transport.spec.js +5 -0
  33. package/docs/releasing.md +8 -6
  34. package/package.json +3 -2
  35. package/skills/libretto/SKILL.md +49 -47
  36. package/skills/libretto/references/code-generation-rules.md +6 -0
  37. package/skills/libretto/references/configuration-file-reference.md +14 -12
  38. package/skills/libretto/references/pages-and-page-targeting.md +1 -1
  39. package/skills/libretto/references/site-security-review.md +6 -6
  40. package/skills/libretto-readonly/SKILL.md +2 -9
  41. package/src/cli/cli.ts +0 -24
  42. package/src/cli/commands/auth.ts +24 -33
  43. package/src/cli/commands/billing.ts +3 -5
  44. package/src/cli/commands/browser.ts +6 -16
  45. package/src/cli/commands/deploy.ts +55 -49
  46. package/src/cli/commands/execution.ts +6 -3
  47. package/src/cli/commands/experiments.ts +1 -1
  48. package/src/cli/commands/setup.ts +2 -381
  49. package/src/cli/commands/shared.ts +1 -1
  50. package/src/cli/commands/snapshot.ts +9 -137
  51. package/src/cli/commands/status.ts +2 -50
  52. package/src/cli/core/auth-fetch.ts +9 -4
  53. package/src/cli/core/browser.ts +15 -8
  54. package/src/cli/core/config.ts +3 -6
  55. package/src/cli/core/daemon/daemon.ts +106 -76
  56. package/src/cli/core/daemon/exec-repl.ts +189 -0
  57. package/src/cli/core/daemon/exec.ts +8 -43
  58. package/src/cli/core/daemon/ipc.spec.ts +27 -0
  59. package/src/cli/core/daemon/ipc.ts +81 -23
  60. package/src/cli/core/daemon/snapshot.ts +1 -43
  61. package/src/cli/core/exec-compiler.ts +8 -3
  62. package/src/cli/core/experiments.ts +9 -38
  63. package/src/cli/core/providers/index.ts +17 -4
  64. package/src/cli/core/providers/libretto-cloud.ts +224 -36
  65. package/src/cli/core/resolve-model.ts +5 -0
  66. package/src/cli/core/workflow-runtime.ts +1 -0
  67. package/src/cli/index.ts +0 -1
  68. package/src/cli/router.ts +9 -6
  69. package/src/shared/instrumentation/instrument.ts +4 -4
  70. package/src/shared/ipc/socket-transport.spec.ts +6 -0
  71. package/src/shared/ipc/socket-transport.ts +20 -5
  72. package/dist/cli/commands/ai.js +0 -110
  73. package/dist/cli/core/ai-model.js +0 -195
  74. package/dist/cli/core/api-snapshot-analyzer.js +0 -86
  75. package/dist/cli/core/snapshot-analyzer.js +0 -667
  76. package/dist/cli/framework/simple-cli.js +0 -880
  77. package/scripts/summarize-evals.mjs +0 -135
  78. package/src/cli/commands/ai.ts +0 -144
  79. package/src/cli/core/ai-model.ts +0 -301
  80. package/src/cli/core/api-snapshot-analyzer.ts +0 -110
  81. package/src/cli/core/snapshot-analyzer.ts +0 -856
  82. package/src/cli/framework/simple-cli.ts +0 -1459
package/README.md CHANGED
@@ -30,19 +30,14 @@ https://github.com/user-attachments/assets/9b9a0ab3-5133-4b20-b3be-459943349d18
30
30
  ```bash
31
31
  npm install libretto
32
32
 
33
- # First-time onboarding: install skill, download Chromium, and pin the default snapshot model
33
+ # First-time onboarding: install skills and download Chromium
34
34
  npx libretto setup
35
35
 
36
36
  # Check workspace readiness at any time
37
37
  npx libretto status
38
-
39
- # Manually change the snapshot analysis model (advanced override)
40
- npx libretto ai configure <openai | anthropic | gemini | vertex>
41
38
  ```
42
39
 
43
- `setup` detects available provider credentials (e.g. `OPENAI_API_KEY`) and automatically pins the default model to `.libretto/config.json`. Re-running `setup` on a healthy workspace shows the current configuration instead of re-prompting. If credentials are missing for a previously configured provider, `setup` offers an interactive repair flow.
44
-
45
- Use `ai configure` when you want to explicitly switch providers or set a custom model string.
40
+ `setup` creates the `.libretto/` directory, installs agent skills, and downloads Chromium unless you pass `--skip-browsers`.
46
41
 
47
42
  ## Use cases
48
43
 
@@ -80,7 +75,7 @@ You can also use Libretto directly from the command line. All commands accept `-
80
75
  npx libretto open <url> # launch browser and open a URL
81
76
  npx libretto run ./integration.ts --headless # run a workflow and close on success
82
77
  npx libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
83
- npx libretto snapshot --objective "..." # capture PNG + HTML and analyze with an LLM
78
+ npx libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
84
79
  npx libretto exec "<code>" # execute Playwright TypeScript against the open page
85
80
  npx libretto close # close the browser
86
81
  ```
@@ -28,19 +28,14 @@ https://github.com/user-attachments/assets/9b9a0ab3-5133-4b20-b3be-459943349d18
28
28
  ```bash
29
29
  npm install libretto
30
30
 
31
- # First-time onboarding: install skill, download Chromium, and pin the default snapshot model
31
+ # First-time onboarding: install skills and download Chromium
32
32
  npx libretto setup
33
33
 
34
34
  # Check workspace readiness at any time
35
35
  npx libretto status
36
-
37
- # Manually change the snapshot analysis model (advanced override)
38
- npx libretto ai configure <openai | anthropic | gemini | vertex>
39
36
  ```
40
37
 
41
- `setup` detects available provider credentials (e.g. `OPENAI_API_KEY`) and automatically pins the default model to `.libretto/config.json`. Re-running `setup` on a healthy workspace shows the current configuration instead of re-prompting. If credentials are missing for a previously configured provider, `setup` offers an interactive repair flow.
42
-
43
- Use `ai configure` when you want to explicitly switch providers or set a custom model string.
38
+ `setup` creates the `.libretto/` directory, installs agent skills, and downloads Chromium unless you pass `--skip-browsers`.
44
39
 
45
40
  ## Use cases
46
41
 
@@ -78,7 +73,7 @@ You can also use Libretto directly from the command line. All commands accept `-
78
73
  npx libretto open <url> # launch browser and open a URL
79
74
  npx libretto run ./integration.ts --headless # run a workflow and close on success
80
75
  npx libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
81
- npx libretto snapshot --objective "..." # capture PNG + HTML and analyze with an LLM
76
+ npx libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
82
77
  npx libretto exec "<code>" # execute Playwright TypeScript against the open page
83
78
  npx libretto close # close the browser
84
79
  ```
package/dist/cli/cli.js CHANGED
@@ -1,6 +1,4 @@
1
- import { resolveAiSetupStatus } from "./core/ai-model.js";
2
1
  import { ensureLibrettoSetup } from "./core/context.js";
3
- import { librettoCommand } from "../shared/package-manager.js";
4
2
  import { createCLIApp } from "./router.js";
5
3
  import { warnIfInstalledSkillOutOfDate } from "./core/skill-version.js";
6
4
  import { loadEnv } from "../shared/env/load-env.js";
@@ -15,27 +13,6 @@ Docs (agent-friendly): https://libretto.sh/docs
15
13
  }
16
14
  function printSetupAudit() {
17
15
  warnIfInstalledSkillOutOfDate();
18
- const status = resolveAiSetupStatus();
19
- switch (status.kind) {
20
- case "ready":
21
- console.log(`\u2713 Snapshot model: ${status.model}`);
22
- break;
23
- case "configured-missing-credentials":
24
- console.log(
25
- `\u2717 ${status.provider} configured (model: ${status.model}), but credentials are missing. Run \`${librettoCommand("setup")}\` to repair.`
26
- );
27
- break;
28
- case "invalid-config":
29
- console.log(
30
- `\u2717 AI config is invalid. Run \`${librettoCommand("setup")}\` to reconfigure.`
31
- );
32
- break;
33
- case "unconfigured":
34
- console.log(
35
- `\u2717 No AI model configured. Run \`${librettoCommand("setup")}\` or \`${librettoCommand("ai configure")}\` to set up.`
36
- );
37
- break;
38
- }
39
16
  }
40
17
  function isRootHelpRequest(rawArgs) {
41
18
  if (rawArgs.length === 0) return true;
@@ -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({
@@ -11,7 +11,7 @@ import {
11
11
  } from "../core/browser.js";
12
12
  import { resolveProviderName } from "../core/providers/index.js";
13
13
  import { readLibrettoConfig } from "../core/config.js";
14
- import { createLoggerForSession, withSessionLogger } from "../core/context.js";
14
+ import { createLoggerForSession } from "../core/context.js";
15
15
  import { librettoCommand } from "../../shared/package-manager.js";
16
16
  import {
17
17
  assertSessionAvailableForStart,
@@ -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(
@@ -246,11 +243,6 @@ const browserCommands = {
246
243
  "session-mode": sessionModeCommand,
247
244
  close: closeCommand
248
245
  };
249
- async function runClose(session) {
250
- await withSessionLogger(session, async (logger) => {
251
- await runCloseWithLogger(session, logger);
252
- });
253
- }
254
246
  export {
255
247
  browserCommands,
256
248
  closeCommand,
@@ -262,7 +254,6 @@ export {
262
254
  pagesCommand,
263
255
  pagesInput,
264
256
  parseViewportArg,
265
- runClose,
266
257
  saveCommand,
267
258
  saveInput,
268
259
  sessionModeCommand,
@@ -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:",