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
@@ -14,16 +14,21 @@ import { parseViewportArg } from "./browser.js";
14
14
  import { getPauseSignalPaths } from "../core/pause-signals.js";
15
15
  import {
16
16
  assertSessionAvailableForStart,
17
+ assertSessionAllowsCommand,
17
18
  clearSessionState,
18
19
  readSessionState,
19
20
  setSessionStatus,
20
21
  type SessionState,
21
22
  } from "../core/session.js";
23
+ import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
22
24
  import {
23
25
  readActionLog,
24
26
  readNetworkLog,
25
27
  wrapPageForActionLogging,
26
28
  } from "../core/telemetry.js";
29
+ import { readLibrettoConfig } from "../core/config.js";
30
+ import { resolveProviderName, getCloudProviderApi } from "../core/providers/index.js";
31
+ import { createReadonlyExecHelpers } from "../core/readonly-exec.js";
27
32
  import type { RunIntegrationWorkerRequest } from "../workers/run-integration-worker-protocol.js";
28
33
  import { SimpleCLI } from "../framework/simple-cli.js";
29
34
  import {
@@ -37,6 +42,7 @@ type ExecFunction = (...args: unknown[]) => Promise<unknown>;
37
42
  type RunIntegrationCommandRequest = RunIntegrationWorkerRequest & {
38
43
  tsconfigPath?: string;
39
44
  };
45
+ type ExecMode = "exec" | "readonly-exec";
40
46
 
41
47
  type StripTypeScriptTypesFn = (
42
48
  code: string,
@@ -202,14 +208,20 @@ async function runExec(
202
208
  code: string,
203
209
  session: string,
204
210
  logger: LoggerApi,
205
- visualize = false,
206
- pageId?: string,
211
+ options: {
212
+ visualize?: boolean;
213
+ pageId?: string;
214
+ mode?: ExecMode;
215
+ } = {},
207
216
  ): Promise<void> {
217
+ const visualize = options.visualize ?? false;
218
+ const pageId = options.pageId;
219
+ const mode = options.mode ?? "exec";
208
220
  const { cleaned: cleanedCode, strippedCount } = stripEmptyCatchHandlers(code);
209
221
  if (strippedCount > 0) {
210
222
  console.log("(Stripped `.catch(() => {})` — letting errors bubble up)");
211
223
  }
212
- logger.info("exec-start", {
224
+ logger.info(`${mode}-start`, {
213
225
  session,
214
226
  codeLength: cleanedCode.length,
215
227
  codePreview: cleanedCode.slice(0, 200),
@@ -235,20 +247,20 @@ async function runExec(
235
247
  const stallInterval = setInterval(() => {
236
248
  const silenceMs = Date.now() - lastActivityTs;
237
249
  if (silenceMs >= STALL_THRESHOLD_MS) {
238
- logger.warn("exec-stall-warning", {
250
+ logger.warn(`${mode}-stall-warning`, {
239
251
  session,
240
252
  silenceMs,
241
253
  codePreview: cleanedCode.slice(0, 200),
242
254
  });
243
255
  console.warn(
244
- `[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1000)}s — exec may be hung (code: ${cleanedCode.slice(0, 100)}...)`,
256
+ `[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1000)}s — ${mode} may be hung (code: ${cleanedCode.slice(0, 100)}...)`,
245
257
  );
246
258
  }
247
259
  }, STALL_THRESHOLD_MS);
248
260
 
249
261
  const execStartTs = Date.now();
250
262
  const sigintHandler = () => {
251
- logger.info("exec-interrupted", {
263
+ logger.info(`${mode}-interrupted`, {
252
264
  session,
253
265
  duration: Date.now() - execStartTs,
254
266
  codePreview: cleanedCode.slice(0, 200),
@@ -256,60 +268,67 @@ async function runExec(
256
268
  };
257
269
  process.on("SIGINT", sigintHandler);
258
270
 
259
- wrapPageForActionLogging(page, session, resolvedPageId, onActivity);
271
+ if (mode === "exec") {
272
+ wrapPageForActionLogging(page, session, resolvedPageId, onActivity);
273
+ }
260
274
 
261
- if (visualize) {
275
+ if (visualize && mode === "exec") {
262
276
  await installInstrumentation(page, { visualize: true, logger });
263
277
  }
264
278
 
265
279
  try {
266
- const execState: Record<string, unknown> = {};
267
-
268
- const networkLog = (
269
- opts: {
270
- last?: number;
271
- filter?: string;
272
- method?: string;
273
- pageId?: string;
274
- } = {},
275
- ) => {
276
- return readNetworkLog(session, opts);
277
- };
278
-
279
- const actionLog = (
280
- opts: {
281
- last?: number;
282
- filter?: string;
283
- action?: string;
284
- source?: string;
285
- pageId?: string;
286
- } = {},
287
- ) => {
288
- return readActionLog(session, opts);
289
- };
290
-
291
- const helpers = {
292
- page,
293
- context,
294
- state: execState,
295
- browser,
296
- networkLog,
297
- actionLog,
298
- console,
299
- setTimeout,
300
- setInterval,
301
- clearTimeout,
302
- clearInterval,
303
- fetch,
304
- URL,
305
- Buffer,
306
- };
280
+ const helpers =
281
+ mode === "readonly-exec"
282
+ ? createReadonlyExecHelpers(page, { onActivity })
283
+ : (() => {
284
+ const execState: Record<string, unknown> = {};
285
+
286
+ const networkLog = (
287
+ opts: {
288
+ last?: number;
289
+ filter?: string;
290
+ method?: string;
291
+ pageId?: string;
292
+ } = {},
293
+ ) => {
294
+ return readNetworkLog(session, opts);
295
+ };
296
+
297
+ const actionLog = (
298
+ opts: {
299
+ last?: number;
300
+ filter?: string;
301
+ action?: string;
302
+ source?: string;
303
+ pageId?: string;
304
+ } = {},
305
+ ) => {
306
+ return readActionLog(session, opts);
307
+ };
308
+
309
+ return {
310
+ page,
311
+ context,
312
+ state: execState,
313
+ browser,
314
+ networkLog,
315
+ actionLog,
316
+ console,
317
+ setTimeout,
318
+ setInterval,
319
+ clearTimeout,
320
+ clearInterval,
321
+ fetch,
322
+ URL,
323
+ Buffer,
324
+ };
325
+ })();
307
326
 
308
327
  const helperNames = Object.keys(helpers);
309
328
  const fn = compileExecFunction(cleanedCode, helperNames);
310
329
 
311
330
  const result = await fn(...Object.values(helpers));
312
- logger.info("exec-success", { session, hasResult: result !== undefined });
331
+ logger.info(`${mode}-success`, { session, hasResult: result !== undefined });
313
332
  if (result !== undefined) {
314
333
  console.log(
315
334
  typeof result === "string" ? result : JSON.stringify(result, null, 2),
@@ -318,7 +337,7 @@ async function runExec(
318
337
  console.log("Executed successfully");
319
338
  }
320
339
  } catch (err) {
321
- logger.error("exec-error", {
340
+ logger.error(`${mode}-error`, {
322
341
  error: err,
323
342
  session,
324
343
  codePreview: cleanedCode.slice(0, 200),
@@ -605,13 +624,15 @@ async function runIntegrationFromFile(
605
624
  );
606
625
  const payload = JSON.stringify({
607
626
  integrationPath: args.integrationPath,
608
- workflowName: args.workflowName,
609
627
  session: args.session,
610
628
  params: args.params,
611
629
  headless: args.headless,
612
630
  visualize: args.visualize,
613
631
  authProfileDomain: args.authProfileDomain,
614
632
  viewport: args.viewport,
633
+ accessMode: args.accessMode,
634
+ cdpEndpoint: args.cdpEndpoint,
635
+ provider: args.provider,
615
636
  } satisfies RunIntegrationWorkerRequest);
616
637
  const worker = spawn(
617
638
  process.execPath,
@@ -690,6 +711,7 @@ export const execCommand = SimpleCLI.command({
690
711
  .input(execInput)
691
712
  .use(withRequiredSession())
692
713
  .handle(async ({ input, ctx }) => {
714
+ assertSessionAllowsCommand(ctx.sessionState, "exec", ["write-access"]);
693
715
  const code = input.code!;
694
716
  const codeFromArgsOrStdin = code === "-" ? readStdinSync() : code;
695
717
  if (codeFromArgsOrStdin === null) {
@@ -701,21 +723,55 @@ export const execCommand = SimpleCLI.command({
701
723
  codeFromArgsOrStdin,
702
724
  ctx.session,
703
725
  ctx.logger,
704
- input.visualize,
705
- input.page,
726
+ {
727
+ visualize: input.visualize,
728
+ pageId: input.page,
729
+ mode: "exec",
730
+ },
706
731
  );
707
732
  });
708
733
 
709
- const runUsage = `Usage: libretto run <integrationFile> <workflowName> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--no-visualize] [--viewport WxH]`;
734
+ export const readonlyExecInput = SimpleCLI.input({
735
+ positionals: [
736
+ SimpleCLI.positional("code", z.string().optional(), {
737
+ help: "Read-only Playwright TypeScript code to execute",
738
+ }),
739
+ ],
740
+ named: {
741
+ session: sessionOption(),
742
+ page: pageOption(),
743
+ },
744
+ }).refine(
745
+ (input) => input.code !== undefined,
746
+ `Usage: libretto readonly-exec <code|-> [--session <name>] [--page <id>]\n echo '<code>' | libretto readonly-exec - [--session <name>] [--page <id>]`,
747
+ );
748
+
749
+ export const readonlyExecCommand = SimpleCLI.command({
750
+ description: "Execute read-only Playwright inspection code",
751
+ })
752
+ .input(readonlyExecInput)
753
+ .use(withRequiredSession())
754
+ .handle(async ({ input, ctx }) => {
755
+ const code = input.code!;
756
+ const codeFromArgsOrStdin = code === "-" ? readStdinSync() : code;
757
+ if (codeFromArgsOrStdin === null) {
758
+ throw new Error(
759
+ "Missing stdin input for `readonly-exec -`. Pipe inspection code into stdin.",
760
+ );
761
+ }
762
+ await runExec(codeFromArgsOrStdin, ctx.session, ctx.logger, {
763
+ pageId: input.page,
764
+ mode: "readonly-exec",
765
+ });
766
+ });
767
+
768
+ const runUsage = `Usage: libretto run <integrationFile> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--read-only|--write-access] [--no-visualize] [--viewport WxH]`;
710
769
 
711
770
  export const runInput = SimpleCLI.input({
712
771
  positionals: [
713
772
  SimpleCLI.positional("integrationFile", z.string().optional(), {
714
773
  help: "Path to the integration file",
715
774
  }),
716
- SimpleCLI.positional("workflowName", z.string().optional(), {
717
- help: "Workflow name to run (from workflow(name, handler))",
718
- }),
719
775
  ],
720
776
  named: {
721
777
  session: sessionOption(),
@@ -731,6 +787,14 @@ export const runInput = SimpleCLI.input({
731
787
  }),
732
788
  headed: SimpleCLI.flag({ help: "Run in headed mode" }),
733
789
  headless: SimpleCLI.flag({ help: "Run in headless mode" }),
790
+ readOnly: SimpleCLI.flag({
791
+ name: "read-only",
792
+ help: "Create the session in read-only mode",
793
+ }),
794
+ writeAccess: SimpleCLI.flag({
795
+ name: "write-access",
796
+ help: "Create the session in write-access mode (overrides config default)",
797
+ }),
734
798
  noVisualize: SimpleCLI.flag({
735
799
  name: "no-visualize",
736
800
  help: "Disable ghost cursor + highlight visualization in headed mode",
@@ -742,10 +806,14 @@ export const runInput = SimpleCLI.input({
742
806
  viewport: SimpleCLI.option(z.string().optional(), {
743
807
  help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)",
744
808
  }),
809
+ provider: SimpleCLI.option(z.string().optional(), {
810
+ help: "Browser provider (local, kernel, browserbase)",
811
+ aliases: ["-p"],
812
+ }),
745
813
  },
746
814
  })
747
815
  .refine(
748
- (input) => Boolean(input.integrationFile && input.workflowName),
816
+ (input) => Boolean(input.integrationFile),
749
817
  runUsage,
750
818
  )
751
819
  .refine(
@@ -755,6 +823,10 @@ export const runInput = SimpleCLI.input({
755
823
  .refine(
756
824
  (input) => !(input.headed && input.headless),
757
825
  "Cannot pass both --headed and --headless.",
826
+ )
827
+ .refine(
828
+ (input) => !(input.readOnly && input.writeAccess),
829
+ "Cannot pass both --read-only and --write-access.",
758
830
  );
759
831
 
760
832
  function resolveRunParams(
@@ -779,11 +851,12 @@ function resolveRunParams(
779
851
  }
780
852
 
781
853
  export const runCommand = SimpleCLI.command({
782
- description: "Run an exported Libretto workflow from a file",
854
+ description: "Run the default-exported Libretto workflow from a file",
783
855
  })
784
856
  .input(runInput)
785
857
  .use(withAutoSession())
786
858
  .handle(async ({ input, ctx }) => {
859
+ warnIfInstalledSkillOutOfDate();
787
860
  await stopExistingFailedRunSession(ctx.session, ctx.logger);
788
861
  assertSessionAvailableForStart(ctx.session, ctx.logger);
789
862
 
@@ -799,20 +872,53 @@ export const runCommand = SimpleCLI.command({
799
872
  ctx.logger,
800
873
  );
801
874
 
802
- await runIntegrationFromFile(
803
- {
804
- integrationPath: input.integrationFile!,
805
- workflowName: input.workflowName!,
806
- session: ctx.session,
807
- params,
808
- tsconfigPath: input.tsconfig,
809
- headless: headlessMode ?? false,
810
- visualize,
811
- authProfileDomain: input.authProfile,
812
- viewport,
813
- },
814
- ctx.logger,
815
- );
875
+ const providerName = resolveProviderName(input.provider);
876
+ let cdpEndpoint: string | undefined;
877
+ let providerInfo: { name: string; sessionId: string } | undefined;
878
+ let provider: ReturnType<typeof getCloudProviderApi> | undefined;
879
+ if (providerName !== "local") {
880
+ provider = getCloudProviderApi(providerName);
881
+ console.log(
882
+ `Creating ${providerName} browser session (session: ${ctx.session})...`,
883
+ );
884
+ const providerSession = await provider.createSession();
885
+ console.log(`Connecting to ${providerName} browser...`);
886
+ cdpEndpoint = providerSession.cdpEndpoint;
887
+ providerInfo = {
888
+ name: providerName,
889
+ sessionId: providerSession.sessionId,
890
+ };
891
+ }
892
+
893
+ try {
894
+ await runIntegrationFromFile(
895
+ {
896
+ integrationPath: input.integrationFile!,
897
+ session: ctx.session,
898
+ params,
899
+ tsconfigPath: input.tsconfig,
900
+ headless: cdpEndpoint ? true : (headlessMode ?? false),
901
+ visualize,
902
+ authProfileDomain: input.authProfile,
903
+ viewport,
904
+ accessMode: input.readOnly ? "read-only" : input.writeAccess ? "write-access" : (readLibrettoConfig().sessionMode ?? "write-access"),
905
+ cdpEndpoint,
906
+ provider: providerInfo,
907
+ },
908
+ ctx.logger,
909
+ );
910
+ } finally {
911
+ if (provider && providerInfo) {
912
+ try {
913
+ await provider.closeSession(providerInfo.sessionId);
914
+ } catch (cleanupErr) {
915
+ console.error(
916
+ `Failed to clean up ${providerInfo.name} session ${providerInfo.sessionId}:`,
917
+ cleanupErr instanceof Error ? cleanupErr.message : cleanupErr,
918
+ );
919
+ }
920
+ }
921
+ }
816
922
  });
817
923
 
818
924
  export const resumeInput = SimpleCLI.input({
@@ -833,6 +939,7 @@ export const resumeCommand = SimpleCLI.command({
833
939
 
834
940
  export const executionCommands = {
835
941
  exec: execCommand,
942
+ "readonly-exec": readonlyExecCommand,
836
943
  run: runCommand,
837
944
  resume: resumeCommand,
838
945
  };