libretto 0.5.0 → 0.5.2

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 (122) hide show
  1. package/README.md +109 -35
  2. package/dist/cli/cli.js +22 -97
  3. package/dist/cli/commands/browser.js +86 -59
  4. package/dist/cli/commands/execution.js +199 -86
  5. package/dist/cli/commands/init.js +34 -29
  6. package/dist/cli/commands/logs.js +4 -5
  7. package/dist/cli/commands/shared.js +30 -29
  8. package/dist/cli/commands/snapshot.js +26 -39
  9. package/dist/cli/core/ai-config.js +21 -4
  10. package/dist/cli/core/api-snapshot-analyzer.js +15 -5
  11. package/dist/cli/core/browser.js +207 -37
  12. package/dist/cli/core/context.js +4 -1
  13. package/dist/cli/core/session-telemetry.js +434 -174
  14. package/dist/cli/core/session.js +21 -8
  15. package/dist/cli/core/snapshot-analyzer.js +14 -31
  16. package/dist/cli/core/snapshot-api-config.js +2 -6
  17. package/dist/cli/core/telemetry.js +20 -4
  18. package/dist/cli/framework/simple-cli.js +45 -25
  19. package/dist/cli/router.js +14 -21
  20. package/dist/cli/workers/run-integration-runtime.js +24 -5
  21. package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
  22. package/dist/cli/workers/run-integration-worker.js +1 -4
  23. package/dist/index.d.ts +1 -2
  24. package/dist/index.js +7 -10
  25. package/dist/runtime/download/download.js +5 -1
  26. package/dist/runtime/extract/extract.js +11 -2
  27. package/dist/runtime/network/network.js +8 -1
  28. package/dist/runtime/recovery/agent.js +6 -2
  29. package/dist/runtime/recovery/errors.js +3 -1
  30. package/dist/runtime/recovery/recovery.js +3 -1
  31. package/dist/shared/condense-dom/condense-dom.js +17 -69
  32. package/dist/shared/config/config.d.ts +1 -9
  33. package/dist/shared/config/config.js +0 -18
  34. package/dist/shared/config/index.d.ts +2 -1
  35. package/dist/shared/config/index.js +0 -10
  36. package/dist/shared/debug/pause.js +9 -3
  37. package/dist/shared/dom-semantics.d.ts +8 -0
  38. package/dist/shared/dom-semantics.js +69 -0
  39. package/dist/shared/instrumentation/instrument.js +101 -5
  40. package/dist/shared/llm/ai-sdk-adapter.js +3 -1
  41. package/dist/shared/llm/client.js +3 -1
  42. package/dist/shared/logger/index.js +4 -1
  43. package/dist/shared/run/api.js +3 -1
  44. package/dist/shared/run/browser.js +47 -3
  45. package/dist/shared/state/session-state.d.ts +2 -1
  46. package/dist/shared/state/session-state.js +5 -2
  47. package/dist/shared/visualization/ghost-cursor.js +36 -14
  48. package/dist/shared/visualization/highlight.js +9 -6
  49. package/dist/shared/workflow/workflow.d.ts +4 -5
  50. package/dist/shared/workflow/workflow.js +3 -5
  51. package/package.json +6 -2
  52. package/scripts/check-skills-sync.mjs +25 -0
  53. package/scripts/compare-eval-summary.mjs +47 -0
  54. package/scripts/postinstall.mjs +15 -15
  55. package/scripts/prepare-release.sh +97 -0
  56. package/scripts/skills-libretto.mjs +103 -0
  57. package/scripts/summarize-evals.mjs +135 -0
  58. package/scripts/sync-skills.mjs +12 -0
  59. package/skills/libretto/SKILL.md +132 -54
  60. package/skills/libretto/references/action-logs.md +101 -0
  61. package/skills/libretto/references/auth-profiles.md +1 -2
  62. package/skills/libretto/references/code-generation-rules.md +210 -0
  63. package/skills/libretto/references/configuration-file-reference.md +53 -0
  64. package/skills/libretto/references/pages-and-page-targeting.md +1 -1
  65. package/skills/libretto/references/site-security-review.md +143 -0
  66. package/src/cli/cli.ts +23 -110
  67. package/src/cli/commands/browser.ts +94 -70
  68. package/src/cli/commands/execution.ts +233 -102
  69. package/src/cli/commands/init.ts +37 -33
  70. package/src/cli/commands/logs.ts +7 -7
  71. package/src/cli/commands/shared.ts +36 -37
  72. package/src/cli/commands/snapshot.ts +44 -59
  73. package/src/cli/core/ai-config.ts +24 -4
  74. package/src/cli/core/api-snapshot-analyzer.ts +17 -6
  75. package/src/cli/core/browser.ts +260 -49
  76. package/src/cli/core/context.ts +7 -2
  77. package/src/cli/core/session-telemetry.ts +449 -197
  78. package/src/cli/core/session.ts +21 -7
  79. package/src/cli/core/snapshot-analyzer.ts +26 -46
  80. package/src/cli/core/snapshot-api-config.ts +170 -175
  81. package/src/cli/core/telemetry.ts +39 -4
  82. package/src/cli/framework/simple-cli.ts +144 -77
  83. package/src/cli/router.ts +13 -21
  84. package/src/cli/workers/run-integration-runtime.ts +36 -9
  85. package/src/cli/workers/run-integration-worker-protocol.ts +2 -0
  86. package/src/cli/workers/run-integration-worker.ts +1 -4
  87. package/src/index.ts +73 -66
  88. package/src/runtime/download/download.ts +62 -58
  89. package/src/runtime/download/index.ts +5 -5
  90. package/src/runtime/extract/extract.ts +71 -61
  91. package/src/runtime/network/index.ts +3 -3
  92. package/src/runtime/network/network.ts +99 -93
  93. package/src/runtime/recovery/agent.ts +217 -212
  94. package/src/runtime/recovery/errors.ts +107 -104
  95. package/src/runtime/recovery/index.ts +3 -3
  96. package/src/runtime/recovery/recovery.ts +38 -35
  97. package/src/shared/condense-dom/condense-dom.ts +27 -82
  98. package/src/shared/config/config.ts +0 -19
  99. package/src/shared/config/index.ts +0 -5
  100. package/src/shared/debug/pause.ts +57 -51
  101. package/src/shared/dom-semantics.ts +68 -0
  102. package/src/shared/instrumentation/errors.ts +64 -62
  103. package/src/shared/instrumentation/index.ts +5 -5
  104. package/src/shared/instrumentation/instrument.ts +339 -209
  105. package/src/shared/llm/ai-sdk-adapter.ts +58 -55
  106. package/src/shared/llm/client.ts +181 -174
  107. package/src/shared/llm/types.ts +39 -39
  108. package/src/shared/logger/index.ts +11 -4
  109. package/src/shared/logger/logger.ts +312 -306
  110. package/src/shared/logger/sinks.ts +118 -114
  111. package/src/shared/paths/paths.ts +50 -49
  112. package/src/shared/paths/repo-root.ts +17 -17
  113. package/src/shared/run/api.ts +5 -1
  114. package/src/shared/run/browser.ts +65 -3
  115. package/src/shared/state/index.ts +9 -9
  116. package/src/shared/state/session-state.ts +46 -43
  117. package/src/shared/visualization/ghost-cursor.ts +180 -149
  118. package/src/shared/visualization/highlight.ts +89 -86
  119. package/src/shared/visualization/index.ts +13 -13
  120. package/src/shared/workflow/workflow.ts +19 -25
  121. package/skills/libretto/references/reverse-engineering-network-requests.md +0 -39
  122. package/skills/libretto/references/user-action-log.md +0 -31
@@ -1,26 +1,15 @@
1
1
  import { z } from "zod";
2
+ import { createLoggerForSession } from "../core/context.js";
2
3
  import {
3
- SESSION_DEFAULT,
4
+ generateSessionName,
4
5
  readSessionStateOrThrow,
5
6
  validateSessionName
6
7
  } from "../core/session.js";
7
8
  import {
8
9
  SimpleCLI
9
10
  } from "../framework/simple-cli.js";
10
- function createSessionSchema() {
11
- return z.string().default(SESSION_DEFAULT).superRefine((value, ctx) => {
12
- try {
13
- validateSessionName(value);
14
- } catch (err) {
15
- ctx.addIssue({
16
- code: z.ZodIssueCode.custom,
17
- message: err instanceof Error ? err.message : String(err)
18
- });
19
- }
20
- });
21
- }
22
- function sessionOption(help = "Use a named session") {
23
- return SimpleCLI.option(createSessionSchema(), { help });
11
+ function sessionOption(help = "Session name") {
12
+ return SimpleCLI.option(z.string().optional(), { help });
24
13
  }
25
14
  function pageOption(help = "Target a specific page id") {
26
15
  return SimpleCLI.option(z.string().optional(), { help });
@@ -28,23 +17,35 @@ function pageOption(help = "Target a specific page id") {
28
17
  function integerOption(help) {
29
18
  return SimpleCLI.option(z.coerce.number().int().optional(), { help });
30
19
  }
31
- const resolveSessionMiddleware = async ({ input, ctx }) => {
32
- return {
33
- ...ctx,
34
- session: input.session
20
+ function withRequiredSession() {
21
+ return async ({ input, ctx }) => {
22
+ if (!input.session) {
23
+ throw new Error("Missing required option --session.");
24
+ }
25
+ validateSessionName(input.session);
26
+ const logger = createLoggerForSession(input.session);
27
+ return {
28
+ ...ctx,
29
+ session: input.session,
30
+ logger,
31
+ sessionState: readSessionStateOrThrow(input.session)
32
+ };
35
33
  };
36
- };
37
- const loadSessionStateMiddleware = async ({ ctx }) => {
38
- return {
39
- ...ctx,
40
- sessionState: readSessionStateOrThrow(ctx.session)
34
+ }
35
+ function withAutoSession() {
36
+ return async ({ input, ctx }) => {
37
+ const session = input.session ?? generateSessionName();
38
+ if (input.session) {
39
+ validateSessionName(input.session);
40
+ }
41
+ const logger = createLoggerForSession(session);
42
+ return { ...ctx, session, logger };
41
43
  };
42
- };
44
+ }
43
45
  export {
44
- createSessionSchema,
45
46
  integerOption,
46
- loadSessionStateMiddleware,
47
47
  pageOption,
48
- resolveSessionMiddleware,
49
- sessionOption
48
+ sessionOption,
49
+ withAutoSession,
50
+ withRequiredSession
50
51
  };
@@ -5,16 +5,10 @@ import { getSessionSnapshotRunDir } from "../core/context.js";
5
5
  import { condenseDom } from "../../shared/condense-dom/condense-dom.js";
6
6
  import { readSessionState } from "../core/session.js";
7
7
  import { SimpleCLI } from "../framework/simple-cli.js";
8
- import {
9
- loadSessionStateMiddleware,
10
- pageOption,
11
- resolveSessionMiddleware,
12
- sessionOption
13
- } from "./shared.js";
8
+ import { pageOption, sessionOption, withRequiredSession } from "./shared.js";
14
9
  import { runApiInterpret } from "../core/api-snapshot-analyzer.js";
15
10
  import { readAiConfig } from "../core/ai-config.js";
16
11
  import { resolveSnapshotApiModelOrThrow } from "../core/snapshot-api-config.js";
17
- const DEFAULT_SNAPSHOT_CONTEXT = "No additional user context provided.";
18
12
  const FALLBACK_SNAPSHOT_VIEWPORT = { width: 1280, height: 800 };
19
13
  function generateSnapshotRunId() {
20
14
  return `snapshot-${Date.now()}`;
@@ -106,6 +100,12 @@ async function captureScreenshot(session, logger, pageId) {
106
100
  const pngPath = `${snapshotRunDir}/page.png`;
107
101
  const htmlPath = `${snapshotRunDir}/page.html`;
108
102
  const condensedHtmlPath = `${snapshotRunDir}/page.condensed.html`;
103
+ const RENDER_SETTLE_TIMEOUT_MS = 1e4;
104
+ await Promise.race([
105
+ page.waitForLoadState("networkidle").catch(() => {
106
+ }),
107
+ new Promise((resolve) => setTimeout(resolve, RENDER_SETTLE_TIMEOUT_MS))
108
+ ]);
109
109
  const restoreViewport = resolveSnapshotViewport(session, logger);
110
110
  const viewportMetrics = await readSnapshotViewportMetrics(page);
111
111
  logger.info("screenshot-viewport-metrics", {
@@ -185,17 +185,10 @@ async function captureScreenshot(session, logger, pageId) {
185
185
  }
186
186
  }
187
187
  async function runSnapshot(session, logger, pageId, objective, context) {
188
- const normalizedObjective = objective?.trim();
189
- const normalizedContext = context?.trim();
190
- if (!normalizedObjective && normalizedContext) {
191
- throw new Error(
192
- "Couldn't run analysis: --objective is required when providing --context."
193
- );
194
- }
195
- const configuredAi = normalizedObjective ? readAiConfig() : null;
196
- if (normalizedObjective) {
197
- resolveSnapshotApiModelOrThrow(configuredAi);
198
- }
188
+ const normalizedObjective = objective.trim();
189
+ const normalizedContext = context.trim();
190
+ const configuredAi = readAiConfig();
191
+ resolveSnapshotApiModelOrThrow(configuredAi);
199
192
  const { pngPath, htmlPath, condensedHtmlPath } = await captureScreenshot(
200
193
  session,
201
194
  logger,
@@ -205,14 +198,10 @@ async function runSnapshot(session, logger, pageId, objective, context) {
205
198
  console.log(` PNG: ${pngPath}`);
206
199
  console.log(` HTML: ${htmlPath}`);
207
200
  console.log(` Condensed HTML: ${condensedHtmlPath}`);
208
- if (!normalizedObjective) {
209
- console.log("Use --objective flag to analyze snapshots.");
210
- return;
211
- }
212
201
  const interpretArgs = {
213
202
  objective: normalizedObjective,
214
203
  session,
215
- context: normalizedContext ?? DEFAULT_SNAPSHOT_CONTEXT,
204
+ context: normalizedContext,
216
205
  pngPath,
217
206
  htmlPath,
218
207
  condensedHtmlPath
@@ -224,24 +213,22 @@ const snapshotInput = SimpleCLI.input({
224
213
  named: {
225
214
  session: sessionOption(),
226
215
  page: pageOption(),
227
- objective: SimpleCLI.option(z.string().optional()),
228
- context: SimpleCLI.option(z.string().optional())
216
+ objective: SimpleCLI.option(z.string()),
217
+ context: SimpleCLI.option(z.string())
229
218
  }
230
219
  });
231
- function createSnapshotCommand(logger) {
232
- return SimpleCLI.command({
233
- description: "Capture PNG + HTML; analyze when --objective is provided (--context optional)"
234
- }).input(snapshotInput).use(resolveSessionMiddleware).use(loadSessionStateMiddleware).handle(async ({ input, ctx }) => {
235
- await runSnapshot(
236
- ctx.session,
237
- logger,
238
- input.page,
239
- input.objective,
240
- input.context
241
- );
242
- });
243
- }
220
+ const snapshotCommand = SimpleCLI.command({
221
+ description: "Capture PNG + HTML and analyze with --objective and --context"
222
+ }).input(snapshotInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
223
+ await runSnapshot(
224
+ ctx.session,
225
+ ctx.logger,
226
+ input.page,
227
+ input.objective,
228
+ input.context
229
+ );
230
+ });
244
231
  export {
245
- createSnapshotCommand,
232
+ snapshotCommand,
246
233
  snapshotInput
247
234
  };
@@ -11,10 +11,15 @@ const ViewportConfigSchema = z.object({
11
11
  width: z.number().int().min(1),
12
12
  height: z.number().int().min(1)
13
13
  });
14
+ const WindowPositionConfigSchema = z.object({
15
+ x: z.number().int(),
16
+ y: z.number().int()
17
+ });
14
18
  const LibrettoConfigSchema = z.object({
15
19
  version: z.literal(CURRENT_CONFIG_VERSION),
16
20
  ai: AiConfigSchema.optional(),
17
- viewport: ViewportConfigSchema.optional()
21
+ viewport: ViewportConfigSchema.optional(),
22
+ windowPosition: WindowPositionConfigSchema.optional()
18
23
  }).passthrough();
19
24
  const DEFAULT_MODELS = {
20
25
  openai: "openai/gpt-5.4",
@@ -26,7 +31,12 @@ const PROVIDER_ALIASES = {
26
31
  claude: DEFAULT_MODELS.anthropic,
27
32
  google: DEFAULT_MODELS.gemini
28
33
  };
29
- const CONFIGURE_PROVIDERS = ["openai", "anthropic", "gemini", "vertex"];
34
+ const CONFIGURE_PROVIDERS = [
35
+ "openai",
36
+ "anthropic",
37
+ "gemini",
38
+ "vertex"
39
+ ];
30
40
  function formatConfigureProviders(separator = " | ") {
31
41
  return CONFIGURE_PROVIDERS.join(separator);
32
42
  }
@@ -44,6 +54,10 @@ function formatExpectedConfigExample() {
44
54
  viewport: {
45
55
  width: 1280,
46
56
  height: 800
57
+ },
58
+ windowPosition: {
59
+ x: 1600,
60
+ y: 120
47
61
  }
48
62
  },
49
63
  null,
@@ -59,7 +73,7 @@ ${detail}` : null,
59
73
  "Expected config example:",
60
74
  formatExpectedConfigExample(),
61
75
  "Notes:",
62
- ' - "ai" and "viewport" are optional.',
76
+ ' - "ai", "viewport", and "windowPosition" are optional.',
63
77
  ' - "ai.model" must be a provider/model string like "openai/gpt-5.4" or "anthropic/claude-sonnet-4-6".',
64
78
  "Fix the file to match this shape, or delete it and rerun:",
65
79
  ` npx libretto ai configure ${formatConfigureProviders()}`
@@ -147,7 +161,9 @@ function runAiConfigure(input, options = {}) {
147
161
  console.log(
148
162
  `No AI config set. Choose a default model: ${configureCommandName} ${formatConfigureProviders()}`
149
163
  );
150
- console.log("Provider credentials still come from your shell or .env file.");
164
+ console.log(
165
+ "Provider credentials still come from your shell or .env file."
166
+ );
151
167
  return;
152
168
  }
153
169
  printAiConfig(config2, configPath);
@@ -182,6 +198,7 @@ export {
182
198
  CURRENT_CONFIG_VERSION,
183
199
  LibrettoConfigSchema,
184
200
  ViewportConfigSchema,
201
+ WindowPositionConfigSchema,
185
202
  clearAiConfig,
186
203
  readAiConfig,
187
204
  readLibrettoConfig,
@@ -1,16 +1,13 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { createLLMClient } from "../../shared/llm/client.js";
3
3
  import {
4
- formatInterpretationOutput,
5
4
  InterpretResultSchema,
6
5
  buildInlinePromptSelection,
7
6
  getMimeType,
8
7
  readFileAsBase64
9
8
  } from "./snapshot-analyzer.js";
10
9
  import { readAiConfig } from "./ai-config.js";
11
- import {
12
- resolveSnapshotApiModelOrThrow
13
- } from "./snapshot-api-config.js";
10
+ import { resolveSnapshotApiModelOrThrow } from "./snapshot-api-config.js";
14
11
  async function runApiInterpret(args, logger, configuredAi = readAiConfig()) {
15
12
  const selection = resolveSnapshotApiModelOrThrow(configuredAi);
16
13
  logger.info("api-interpret-start", {
@@ -67,7 +64,20 @@ async function runApiInterpret(args, logger, configuredAi = readAiConfig()) {
67
64
  selectorCount: parsed.selectors.length,
68
65
  answer: parsed.answer.slice(0, 200)
69
66
  });
70
- console.log(formatInterpretationOutput(parsed, "Interpretation (via API):"));
67
+ console.log("");
68
+ console.log("Analysis:");
69
+ console.log(parsed.answer);
70
+ if (parsed.selectors.length > 0) {
71
+ console.log("");
72
+ console.log("Selectors:");
73
+ parsed.selectors.forEach((selector, index) => {
74
+ console.log(` ${index + 1}. ${selector.label}: ${selector.selector}`);
75
+ });
76
+ }
77
+ if (parsed.notes?.trim()) {
78
+ console.log("");
79
+ console.log(`Notes: ${parsed.notes.trim()}`);
80
+ }
71
81
  }
72
82
  export {
73
83
  runApiInterpret