libretto 0.5.5 → 0.6.0

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 (110) hide show
  1. package/README.md +23 -10
  2. package/README.template.md +23 -10
  3. package/dist/cli/cli.js +10 -0
  4. package/dist/cli/commands/ai.js +77 -2
  5. package/dist/cli/commands/browser.js +98 -8
  6. package/dist/cli/commands/execution.js +152 -56
  7. package/dist/cli/commands/setup.js +390 -0
  8. package/dist/cli/commands/snapshot.js +2 -2
  9. package/dist/cli/commands/status.js +62 -0
  10. package/dist/cli/core/{snapshot-api-config.js → ai-model.js} +81 -7
  11. package/dist/cli/core/api-snapshot-analyzer.js +7 -5
  12. package/dist/cli/core/browser.js +202 -36
  13. package/dist/cli/core/{ai-config.js → config.js} +14 -79
  14. package/dist/cli/core/context.js +1 -25
  15. package/dist/cli/core/deploy-artifact.js +121 -61
  16. package/dist/cli/core/providers/browserbase.js +53 -0
  17. package/dist/cli/core/providers/index.js +48 -0
  18. package/dist/cli/core/providers/kernel.js +46 -0
  19. package/dist/cli/core/providers/libretto-cloud.js +58 -0
  20. package/dist/cli/core/readonly-exec.js +231 -0
  21. package/dist/{shared/llm/client.js → cli/core/resolve-model.js} +4 -68
  22. package/dist/cli/core/session.js +53 -0
  23. package/dist/cli/core/skill-version.js +73 -0
  24. package/dist/cli/core/telemetry.js +1 -54
  25. package/dist/cli/index.js +1 -7
  26. package/dist/cli/router.js +4 -4
  27. package/dist/cli/workers/run-integration-runtime.js +19 -13
  28. package/dist/cli/workers/run-integration-worker-protocol.js +5 -2
  29. package/dist/index.d.ts +2 -4
  30. package/dist/index.js +2 -2
  31. package/dist/runtime/extract/extract.d.ts +2 -2
  32. package/dist/runtime/extract/extract.js +4 -2
  33. package/dist/runtime/extract/index.d.ts +1 -1
  34. package/dist/runtime/recovery/agent.d.ts +2 -3
  35. package/dist/runtime/recovery/agent.js +5 -3
  36. package/dist/runtime/recovery/errors.d.ts +2 -3
  37. package/dist/runtime/recovery/errors.js +4 -2
  38. package/dist/runtime/recovery/index.d.ts +1 -2
  39. package/dist/runtime/recovery/recovery.d.ts +2 -3
  40. package/dist/runtime/recovery/recovery.js +3 -3
  41. package/dist/shared/debug/pause.js +4 -21
  42. package/dist/shared/run/api.d.ts +2 -0
  43. package/dist/shared/run/browser.d.ts +9 -1
  44. package/dist/shared/run/browser.js +43 -3
  45. package/dist/shared/state/index.d.ts +1 -1
  46. package/dist/shared/state/index.js +2 -0
  47. package/dist/shared/state/session-state.d.ts +20 -1
  48. package/dist/shared/state/session-state.js +12 -2
  49. package/dist/shared/workflow/workflow.d.ts +2 -1
  50. package/dist/shared/workflow/workflow.js +16 -9
  51. package/package.json +17 -16
  52. package/scripts/postinstall.mjs +13 -11
  53. package/scripts/skills-libretto.mjs +14 -4
  54. package/skills/AGENTS.md +11 -0
  55. package/skills/libretto/SKILL.md +30 -9
  56. package/skills/libretto/references/auth-profiles.md +1 -1
  57. package/skills/libretto/references/code-generation-rules.md +3 -3
  58. package/skills/libretto/references/configuration-file-reference.md +11 -6
  59. package/skills/libretto-readonly/SKILL.md +95 -0
  60. package/src/cli/cli.ts +10 -0
  61. package/src/cli/commands/ai.ts +111 -1
  62. package/src/cli/commands/browser.ts +111 -9
  63. package/src/cli/commands/execution.ts +181 -74
  64. package/src/cli/commands/setup.ts +516 -0
  65. package/src/cli/commands/snapshot.ts +2 -2
  66. package/src/cli/commands/status.ts +79 -0
  67. package/src/cli/core/{snapshot-api-config.ts → ai-model.ts} +154 -14
  68. package/src/cli/core/api-snapshot-analyzer.ts +7 -5
  69. package/src/cli/core/browser.ts +242 -35
  70. package/src/cli/core/{ai-config.ts → config.ts} +14 -108
  71. package/src/cli/core/context.ts +1 -45
  72. package/src/cli/core/deploy-artifact.ts +141 -71
  73. package/src/cli/core/providers/browserbase.ts +57 -0
  74. package/src/cli/core/providers/index.ts +62 -0
  75. package/src/cli/core/providers/kernel.ts +49 -0
  76. package/src/cli/core/providers/libretto-cloud.ts +61 -0
  77. package/src/cli/core/providers/types.ts +9 -0
  78. package/src/cli/core/readonly-exec.ts +284 -0
  79. package/src/{shared/llm/client.ts → cli/core/resolve-model.ts} +3 -85
  80. package/src/cli/core/session.ts +75 -2
  81. package/src/cli/core/skill-version.ts +93 -0
  82. package/src/cli/core/telemetry.ts +0 -52
  83. package/src/cli/index.ts +0 -6
  84. package/src/cli/router.ts +4 -4
  85. package/src/cli/workers/run-integration-runtime.ts +18 -16
  86. package/src/cli/workers/run-integration-worker-protocol.ts +4 -1
  87. package/src/index.ts +1 -7
  88. package/src/runtime/extract/extract.ts +6 -5
  89. package/src/runtime/recovery/agent.ts +5 -4
  90. package/src/runtime/recovery/errors.ts +4 -3
  91. package/src/runtime/recovery/recovery.ts +4 -4
  92. package/src/shared/debug/pause.ts +4 -23
  93. package/src/shared/run/browser.ts +50 -1
  94. package/src/shared/state/index.ts +2 -0
  95. package/src/shared/state/session-state.ts +10 -0
  96. package/src/shared/workflow/workflow.ts +24 -13
  97. package/dist/cli/commands/init.js +0 -286
  98. package/dist/cli/commands/logs.js +0 -117
  99. package/dist/shared/llm/ai-sdk-adapter.d.ts +0 -22
  100. package/dist/shared/llm/ai-sdk-adapter.js +0 -49
  101. package/dist/shared/llm/client.d.ts +0 -13
  102. package/dist/shared/llm/index.d.ts +0 -5
  103. package/dist/shared/llm/index.js +0 -6
  104. package/dist/shared/llm/types.d.ts +0 -67
  105. package/src/cli/commands/init.ts +0 -331
  106. package/src/cli/commands/logs.ts +0 -128
  107. package/src/shared/llm/ai-sdk-adapter.ts +0 -81
  108. package/src/shared/llm/index.ts +0 -3
  109. package/src/shared/llm/types.ts +0 -63
  110. /package/dist/{shared/llm → cli/core/providers}/types.js +0 -0
@@ -13,15 +13,20 @@ import { parseViewportArg } from "./browser.js";
13
13
  import { getPauseSignalPaths } from "../core/pause-signals.js";
14
14
  import {
15
15
  assertSessionAvailableForStart,
16
+ assertSessionAllowsCommand,
16
17
  clearSessionState,
17
18
  readSessionState,
18
19
  setSessionStatus
19
20
  } from "../core/session.js";
21
+ import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
20
22
  import {
21
23
  readActionLog,
22
24
  readNetworkLog,
23
25
  wrapPageForActionLogging
24
26
  } from "../core/telemetry.js";
27
+ import { readLibrettoConfig } from "../core/config.js";
28
+ import { resolveProviderName, getCloudProviderApi } from "../core/providers/index.js";
29
+ import { createReadonlyExecHelpers } from "../core/readonly-exec.js";
25
30
  import { SimpleCLI } from "../framework/simple-cli.js";
26
31
  import {
27
32
  pageOption,
@@ -135,12 +140,15 @@ function stripEmptyCatchHandlers(code) {
135
140
  }
136
141
  return { cleaned: result, strippedCount };
137
142
  }
138
- async function runExec(code, session, logger, visualize = false, pageId) {
143
+ async function runExec(code, session, logger, options = {}) {
144
+ const visualize = options.visualize ?? false;
145
+ const pageId = options.pageId;
146
+ const mode = options.mode ?? "exec";
139
147
  const { cleaned: cleanedCode, strippedCount } = stripEmptyCatchHandlers(code);
140
148
  if (strippedCount > 0) {
141
149
  console.log("(Stripped `.catch(() => {})` \u2014 letting errors bubble up)");
142
150
  }
143
- logger.info("exec-start", {
151
+ logger.info(`${mode}-start`, {
144
152
  session,
145
153
  codeLength: cleanedCode.length,
146
154
  codePreview: cleanedCode.slice(0, 200),
@@ -164,57 +172,61 @@ async function runExec(code, session, logger, visualize = false, pageId) {
164
172
  const stallInterval = setInterval(() => {
165
173
  const silenceMs = Date.now() - lastActivityTs;
166
174
  if (silenceMs >= STALL_THRESHOLD_MS) {
167
- logger.warn("exec-stall-warning", {
175
+ logger.warn(`${mode}-stall-warning`, {
168
176
  session,
169
177
  silenceMs,
170
178
  codePreview: cleanedCode.slice(0, 200)
171
179
  });
172
180
  console.warn(
173
- `[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1e3)}s \u2014 exec may be hung (code: ${cleanedCode.slice(0, 100)}...)`
181
+ `[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1e3)}s \u2014 ${mode} may be hung (code: ${cleanedCode.slice(0, 100)}...)`
174
182
  );
175
183
  }
176
184
  }, STALL_THRESHOLD_MS);
177
185
  const execStartTs = Date.now();
178
186
  const sigintHandler = () => {
179
- logger.info("exec-interrupted", {
187
+ logger.info(`${mode}-interrupted`, {
180
188
  session,
181
189
  duration: Date.now() - execStartTs,
182
190
  codePreview: cleanedCode.slice(0, 200)
183
191
  });
184
192
  };
185
193
  process.on("SIGINT", sigintHandler);
186
- wrapPageForActionLogging(page, session, resolvedPageId, onActivity);
187
- if (visualize) {
194
+ if (mode === "exec") {
195
+ wrapPageForActionLogging(page, session, resolvedPageId, onActivity);
196
+ }
197
+ if (visualize && mode === "exec") {
188
198
  await installInstrumentation(page, { visualize: true, logger });
189
199
  }
190
200
  try {
191
- const execState = {};
192
- const networkLog = (opts = {}) => {
193
- return readNetworkLog(session, opts);
194
- };
195
- const actionLog = (opts = {}) => {
196
- return readActionLog(session, opts);
197
- };
198
- const helpers = {
199
- page,
200
- context,
201
- state: execState,
202
- browser,
203
- networkLog,
204
- actionLog,
205
- console,
206
- setTimeout,
207
- setInterval,
208
- clearTimeout,
209
- clearInterval,
210
- fetch,
211
- URL,
212
- Buffer
213
- };
201
+ const helpers = mode === "readonly-exec" ? createReadonlyExecHelpers(page, { onActivity }) : (() => {
202
+ const execState = {};
203
+ const networkLog = (opts = {}) => {
204
+ return readNetworkLog(session, opts);
205
+ };
206
+ const actionLog = (opts = {}) => {
207
+ return readActionLog(session, opts);
208
+ };
209
+ return {
210
+ page,
211
+ context,
212
+ state: execState,
213
+ browser,
214
+ networkLog,
215
+ actionLog,
216
+ console,
217
+ setTimeout,
218
+ setInterval,
219
+ clearTimeout,
220
+ clearInterval,
221
+ fetch,
222
+ URL,
223
+ Buffer
224
+ };
225
+ })();
214
226
  const helperNames = Object.keys(helpers);
215
227
  const fn = compileExecFunction(cleanedCode, helperNames);
216
228
  const result = await fn(...Object.values(helpers));
217
- logger.info("exec-success", { session, hasResult: result !== void 0 });
229
+ logger.info(`${mode}-success`, { session, hasResult: result !== void 0 });
218
230
  if (result !== void 0) {
219
231
  console.log(
220
232
  typeof result === "string" ? result : JSON.stringify(result, null, 2)
@@ -223,7 +235,7 @@ async function runExec(code, session, logger, visualize = false, pageId) {
223
235
  console.log("Executed successfully");
224
236
  }
225
237
  } catch (err) {
226
- logger.error("exec-error", {
238
+ logger.error(`${mode}-error`, {
227
239
  error: err,
228
240
  session,
229
241
  codePreview: cleanedCode.slice(0, 200)
@@ -445,13 +457,15 @@ async function runIntegrationFromFile(args, logger) {
445
457
  );
446
458
  const payload = JSON.stringify({
447
459
  integrationPath: args.integrationPath,
448
- workflowName: args.workflowName,
449
460
  session: args.session,
450
461
  params: args.params,
451
462
  headless: args.headless,
452
463
  visualize: args.visualize,
453
464
  authProfileDomain: args.authProfileDomain,
454
- viewport: args.viewport
465
+ viewport: args.viewport,
466
+ accessMode: args.accessMode,
467
+ cdpEndpoint: args.cdpEndpoint,
468
+ provider: args.provider
455
469
  });
456
470
  const worker = spawn(
457
471
  process.execPath,
@@ -526,6 +540,7 @@ const execInput = SimpleCLI.input({
526
540
  const execCommand = SimpleCLI.command({
527
541
  description: "Execute Playwright TypeScript code"
528
542
  }).input(execInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
543
+ assertSessionAllowsCommand(ctx.sessionState, "exec", ["write-access"]);
529
544
  const code = input.code;
530
545
  const codeFromArgsOrStdin = code === "-" ? readStdinSync() : code;
531
546
  if (codeFromArgsOrStdin === null) {
@@ -537,18 +552,48 @@ const execCommand = SimpleCLI.command({
537
552
  codeFromArgsOrStdin,
538
553
  ctx.session,
539
554
  ctx.logger,
540
- input.visualize,
541
- input.page
555
+ {
556
+ visualize: input.visualize,
557
+ pageId: input.page,
558
+ mode: "exec"
559
+ }
542
560
  );
543
561
  });
544
- const runUsage = `Usage: libretto run <integrationFile> <workflowName> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--no-visualize] [--viewport WxH]`;
562
+ const readonlyExecInput = SimpleCLI.input({
563
+ positionals: [
564
+ SimpleCLI.positional("code", z.string().optional(), {
565
+ help: "Read-only Playwright TypeScript code to execute"
566
+ })
567
+ ],
568
+ named: {
569
+ session: sessionOption(),
570
+ page: pageOption()
571
+ }
572
+ }).refine(
573
+ (input) => input.code !== void 0,
574
+ `Usage: libretto readonly-exec <code|-> [--session <name>] [--page <id>]
575
+ echo '<code>' | libretto readonly-exec - [--session <name>] [--page <id>]`
576
+ );
577
+ const readonlyExecCommand = SimpleCLI.command({
578
+ description: "Execute read-only Playwright inspection code"
579
+ }).input(readonlyExecInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
580
+ const code = input.code;
581
+ const codeFromArgsOrStdin = code === "-" ? readStdinSync() : code;
582
+ if (codeFromArgsOrStdin === null) {
583
+ throw new Error(
584
+ "Missing stdin input for `readonly-exec -`. Pipe inspection code into stdin."
585
+ );
586
+ }
587
+ await runExec(codeFromArgsOrStdin, ctx.session, ctx.logger, {
588
+ pageId: input.page,
589
+ mode: "readonly-exec"
590
+ });
591
+ });
592
+ const runUsage = `Usage: libretto run <integrationFile> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--read-only|--write-access] [--no-visualize] [--viewport WxH]`;
545
593
  const runInput = SimpleCLI.input({
546
594
  positionals: [
547
595
  SimpleCLI.positional("integrationFile", z.string().optional(), {
548
596
  help: "Path to the integration file"
549
- }),
550
- SimpleCLI.positional("workflowName", z.string().optional(), {
551
- help: "Workflow name to run (from workflow(name, handler))"
552
597
  })
553
598
  ],
554
599
  named: {
@@ -565,6 +610,14 @@ const runInput = SimpleCLI.input({
565
610
  }),
566
611
  headed: SimpleCLI.flag({ help: "Run in headed mode" }),
567
612
  headless: SimpleCLI.flag({ help: "Run in headless mode" }),
613
+ readOnly: SimpleCLI.flag({
614
+ name: "read-only",
615
+ help: "Create the session in read-only mode"
616
+ }),
617
+ writeAccess: SimpleCLI.flag({
618
+ name: "write-access",
619
+ help: "Create the session in write-access mode (overrides config default)"
620
+ }),
568
621
  noVisualize: SimpleCLI.flag({
569
622
  name: "no-visualize",
570
623
  help: "Disable ghost cursor + highlight visualization in headed mode"
@@ -575,10 +628,14 @@ const runInput = SimpleCLI.input({
575
628
  }),
576
629
  viewport: SimpleCLI.option(z.string().optional(), {
577
630
  help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)"
631
+ }),
632
+ provider: SimpleCLI.option(z.string().optional(), {
633
+ help: "Browser provider (local, kernel, browserbase)",
634
+ aliases: ["-p"]
578
635
  })
579
636
  }
580
637
  }).refine(
581
- (input) => Boolean(input.integrationFile && input.workflowName),
638
+ (input) => Boolean(input.integrationFile),
582
639
  runUsage
583
640
  ).refine(
584
641
  (input) => !(input.params && input.paramsFile),
@@ -586,6 +643,9 @@ const runInput = SimpleCLI.input({
586
643
  ).refine(
587
644
  (input) => !(input.headed && input.headless),
588
645
  "Cannot pass both --headed and --headless."
646
+ ).refine(
647
+ (input) => !(input.readOnly && input.writeAccess),
648
+ "Cannot pass both --read-only and --write-access."
589
649
  );
590
650
  function resolveRunParams(rawInlineParams, paramsFile) {
591
651
  if (paramsFile) {
@@ -605,8 +665,9 @@ function resolveRunParams(rawInlineParams, paramsFile) {
605
665
  return {};
606
666
  }
607
667
  const runCommand = SimpleCLI.command({
608
- description: "Run an exported Libretto workflow from a file"
668
+ description: "Run the default-exported Libretto workflow from a file"
609
669
  }).input(runInput).use(withAutoSession()).handle(async ({ input, ctx }) => {
670
+ warnIfInstalledSkillOutOfDate();
610
671
  await stopExistingFailedRunSession(ctx.session, ctx.logger);
611
672
  assertSessionAvailableForStart(ctx.session, ctx.logger);
612
673
  const params = resolveRunParams(input.params, input.paramsFile);
@@ -616,20 +677,52 @@ const runCommand = SimpleCLI.command({
616
677
  parseViewportArg(input.viewport),
617
678
  ctx.logger
618
679
  );
619
- await runIntegrationFromFile(
620
- {
621
- integrationPath: input.integrationFile,
622
- workflowName: input.workflowName,
623
- session: ctx.session,
624
- params,
625
- tsconfigPath: input.tsconfig,
626
- headless: headlessMode ?? false,
627
- visualize,
628
- authProfileDomain: input.authProfile,
629
- viewport
630
- },
631
- ctx.logger
632
- );
680
+ const providerName = resolveProviderName(input.provider);
681
+ let cdpEndpoint;
682
+ let providerInfo;
683
+ let provider;
684
+ if (providerName !== "local") {
685
+ provider = getCloudProviderApi(providerName);
686
+ console.log(
687
+ `Creating ${providerName} browser session (session: ${ctx.session})...`
688
+ );
689
+ const providerSession = await provider.createSession();
690
+ console.log(`Connecting to ${providerName} browser...`);
691
+ cdpEndpoint = providerSession.cdpEndpoint;
692
+ providerInfo = {
693
+ name: providerName,
694
+ sessionId: providerSession.sessionId
695
+ };
696
+ }
697
+ try {
698
+ await runIntegrationFromFile(
699
+ {
700
+ integrationPath: input.integrationFile,
701
+ session: ctx.session,
702
+ params,
703
+ tsconfigPath: input.tsconfig,
704
+ headless: cdpEndpoint ? true : headlessMode ?? false,
705
+ visualize,
706
+ authProfileDomain: input.authProfile,
707
+ viewport,
708
+ accessMode: input.readOnly ? "read-only" : input.writeAccess ? "write-access" : readLibrettoConfig().sessionMode ?? "write-access",
709
+ cdpEndpoint,
710
+ provider: providerInfo
711
+ },
712
+ ctx.logger
713
+ );
714
+ } finally {
715
+ if (provider && providerInfo) {
716
+ try {
717
+ await provider.closeSession(providerInfo.sessionId);
718
+ } catch (cleanupErr) {
719
+ console.error(
720
+ `Failed to clean up ${providerInfo.name} session ${providerInfo.sessionId}:`,
721
+ cleanupErr instanceof Error ? cleanupErr.message : cleanupErr
722
+ );
723
+ }
724
+ }
725
+ }
633
726
  });
634
727
  const resumeInput = SimpleCLI.input({
635
728
  positionals: [],
@@ -644,6 +737,7 @@ const resumeCommand = SimpleCLI.command({
644
737
  });
645
738
  const executionCommands = {
646
739
  exec: execCommand,
740
+ "readonly-exec": readonlyExecCommand,
647
741
  run: runCommand,
648
742
  resume: resumeCommand
649
743
  };
@@ -651,6 +745,8 @@ export {
651
745
  execCommand,
652
746
  execInput,
653
747
  executionCommands,
748
+ readonlyExecCommand,
749
+ readonlyExecInput,
654
750
  resumeCommand,
655
751
  resumeInput,
656
752
  runCommand,