libretto 0.4.4 → 0.5.1

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 (194) hide show
  1. package/README.md +106 -36
  2. package/dist/cli/cli.js +39 -113
  3. package/dist/cli/commands/ai.js +1 -1
  4. package/dist/cli/commands/browser.js +87 -60
  5. package/dist/cli/commands/execution.js +201 -88
  6. package/dist/cli/commands/init.js +30 -8
  7. package/dist/cli/commands/logs.js +5 -6
  8. package/dist/cli/commands/shared.js +30 -29
  9. package/dist/cli/commands/snapshot.js +26 -39
  10. package/dist/cli/core/ai-config.js +9 -2
  11. package/dist/cli/core/api-snapshot-analyzer.js +15 -5
  12. package/dist/cli/core/browser.js +141 -33
  13. package/dist/cli/core/context.js +7 -18
  14. package/dist/cli/core/session-telemetry.js +5 -2
  15. package/dist/cli/core/session.js +23 -10
  16. package/dist/cli/core/snapshot-analyzer.js +16 -33
  17. package/dist/cli/core/snapshot-api-config.js +2 -6
  18. package/dist/cli/core/telemetry.js +10 -2
  19. package/dist/cli/framework/simple-cli.js +45 -25
  20. package/dist/cli/router.js +14 -21
  21. package/dist/cli/workers/run-integration-runtime.js +26 -7
  22. package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
  23. package/dist/cli/workers/run-integration-worker.js +1 -4
  24. package/dist/index.d.ts +1 -2
  25. package/dist/index.js +7 -10
  26. package/dist/runtime/download/download.js +5 -1
  27. package/dist/runtime/extract/extract.js +11 -2
  28. package/dist/runtime/network/network.js +8 -1
  29. package/dist/runtime/recovery/agent.js +6 -2
  30. package/dist/runtime/recovery/errors.js +3 -1
  31. package/dist/runtime/recovery/recovery.js +3 -1
  32. package/dist/shared/condense-dom/condense-dom.js +6 -13
  33. package/dist/shared/config/config.d.ts +1 -9
  34. package/dist/shared/config/config.js +0 -18
  35. package/dist/shared/config/index.d.ts +2 -1
  36. package/dist/shared/config/index.js +0 -10
  37. package/dist/shared/debug/pause.js +9 -3
  38. package/dist/shared/instrumentation/instrument.js +101 -5
  39. package/dist/shared/llm/ai-sdk-adapter.js +3 -1
  40. package/dist/shared/llm/client.js +3 -1
  41. package/dist/shared/logger/index.js +4 -1
  42. package/dist/shared/paths/paths.js +2 -1
  43. package/dist/shared/paths/repo-root.d.ts +3 -0
  44. package/dist/shared/paths/repo-root.js +24 -0
  45. package/dist/shared/run/api.js +3 -1
  46. package/dist/shared/run/browser.js +7 -2
  47. package/dist/shared/state/session-state.d.ts +2 -1
  48. package/dist/shared/state/session-state.js +5 -2
  49. package/dist/shared/visualization/ghost-cursor.js +19 -10
  50. package/dist/shared/visualization/highlight.js +9 -6
  51. package/dist/shared/workflow/workflow.d.ts +4 -5
  52. package/dist/shared/workflow/workflow.js +3 -5
  53. package/package.json +11 -8
  54. package/scripts/check-skills-sync.mjs +25 -0
  55. package/scripts/compare-eval-summary.mjs +47 -0
  56. package/scripts/postinstall.mjs +26 -17
  57. package/scripts/prepare-release.sh +97 -0
  58. package/scripts/skills-libretto.mjs +103 -0
  59. package/scripts/summarize-evals.mjs +135 -0
  60. package/scripts/sync-skills.mjs +12 -0
  61. package/skills/libretto/SKILL.md +130 -377
  62. package/skills/libretto/references/auth-profiles.md +30 -0
  63. package/skills/libretto/{code-generation-rules.md → references/code-generation-rules.md} +27 -42
  64. package/skills/libretto/references/configuration-file-reference.md +53 -0
  65. package/skills/libretto/references/pages-and-page-targeting.md +29 -0
  66. package/skills/libretto/references/site-security-review.md +143 -0
  67. package/src/cli/cli.ts +86 -0
  68. package/src/cli/commands/ai.ts +35 -0
  69. package/src/cli/commands/browser.ts +189 -0
  70. package/src/cli/commands/execution.ts +822 -0
  71. package/src/cli/commands/init.ts +350 -0
  72. package/src/cli/commands/logs.ts +128 -0
  73. package/src/cli/commands/shared.ts +69 -0
  74. package/src/cli/commands/snapshot.ts +312 -0
  75. package/src/cli/core/ai-config.ts +264 -0
  76. package/src/cli/core/api-snapshot-analyzer.ts +108 -0
  77. package/src/cli/core/browser.ts +976 -0
  78. package/src/cli/core/context.ts +127 -0
  79. package/src/cli/core/pause-signals.ts +35 -0
  80. package/src/cli/core/session-telemetry.ts +564 -0
  81. package/src/cli/core/session.ts +223 -0
  82. package/src/cli/core/snapshot-analyzer.ts +855 -0
  83. package/src/cli/core/snapshot-api-config.ts +231 -0
  84. package/src/cli/core/telemetry.ts +459 -0
  85. package/src/cli/framework/simple-cli.ts +1340 -0
  86. package/src/cli/index.ts +13 -0
  87. package/src/cli/router.ts +20 -0
  88. package/src/cli/workers/run-integration-runtime.ts +338 -0
  89. package/src/cli/workers/run-integration-worker-protocol.ts +16 -0
  90. package/src/cli/workers/run-integration-worker.ts +72 -0
  91. package/src/index.ts +127 -0
  92. package/src/runtime/download/download.ts +104 -0
  93. package/src/runtime/download/index.ts +7 -0
  94. package/src/runtime/extract/extract.ts +102 -0
  95. package/src/runtime/extract/index.ts +1 -0
  96. package/src/runtime/network/index.ts +5 -0
  97. package/src/runtime/network/network.ts +119 -0
  98. package/{dist/runtime/recovery/agent.cjs → src/runtime/recovery/agent.ts} +114 -76
  99. package/src/runtime/recovery/errors.ts +155 -0
  100. package/src/runtime/recovery/index.ts +7 -0
  101. package/src/runtime/recovery/recovery.ts +53 -0
  102. package/{dist/shared/condense-dom/condense-dom.cjs → src/shared/condense-dom/condense-dom.ts} +249 -124
  103. package/src/shared/config/config.ts +3 -0
  104. package/src/shared/config/index.ts +0 -0
  105. package/src/shared/debug/index.ts +1 -0
  106. package/src/shared/debug/pause.ts +91 -0
  107. package/src/shared/instrumentation/errors.ts +84 -0
  108. package/src/shared/instrumentation/index.ts +9 -0
  109. package/src/shared/instrumentation/instrument.ts +406 -0
  110. package/src/shared/llm/ai-sdk-adapter.ts +81 -0
  111. package/{dist/shared/llm/client.cjs → src/shared/llm/client.ts} +86 -80
  112. package/src/shared/llm/index.ts +3 -0
  113. package/src/shared/llm/types.ts +63 -0
  114. package/src/shared/logger/index.ts +13 -0
  115. package/src/shared/logger/logger.ts +358 -0
  116. package/src/shared/logger/sinks.ts +148 -0
  117. package/src/shared/paths/paths.ts +110 -0
  118. package/src/shared/paths/repo-root.ts +27 -0
  119. package/src/shared/run/api.ts +6 -0
  120. package/src/shared/run/browser.ts +107 -0
  121. package/src/shared/state/index.ts +11 -0
  122. package/src/shared/state/session-state.ts +77 -0
  123. package/src/shared/visualization/ghost-cursor.ts +213 -0
  124. package/src/shared/visualization/highlight.ts +149 -0
  125. package/src/shared/visualization/index.ts +18 -0
  126. package/src/shared/workflow/workflow.ts +36 -0
  127. package/dist/index.cjs +0 -144
  128. package/dist/index.d.cts +0 -21
  129. package/dist/runtime/download/download.cjs +0 -70
  130. package/dist/runtime/download/download.d.cts +0 -35
  131. package/dist/runtime/download/index.cjs +0 -30
  132. package/dist/runtime/download/index.d.cts +0 -3
  133. package/dist/runtime/extract/extract.cjs +0 -88
  134. package/dist/runtime/extract/extract.d.cts +0 -23
  135. package/dist/runtime/extract/index.cjs +0 -28
  136. package/dist/runtime/extract/index.d.cts +0 -5
  137. package/dist/runtime/network/index.cjs +0 -28
  138. package/dist/runtime/network/index.d.cts +0 -4
  139. package/dist/runtime/network/network.cjs +0 -91
  140. package/dist/runtime/network/network.d.cts +0 -28
  141. package/dist/runtime/recovery/agent.d.cts +0 -13
  142. package/dist/runtime/recovery/errors.cjs +0 -124
  143. package/dist/runtime/recovery/errors.d.cts +0 -31
  144. package/dist/runtime/recovery/index.cjs +0 -34
  145. package/dist/runtime/recovery/index.d.cts +0 -7
  146. package/dist/runtime/recovery/recovery.cjs +0 -55
  147. package/dist/runtime/recovery/recovery.d.cts +0 -12
  148. package/dist/shared/condense-dom/condense-dom.d.cts +0 -34
  149. package/dist/shared/config/config.cjs +0 -44
  150. package/dist/shared/config/config.d.cts +0 -10
  151. package/dist/shared/config/index.cjs +0 -32
  152. package/dist/shared/config/index.d.cts +0 -1
  153. package/dist/shared/debug/index.cjs +0 -28
  154. package/dist/shared/debug/index.d.cts +0 -1
  155. package/dist/shared/debug/pause.cjs +0 -86
  156. package/dist/shared/debug/pause.d.cts +0 -12
  157. package/dist/shared/instrumentation/errors.cjs +0 -81
  158. package/dist/shared/instrumentation/errors.d.cts +0 -12
  159. package/dist/shared/instrumentation/index.cjs +0 -35
  160. package/dist/shared/instrumentation/index.d.cts +0 -6
  161. package/dist/shared/instrumentation/instrument.cjs +0 -206
  162. package/dist/shared/instrumentation/instrument.d.cts +0 -32
  163. package/dist/shared/llm/ai-sdk-adapter.cjs +0 -71
  164. package/dist/shared/llm/ai-sdk-adapter.d.cts +0 -22
  165. package/dist/shared/llm/client.d.cts +0 -13
  166. package/dist/shared/llm/index.cjs +0 -31
  167. package/dist/shared/llm/index.d.cts +0 -5
  168. package/dist/shared/llm/types.cjs +0 -16
  169. package/dist/shared/llm/types.d.cts +0 -67
  170. package/dist/shared/logger/index.cjs +0 -37
  171. package/dist/shared/logger/index.d.cts +0 -2
  172. package/dist/shared/logger/logger.cjs +0 -232
  173. package/dist/shared/logger/logger.d.cts +0 -86
  174. package/dist/shared/logger/sinks.cjs +0 -160
  175. package/dist/shared/logger/sinks.d.cts +0 -9
  176. package/dist/shared/paths/paths.cjs +0 -104
  177. package/dist/shared/paths/paths.d.cts +0 -10
  178. package/dist/shared/run/api.cjs +0 -28
  179. package/dist/shared/run/api.d.cts +0 -2
  180. package/dist/shared/run/browser.cjs +0 -98
  181. package/dist/shared/run/browser.d.cts +0 -22
  182. package/dist/shared/state/index.cjs +0 -38
  183. package/dist/shared/state/index.d.cts +0 -2
  184. package/dist/shared/state/session-state.cjs +0 -92
  185. package/dist/shared/state/session-state.d.cts +0 -40
  186. package/dist/shared/visualization/ghost-cursor.cjs +0 -174
  187. package/dist/shared/visualization/ghost-cursor.d.cts +0 -37
  188. package/dist/shared/visualization/highlight.cjs +0 -134
  189. package/dist/shared/visualization/highlight.d.cts +0 -22
  190. package/dist/shared/visualization/index.cjs +0 -45
  191. package/dist/shared/visualization/index.d.cts +0 -3
  192. package/dist/shared/workflow/workflow.cjs +0 -47
  193. package/dist/shared/workflow/workflow.d.cts +0 -21
  194. package/skills/libretto/integration-approach-selection.md +0 -174
@@ -1,9 +1,4 @@
1
- import {
2
- existsSync,
3
- mkdtempSync,
4
- readFileSync,
5
- rmSync
6
- } from "node:fs";
1
+ import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
7
2
  import { extname, isAbsolute, join, resolve } from "node:path";
8
3
  import { spawn } from "node:child_process";
9
4
  import { tmpdir } from "node:os";
@@ -60,7 +55,12 @@ Screenshot file path: ${pngPath}
60
55
  Use the screenshot alongside the HTML snapshot context above.`;
61
56
  }
62
57
  async runAnalyzer(args, logger, stdinText) {
63
- const result = await runExternalCommand(this.command, args, logger, stdinText);
58
+ const result = await runExternalCommand(
59
+ this.command,
60
+ args,
61
+ logger,
62
+ stdinText
63
+ );
64
64
  if (result.exitCode !== 0) {
65
65
  throw new Error(
66
66
  `Analyzer command failed (${[this.command, ...args].join(" ")}).
@@ -76,7 +76,7 @@ ${stripAnsi(result.stderr).trim() || stripAnsi(result.stdout).trim() || "No erro
76
76
  }
77
77
  class CodexUserCodingAgent extends UserCodingAgent {
78
78
  async analyzeSnapshot(prompt, pngPath, logger) {
79
- const tempDir = mkdtempSync(join(tmpdir(), "libretto-cli-analyzer-"));
79
+ const tempDir = mkdtempSync(join(tmpdir(), "libretto-analyzer-"));
80
80
  const outputPath = join(
81
81
  tempDir,
82
82
  `snapshot-analyzer-${Date.now()}-${Math.random().toString(36).slice(2)}.json`
@@ -171,7 +171,7 @@ async function runExternalCommand(command, args, logger, stdinText) {
171
171
  if (error.code === "ENOENT") {
172
172
  reject(
173
173
  new Error(
174
- `Command not found: ${command}. Configure AI with 'libretto-cli ai configure'.`
174
+ `Command not found: ${command}. Configure AI with 'libretto ai configure'.`
175
175
  )
176
176
  );
177
177
  return;
@@ -535,7 +535,9 @@ function buildInlinePromptSelection(args, fullHtmlContent, condensedHtmlContent,
535
535
  fullDomChars: fullHtmlContent.length,
536
536
  fullDomEstimatedTokens: estimateTokensFromChars(fullHtmlContent.length),
537
537
  condensedDomChars: condensedHtmlContent.length,
538
- condensedDomEstimatedTokens: estimateTokensFromChars(condensedHtmlContent.length),
538
+ condensedDomEstimatedTokens: estimateTokensFromChars(
539
+ condensedHtmlContent.length
540
+ ),
539
541
  configuredModel: model
540
542
  };
541
543
  const buildCandidate = (domSource, htmlContent, selectionReason, truncated) => {
@@ -607,7 +609,10 @@ function buildInlinePromptSelection(args, fullHtmlContent, condensedHtmlContent,
607
609
  2e3,
608
610
  budget.promptBudgetTokens - estimateTokensFromChars(basePrompt.length)
609
611
  );
610
- const truncatedHtml = truncateText(condensedHtmlContent, availableHtmlTokens * 4);
612
+ const truncatedHtml = truncateText(
613
+ condensedHtmlContent,
614
+ availableHtmlTokens * 4
615
+ );
611
616
  return buildCandidate(
612
617
  "condensed",
613
618
  truncatedHtml.text,
@@ -615,27 +620,6 @@ function buildInlinePromptSelection(args, fullHtmlContent, condensedHtmlContent,
615
620
  truncatedHtml.truncated
616
621
  );
617
622
  }
618
- function formatInterpretationOutput(parsed, header = "Interpretation:") {
619
- const outputLines = [];
620
- outputLines.push(header);
621
- outputLines.push(`Answer: ${parsed.answer}`);
622
- outputLines.push("");
623
- if (parsed.selectors.length === 0) {
624
- outputLines.push("Selectors: none found.");
625
- } else {
626
- outputLines.push("Selectors:");
627
- parsed.selectors.forEach((selector, index) => {
628
- outputLines.push(` ${index + 1}. ${selector.label}`);
629
- outputLines.push(` selector: ${selector.selector}`);
630
- outputLines.push(` rationale: ${selector.rationale}`);
631
- });
632
- }
633
- if (parsed.notes && parsed.notes.trim()) {
634
- outputLines.push("");
635
- outputLines.push(`Notes: ${parsed.notes.trim()}`);
636
- }
637
- return outputLines.join("\n");
638
- }
639
623
  async function runInterpret(args, logger) {
640
624
  logger.info("interpret-start", {
641
625
  objective: args.objective,
@@ -676,7 +660,6 @@ export {
676
660
  InterpretResultSchema,
677
661
  buildInlinePromptSelection,
678
662
  canAnalyzeSnapshots,
679
- formatInterpretationOutput,
680
663
  getMimeType,
681
664
  readFileAsBase64,
682
665
  runInterpret
@@ -1,8 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
- import {
4
- readAiConfig
5
- } from "./ai-config.js";
3
+ import { readAiConfig } from "./ai-config.js";
6
4
  import { LIBRETTO_CONFIG_PATH, REPO_ROOT } from "./context.js";
7
5
  import {
8
6
  hasProviderCredentials,
@@ -154,9 +152,7 @@ function resolveSnapshotApiModel(config = readAiConfig()) {
154
152
  function resolveSnapshotApiModelOrThrow(config = readAiConfig()) {
155
153
  const selection = resolveSnapshotApiModel(config);
156
154
  if (!selection) {
157
- throw new SnapshotApiUnavailableError(
158
- noSnapshotApiConfiguredMessage()
159
- );
155
+ throw new SnapshotApiUnavailableError(noSnapshotApiConfiguredMessage());
160
156
  }
161
157
  if (!hasProviderCredentials(selection.provider)) {
162
158
  throw new SnapshotApiUnavailableError(
@@ -1,4 +1,9 @@
1
- import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
1
+ import {
2
+ appendFileSync,
3
+ existsSync,
4
+ readFileSync,
5
+ writeFileSync
6
+ } from "node:fs";
2
7
  import {
3
8
  getSessionActionsLogPath,
4
9
  getSessionNetworkLogPath
@@ -56,7 +61,10 @@ function clearNetworkLog(session) {
56
61
  function parentLogAction(session, entry) {
57
62
  try {
58
63
  const record = { ts: (/* @__PURE__ */ new Date()).toISOString(), ...entry };
59
- appendFileSync(getSessionActionsLogPath(session), JSON.stringify(record) + "\n");
64
+ appendFileSync(
65
+ getSessionActionsLogPath(session),
66
+ JSON.stringify(record) + "\n"
67
+ );
60
68
  } catch {
61
69
  }
62
70
  }
@@ -214,7 +214,9 @@ class SimpleCLIApp {
214
214
  if (isHelpFlag(argsBeforePassthrough[0])) {
215
215
  return [];
216
216
  }
217
- const helpFlagIndex = argsBeforePassthrough.findIndex((arg) => isHelpFlag(arg));
217
+ const helpFlagIndex = argsBeforePassthrough.findIndex(
218
+ (arg) => isHelpFlag(arg)
219
+ );
218
220
  if (helpFlagIndex >= 0) {
219
221
  return argsBeforePassthrough.slice(0, helpFlagIndex);
220
222
  }
@@ -229,7 +231,10 @@ class SimpleCLIApp {
229
231
  }
230
232
  throw new Error(`Unknown command: ${args.join(" ")}`);
231
233
  }
232
- const rawInput = this.parseCommandInput(command2, args.slice(command2.path.length));
234
+ const rawInput = this.parseCommandInput(
235
+ command2,
236
+ args.slice(command2.path.length)
237
+ );
233
238
  return {
234
239
  routeKey: command2.routeKey,
235
240
  rawInput
@@ -239,7 +244,9 @@ class SimpleCLIApp {
239
244
  const inputDefinition = command2.input?.getDefinition();
240
245
  if (!inputDefinition) {
241
246
  if (args.length > 0) {
242
- throw new Error(`Unexpected arguments for ${this.name} ${command2.path.join(" ")}.`);
247
+ throw new Error(
248
+ `Unexpected arguments for ${this.name} ${command2.path.join(" ")}.`
249
+ );
243
250
  }
244
251
  return {
245
252
  positionals: [],
@@ -256,7 +263,9 @@ class SimpleCLIApp {
256
263
  const arg = args[index];
257
264
  if (arg === "--") {
258
265
  if (!passthroughEntry) {
259
- throw new Error(`Unexpected "--" for ${this.name} ${command2.path.join(" ")}.`);
266
+ throw new Error(
267
+ `Unexpected "--" for ${this.name} ${command2.path.join(" ")}.`
268
+ );
260
269
  }
261
270
  named["--"] = args.slice(index + 1);
262
271
  break;
@@ -305,7 +314,11 @@ class SimpleCLIApp {
305
314
  }
306
315
  positionals.push(arg);
307
316
  }
308
- validateParsedPositionals(command2, inputDefinition.positionals, positionals);
317
+ validateParsedPositionals(
318
+ command2,
319
+ inputDefinition.positionals,
320
+ positionals
321
+ );
309
322
  validateRequiredNamedArgs(inputDefinition.named, named);
310
323
  return {
311
324
  positionals,
@@ -506,10 +519,7 @@ class SimpleCLIApp {
506
519
  function splitNamedArg(arg) {
507
520
  const separatorIndex = arg.indexOf("=");
508
521
  if (separatorIndex < 0) return [arg, void 0];
509
- return [
510
- arg.slice(0, separatorIndex),
511
- arg.slice(separatorIndex + 1)
512
- ];
522
+ return [arg.slice(0, separatorIndex), arg.slice(separatorIndex + 1)];
513
523
  }
514
524
  function readNamedArgValue(args, index, rawName, displayName, spec, inlineValue, namedSpecs) {
515
525
  if (spec.kind === "flag") {
@@ -549,22 +559,29 @@ function buildNamedArgLookup(namedDefinition) {
549
559
  return lookup;
550
560
  }
551
561
  function validateParsedPositionals(command2, definitions, positionals) {
552
- const variadicDefinition = definitions.find((definition) => definition.variadic);
562
+ const variadicDefinition = definitions.find(
563
+ (definition) => definition.variadic
564
+ );
553
565
  if (!variadicDefinition && positionals.length > definitions.length) {
554
566
  throw new Error(`Unexpected arguments for ${command2.path.join(" ")}.`);
555
567
  }
556
568
  definitions.forEach((definition, index) => {
557
569
  const value = definition.variadic ? positionals.slice(index) : positionals[index];
558
- if (value !== void 0 && (!Array.isArray(value) || value.length > 0)) return;
570
+ if (value !== void 0 && (!Array.isArray(value) || value.length > 0))
571
+ return;
559
572
  if (schemaAcceptsUndefined(definition.schema)) return;
560
573
  throw new Error(`Missing required argument <${definition.key}>.`);
561
574
  });
562
575
  }
563
576
  function validateInputDefinition(definition) {
564
- const variadicIndex = definition.positionals.findIndex((positional2) => positional2.variadic);
577
+ const variadicIndex = definition.positionals.findIndex(
578
+ (positional2) => positional2.variadic
579
+ );
565
580
  if (variadicIndex < 0) return;
566
581
  if (variadicIndex !== definition.positionals.length - 1) {
567
- throw new Error("Variadic positional arguments must be the last positional.");
582
+ throw new Error(
583
+ "Variadic positional arguments must be the last positional."
584
+ );
568
585
  }
569
586
  }
570
587
  function validateRequiredNamedArgs(definitions, named) {
@@ -596,11 +613,10 @@ function resolveRouteTree(routes, parentPath = [], parentMiddlewares = []) {
596
613
  kind: "group",
597
614
  path: groupPath
598
615
  });
599
- const nested = resolveRouteTree(
600
- routeValue.routes,
601
- groupPath,
602
- [...parentMiddlewares, ...routeValue.middlewares]
603
- );
616
+ const nested = resolveRouteTree(routeValue.routes, groupPath, [
617
+ ...parentMiddlewares,
618
+ ...routeValue.middlewares
619
+ ]);
604
620
  resolved.commands.push(...nested.commands);
605
621
  resolved.groups.push(...nested.groups);
606
622
  resolved.routeEntries.push(...nested.routeEntries);
@@ -608,7 +624,9 @@ function resolveRouteTree(routes, parentPath = [], parentMiddlewares = []) {
608
624
  }
609
625
  const command2 = routeValue.getDefinition();
610
626
  if (!command2.handler) {
611
- throw new Error(`Command "${[...parentPath, token].join(" ")}" is missing a handler.`);
627
+ throw new Error(
628
+ `Command "${[...parentPath, token].join(" ")}" is missing a handler.`
629
+ );
612
630
  }
613
631
  const path = [...parentPath, token];
614
632
  resolved.commands.push({
@@ -616,7 +634,10 @@ function resolveRouteTree(routes, parentPath = [], parentMiddlewares = []) {
616
634
  path,
617
635
  description: command2.config.description,
618
636
  input: command2.input,
619
- middlewares: mergeInheritedMiddlewares(parentMiddlewares, command2.middlewares),
637
+ middlewares: mergeInheritedMiddlewares(
638
+ parentMiddlewares,
639
+ command2.middlewares
640
+ ),
620
641
  handler: command2.handler
621
642
  });
622
643
  resolved.routeEntries.push({
@@ -630,7 +651,9 @@ function mergeInheritedMiddlewares(parentMiddlewares, commandMiddlewares) {
630
651
  if (parentMiddlewares.length === 0) {
631
652
  return [...commandMiddlewares];
632
653
  }
633
- if (commandMiddlewares.length >= parentMiddlewares.length && parentMiddlewares.every((middleware, index) => commandMiddlewares[index] === middleware)) {
654
+ if (commandMiddlewares.length >= parentMiddlewares.length && parentMiddlewares.every(
655
+ (middleware, index) => commandMiddlewares[index] === middleware
656
+ )) {
634
657
  return [...commandMiddlewares];
635
658
  }
636
659
  return [...parentMiddlewares, ...commandMiddlewares];
@@ -653,10 +676,7 @@ function buildInputNormalizer(definition) {
653
676
  spec.name ? toCamelCase(spec.name) : "",
654
677
  ...(spec.aliases ?? []).flatMap((alias) => {
655
678
  const normalizedAlias = normalizeNamedArgToken(alias);
656
- return [
657
- normalizedAlias,
658
- toCamelCase(normalizedAlias)
659
- ];
679
+ return [normalizedAlias, toCamelCase(normalizedAlias)];
660
680
  }),
661
681
  toKebabCase(key),
662
682
  key
@@ -1,29 +1,22 @@
1
1
  import { aiCommands } from "./commands/ai.js";
2
- import { createBrowserCommands } from "./commands/browser.js";
3
- import { createExecutionCommands } from "./commands/execution.js";
2
+ import { browserCommands } from "./commands/browser.js";
3
+ import { executionCommands } from "./commands/execution.js";
4
4
  import { initCommand } from "./commands/init.js";
5
5
  import { logCommands } from "./commands/logs.js";
6
- import { sessionOption } from "./commands/shared.js";
7
- import { createSnapshotCommand } from "./commands/snapshot.js";
6
+ import { snapshotCommand } from "./commands/snapshot.js";
8
7
  import { SimpleCLI } from "./framework/simple-cli.js";
9
- function buildCLIRoutes(logger) {
10
- return {
11
- ...createBrowserCommands(logger),
12
- ...createExecutionCommands(logger),
13
- ...logCommands,
14
- ai: aiCommands,
15
- init: initCommand,
16
- snapshot: createSnapshotCommand(logger)
17
- };
18
- }
19
- function createCLIApp(logger) {
20
- return SimpleCLI.define("libretto-cli", buildCLIRoutes(logger), {
21
- globalNamed: {
22
- session: sessionOption()
23
- }
24
- });
8
+ const cliRoutes = {
9
+ ...browserCommands,
10
+ ...executionCommands,
11
+ ...logCommands,
12
+ ai: aiCommands,
13
+ init: initCommand,
14
+ snapshot: snapshotCommand
15
+ };
16
+ function createCLIApp() {
17
+ return SimpleCLI.define("libretto", cliRoutes);
25
18
  }
26
19
  export {
27
- buildCLIRoutes,
20
+ cliRoutes,
28
21
  createCLIApp
29
22
  };
@@ -4,6 +4,7 @@ import { cwd } from "node:process";
4
4
  import { isAbsolute, resolve } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
6
  import {
7
+ instrumentContext,
7
8
  launchBrowser
8
9
  } from "../../index.js";
9
10
  import { parseSessionStateContent } from "../../shared/state/index.js";
@@ -13,7 +14,10 @@ import {
13
14
  getSessionNetworkLogPath,
14
15
  getSessionStatePath
15
16
  } from "../core/context.js";
16
- import { getPauseSignalPaths, removeSignalIfExists } from "../core/pause-signals.js";
17
+ import {
18
+ getPauseSignalPaths,
19
+ removeSignalIfExists
20
+ } from "../core/pause-signals.js";
17
21
  import { installSessionTelemetry } from "../core/session-telemetry.js";
18
22
  const LIBRETTO_WORKFLOW_BRAND = /* @__PURE__ */ Symbol.for("libretto.workflow");
19
23
  const FAILURE_HOLD_POLL_INTERVAL_MS = 250;
@@ -40,7 +44,7 @@ function readSessionStatePid(session) {
40
44
  const statePath = getSessionStatePath(session);
41
45
  if (!existsSync(statePath)) return null;
42
46
  try {
43
- return parseSessionStateContent(readFileSync(statePath, "utf8"), statePath).pid;
47
+ return parseSessionStateContent(readFileSync(statePath, "utf8"), statePath).pid ?? null;
44
48
  } catch {
45
49
  return null;
46
50
  }
@@ -66,7 +70,7 @@ async function waitForFailureSessionRelease(args) {
66
70
  function isLoadedLibrettoWorkflow(value) {
67
71
  if (!value || typeof value !== "object") return false;
68
72
  const candidate = value;
69
- return candidate[LIBRETTO_WORKFLOW_BRAND] === true && typeof candidate.run === "function" && !!candidate.metadata && typeof candidate.metadata === "object";
73
+ return candidate[LIBRETTO_WORKFLOW_BRAND] === true && typeof candidate.run === "function";
70
74
  }
71
75
  function resolveLocalAuthProfilePath(domain) {
72
76
  return getProfilePath(normalizeDomain(domain));
@@ -77,9 +81,9 @@ function getMissingLocalAuthProfileError(args) {
77
81
  `Local auth profile not found for domain "${normalizedDomain}".`,
78
82
  `Expected profile file: ${args.profilePath}`,
79
83
  "To create it:",
80
- ` 1. libretto-cli open https://${normalizedDomain} --headed --session ${args.session}`,
84
+ ` 1. libretto open https://${normalizedDomain} --headed --session ${args.session}`,
81
85
  " 2. Log in manually in the browser window.",
82
- ` 3. libretto-cli save ${normalizedDomain} --session ${args.session}`
86
+ ` 3. libretto save ${normalizedDomain} --session ${args.session}`
83
87
  ].join("\n");
84
88
  }
85
89
  function getAbsoluteIntegrationPath(integrationPath) {
@@ -119,8 +123,8 @@ ${TSCONFIG_HINT}` : "";
119
123
  ' import { workflow } from "libretto";',
120
124
  "",
121
125
  ` export const ${exportName} = workflow<InputType, OutputType>(`,
122
- " {},",
123
126
  " async (ctx, input) => {",
127
+ " // ctx.session \u2014 libretto session name",
124
128
  " // ctx.page \u2014 Playwright Page instance",
125
129
  " // ctx.logger \u2014 MinimalLogger",
126
130
  " // ctx.services \u2014 injected dependencies (generic, default {})",
@@ -133,6 +137,12 @@ ${TSCONFIG_HINT}` : "";
133
137
  }
134
138
  return targetExport;
135
139
  }
140
+ async function installHeadedWorkflowVisualization(args) {
141
+ await (args.instrument ?? instrumentContext)(args.context, {
142
+ visualize: true,
143
+ logger: args.logger
144
+ });
145
+ }
136
146
  async function runIntegrationInternal(args, options) {
137
147
  const { logger } = options;
138
148
  const absolutePath = getAbsoluteIntegrationPath(args.integrationPath);
@@ -165,8 +175,15 @@ async function runIntegrationInternal(args, options) {
165
175
  const browserSession = await launchBrowser({
166
176
  sessionName: args.session,
167
177
  headless: args.headless,
168
- storageStatePath
178
+ storageStatePath,
179
+ viewport: args.viewport
169
180
  });
181
+ if (!args.headless && args.visualize !== false) {
182
+ await installHeadedWorkflowVisualization({
183
+ context: browserSession.context,
184
+ logger: integrationLogger
185
+ });
186
+ }
170
187
  const actionsLogPath = getSessionActionsLogPath(args.session);
171
188
  const networkLogPath = getSessionNetworkLogPath(args.session);
172
189
  await installSessionTelemetry({
@@ -181,6 +198,7 @@ async function runIntegrationInternal(args, options) {
181
198
  }
182
199
  });
183
200
  const workflowContext = {
201
+ session: args.session,
184
202
  logger: integrationLogger,
185
203
  page: browserSession.page,
186
204
  services: {}
@@ -228,5 +246,6 @@ async function runIntegrationFromFileInWorker(args, logger) {
228
246
  });
229
247
  }
230
248
  export {
249
+ installHeadedWorkflowVisualization,
231
250
  runIntegrationFromFileInWorker
232
251
  };
@@ -5,7 +5,9 @@ const RunIntegrationWorkerRequestSchema = z.object({
5
5
  session: z.string().min(1),
6
6
  params: z.unknown(),
7
7
  headless: z.boolean(),
8
- authProfileDomain: z.string().optional()
8
+ visualize: z.boolean().default(true),
9
+ authProfileDomain: z.string().optional(),
10
+ viewport: z.object({ width: z.number(), height: z.number() }).optional()
9
11
  });
10
12
  export {
11
13
  RunIntegrationWorkerRequestSchema
@@ -4,10 +4,7 @@ import {
4
4
  RunIntegrationWorkerRequestSchema
5
5
  } from "./run-integration-worker-protocol.js";
6
6
  import { runIntegrationFromFileInWorker } from "./run-integration-runtime.js";
7
- import {
8
- ensureLibrettoSetup,
9
- withSessionLogger
10
- } from "../core/context.js";
7
+ import { ensureLibrettoSetup, withSessionLogger } from "../core/context.js";
11
8
  import { getPauseSignalPaths } from "../core/pause-signals.js";
12
9
  function parseWorkerRequest(argv) {
13
10
  const rawPayload = argv[2];
package/dist/index.d.ts CHANGED
@@ -10,12 +10,11 @@ export { ExtractOptions, extractFromPage } from './runtime/extract/extract.js';
10
10
  export { PageRequestOptions, RequestConfig, pageRequest } from './runtime/network/network.js';
11
11
  export { DownloadResult, DownloadViaClickOptions, SaveDownloadOptions, downloadAndSave, downloadViaClick } from './runtime/download/download.js';
12
12
  export { pause } from './shared/debug/pause.js';
13
- export { isDebugMode, isDryRun, shouldPauseBeforeMutation } from './shared/config/config.js';
14
13
  export { InstrumentationOptions, InstrumentedPage, installInstrumentation, instrumentContext, instrumentPage } from './shared/instrumentation/instrument.js';
15
14
  export { GhostCursorOptions, ensureGhostCursor, ghostClick, hideGhostCursor, moveGhostCursor } from './shared/visualization/ghost-cursor.js';
16
15
  export { HighlightOptions, clearHighlights, ensureHighlightLayer, showHighlight } from './shared/visualization/highlight.js';
17
16
  export { BrowserSession, LaunchBrowserArgs, launchBrowser } from './shared/run/browser.js';
18
- export { LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, LibrettoWorkflowContext, LibrettoWorkflowHandler, LibrettoWorkflowMetadata, workflow } from './shared/workflow/workflow.js';
17
+ export { LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, LibrettoWorkflowContext, LibrettoWorkflowHandler, workflow } from './shared/workflow/workflow.js';
19
18
  import 'zod';
20
19
  import 'ai';
21
20
  import 'playwright';
package/dist/index.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { resolve } from "node:path";
2
2
  import { pathToFileURL } from "node:url";
3
- import { Logger, defaultLogger } from "./shared/logger/logger.js";
3
+ import {
4
+ Logger,
5
+ defaultLogger
6
+ } from "./shared/logger/logger.js";
4
7
  import {
5
8
  createFileLogSink,
6
9
  prettyConsoleSink,
@@ -20,7 +23,9 @@ import { attemptWithRecovery } from "./runtime/recovery/recovery.js";
20
23
  import {
21
24
  detectSubmissionError
22
25
  } from "./runtime/recovery/errors.js";
23
- import { extractFromPage } from "./runtime/extract/extract.js";
26
+ import {
27
+ extractFromPage
28
+ } from "./runtime/extract/extract.js";
24
29
  import {
25
30
  pageRequest
26
31
  } from "./runtime/network/network.js";
@@ -29,11 +34,6 @@ import {
29
34
  downloadAndSave
30
35
  } from "./runtime/download/download.js";
31
36
  import { pause } from "./shared/debug/pause.js";
32
- import {
33
- isDebugMode,
34
- isDryRun,
35
- shouldPauseBeforeMutation
36
- } from "./shared/config/config.js";
37
37
  import {
38
38
  instrumentPage,
39
39
  installInstrumentation,
@@ -97,8 +97,6 @@ export {
97
97
  installInstrumentation,
98
98
  instrumentContext,
99
99
  instrumentPage,
100
- isDebugMode,
101
- isDryRun,
102
100
  jsonlConsoleSink,
103
101
  launchBrowser,
104
102
  moveGhostCursor,
@@ -108,7 +106,6 @@ export {
108
106
  pause,
109
107
  prettyConsoleSink,
110
108
  serializeSessionState,
111
- shouldPauseBeforeMutation,
112
109
  showHighlight,
113
110
  workflow
114
111
  };
@@ -29,7 +29,11 @@ async function downloadViaClick(page, selector, options) {
29
29
  }
30
30
  async function downloadAndSave(page, selector, options) {
31
31
  const { savePath, ...downloadOpts } = options ?? {};
32
- const { buffer, filename } = await downloadViaClick(page, selector, downloadOpts);
32
+ const { buffer, filename } = await downloadViaClick(
33
+ page,
34
+ selector,
35
+ downloadOpts
36
+ );
33
37
  const dest = resolve(savePath ?? filename);
34
38
  await writeFile(dest, buffer);
35
39
  options?.logger?.info("download:saved", {
@@ -1,6 +1,15 @@
1
- import { defaultLogger } from "../../shared/logger/logger.js";
1
+ import {
2
+ defaultLogger
3
+ } from "../../shared/logger/logger.js";
2
4
  async function extractFromPage(options) {
3
- const { page, instruction, schema, selector, logger = defaultLogger, llmClient } = options;
5
+ const {
6
+ page,
7
+ instruction,
8
+ schema,
9
+ selector,
10
+ logger = defaultLogger,
11
+ llmClient
12
+ } = options;
4
13
  let screenshot;
5
14
  let domContent;
6
15
  if (selector) {
@@ -1,5 +1,12 @@
1
1
  async function pageRequest(page, config, options) {
2
- const { url, method = "GET", headers = {}, body, bodyType = "json", responseType = "json" } = config;
2
+ const {
3
+ url,
4
+ method = "GET",
5
+ headers = {},
6
+ body,
7
+ bodyType = "json",
8
+ responseType = "json"
9
+ } = config;
3
10
  const { logger, schema } = options ?? {};
4
11
  const startTime = Date.now();
5
12
  const fetchHeaders = { ...headers };
@@ -1,4 +1,6 @@
1
- import { defaultLogger } from "../../shared/logger/logger.js";
1
+ import {
2
+ defaultLogger
3
+ } from "../../shared/logger/logger.js";
2
4
  function delay(ms) {
3
5
  return new Promise((resolve) => setTimeout(resolve, ms));
4
6
  }
@@ -86,7 +88,9 @@ async function executeBrowserAction(page, action, logger = defaultLogger) {
86
88
  if (point) await page.mouse.move(point.x, point.y);
87
89
  }
88
90
  await page.mouse.up();
89
- logger.info(`Dragged from (${start.x}, ${start.y}) to (${end.x}, ${end.y})`);
91
+ logger.info(
92
+ `Dragged from (${start.x}, ${start.y}) to (${end.x}, ${end.y})`
93
+ );
90
94
  }
91
95
  break;
92
96
  }
@@ -1,4 +1,6 @@
1
- import { defaultLogger } from "../../shared/logger/logger.js";
1
+ import {
2
+ defaultLogger
3
+ } from "../../shared/logger/logger.js";
2
4
  import { z } from "zod";
3
5
  const detectSubmissionErrorSchema = z.object({
4
6
  hasError: z.boolean().describe("Whether an error is visible on the page"),
@@ -1,4 +1,6 @@
1
- import { defaultLogger } from "../../shared/logger/logger.js";
1
+ import {
2
+ defaultLogger
3
+ } from "../../shared/logger/logger.js";
2
4
  import { executeRecoveryAgent } from "./agent.js";
3
5
  async function attemptWithRecovery(page, fn, logger, llmClient) {
4
6
  const log = logger ?? defaultLogger;
@@ -28,12 +28,7 @@ const STATE_ATTRS = /* @__PURE__ */ new Set([
28
28
  "open",
29
29
  "multiple"
30
30
  ]);
31
- const BOOLEAN_ATTRS = /* @__PURE__ */ new Set([
32
- ...STATE_ATTRS,
33
- "async",
34
- "defer",
35
- "nomodule"
36
- ]);
31
+ const BOOLEAN_ATTRS = /* @__PURE__ */ new Set([...STATE_ATTRS, "async", "defer", "nomodule"]);
37
32
  const EMPTY_VALUE_DROP_ATTRS = /* @__PURE__ */ new Set([
38
33
  "alt",
39
34
  "autocomplete",
@@ -170,12 +165,8 @@ function condenseDom(html) {
170
165
  }
171
166
  const hasAriaLabel = /aria-label\s*=/i.test(attrs);
172
167
  if (!hasAriaLabel) {
173
- const titleMatch = inner.match(
174
- /<title[^>]*>([^<]+)<\/title>/i
175
- );
176
- const descMatch = inner.match(
177
- /<desc[^>]*>([^<]+)<\/desc>/i
178
- );
168
+ const titleMatch = inner.match(/<title[^>]*>([^<]+)<\/title>/i);
169
+ const descMatch = inner.match(/<desc[^>]*>([^<]+)<\/desc>/i);
179
170
  const labelText = titleMatch?.[1]?.trim() || descMatch?.[1]?.trim();
180
171
  if (labelText) {
181
172
  keepAttrs.push(
@@ -404,7 +395,9 @@ function shouldKeepCustomDataAttribute(tagName, attrName, value, interactive) {
404
395
  function looksMeaningfulToken(value) {
405
396
  if (!/^[a-z][a-z0-9-]{1,40}$/i.test(value)) return false;
406
397
  if (!/[a-z]{3}/i.test(value)) return false;
407
- if (/(track|metric|telemetry|analytics|component|display|loaded|token|dps|color|screen|strict|rehydr|fetch)/i.test(value)) {
398
+ if (/(track|metric|telemetry|analytics|component|display|loaded|token|dps|color|screen|strict|rehydr|fetch)/i.test(
399
+ value
400
+ )) {
408
401
  return false;
409
402
  }
410
403
  return true;