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
@@ -12,13 +12,15 @@ import {
12
12
  import { SimpleCLI } from "../framework/simple-cli.js";
13
13
  import {
14
14
  integerOption,
15
- loadSessionStateMiddleware,
16
15
  pageOption,
17
- resolveSessionMiddleware,
18
16
  sessionOption,
17
+ withRequiredSession,
19
18
  } from "./shared.js";
20
19
 
21
- async function resolvePageId(session: string, pageId?: string): Promise<string | undefined> {
20
+ async function resolvePageId(
21
+ session: string,
22
+ pageId?: string,
23
+ ): Promise<string | undefined> {
22
24
  if (!pageId) return undefined;
23
25
  const pages = await withSessionLogger(session, async (logger) =>
24
26
  listOpenPages(session, logger),
@@ -48,8 +50,7 @@ export const networkCommand = SimpleCLI.command({
48
50
  description: "View captured network requests",
49
51
  })
50
52
  .input(networkInput)
51
- .use(resolveSessionMiddleware)
52
- .use(loadSessionStateMiddleware)
53
+ .use(withRequiredSession())
53
54
  .handle(async ({ input, ctx }) => {
54
55
  if (input.clear) {
55
56
  clearNetworkLog(ctx.session);
@@ -93,8 +94,7 @@ export const actionsCommand = SimpleCLI.command({
93
94
  description: "View captured actions",
94
95
  })
95
96
  .input(actionsInput)
96
- .use(resolveSessionMiddleware)
97
- .use(loadSessionStateMiddleware)
97
+ .use(withRequiredSession())
98
98
  .handle(async ({ input, ctx }) => {
99
99
  if (input.clear) {
100
100
  clearActionLog(ctx.session);
@@ -1,6 +1,8 @@
1
1
  import { z } from "zod";
2
+ import type { LoggerApi } from "../../shared/logger/index.js";
3
+ import { createLoggerForSession } from "../core/context.js";
2
4
  import {
3
- SESSION_DEFAULT,
5
+ generateSessionName,
4
6
  readSessionStateOrThrow,
5
7
  type SessionState,
6
8
  validateSessionName,
@@ -10,21 +12,8 @@ import {
10
12
  type SimpleCLIMiddleware,
11
13
  } from "../framework/simple-cli.js";
12
14
 
13
- export function createSessionSchema() {
14
- return z.string().default(SESSION_DEFAULT).superRefine((value, ctx) => {
15
- try {
16
- validateSessionName(value);
17
- } catch (err) {
18
- ctx.addIssue({
19
- code: z.ZodIssueCode.custom,
20
- message: err instanceof Error ? err.message : String(err),
21
- });
22
- }
23
- });
24
- }
25
-
26
- export function sessionOption(help = "Use a named session") {
27
- return SimpleCLI.option(createSessionSchema(), { help });
15
+ export function sessionOption(help = "Session name") {
16
+ return SimpleCLI.option(z.string().optional(), { help });
28
17
  }
29
18
 
30
19
  export function pageOption(help = "Target a specific page id") {
@@ -35,36 +24,46 @@ export function integerOption(help?: string) {
35
24
  return SimpleCLI.option(z.coerce.number().int().optional(), { help });
36
25
  }
37
26
 
38
- export type SessionInput = {
39
- session: string;
40
- };
41
-
42
27
  export type SessionContext = {
43
28
  session: string;
29
+ logger: LoggerApi;
44
30
  };
45
31
 
46
32
  export type SessionStateContext = SessionContext & {
47
33
  sessionState: SessionState;
48
34
  };
49
35
 
50
- export const resolveSessionMiddleware: SimpleCLIMiddleware<
51
- SessionInput,
36
+ export function withRequiredSession(): SimpleCLIMiddleware<
37
+ { session?: string },
52
38
  {},
53
- SessionContext
54
- > = async ({ input, ctx }) => {
55
- return {
56
- ...ctx,
57
- session: input.session,
39
+ SessionStateContext
40
+ > {
41
+ return async ({ input, ctx }) => {
42
+ if (!input.session) {
43
+ throw new Error("Missing required option --session.");
44
+ }
45
+ validateSessionName(input.session);
46
+ const logger = createLoggerForSession(input.session);
47
+ return {
48
+ ...ctx,
49
+ session: input.session,
50
+ logger,
51
+ sessionState: readSessionStateOrThrow(input.session),
52
+ };
58
53
  };
59
- };
54
+ }
60
55
 
61
- export const loadSessionStateMiddleware: SimpleCLIMiddleware<
62
- SessionInput,
63
- SessionContext,
64
- SessionStateContext
65
- > = async ({ ctx }) => {
66
- return {
67
- ...ctx,
68
- sessionState: readSessionStateOrThrow(ctx.session),
56
+ export function withAutoSession(): SimpleCLIMiddleware<
57
+ { session?: string },
58
+ {},
59
+ SessionContext
60
+ > {
61
+ return async ({ input, ctx }) => {
62
+ const session = input.session ?? generateSessionName();
63
+ if (input.session) {
64
+ validateSessionName(input.session);
65
+ }
66
+ const logger = createLoggerForSession(session);
67
+ return { ...ctx, session, logger };
69
68
  };
70
- };
69
+ }
@@ -10,17 +10,11 @@ import {
10
10
  type ScreenshotPair,
11
11
  } from "../core/snapshot-analyzer.js";
12
12
  import { SimpleCLI } from "../framework/simple-cli.js";
13
- import {
14
- loadSessionStateMiddleware,
15
- pageOption,
16
- resolveSessionMiddleware,
17
- sessionOption,
18
- } from "./shared.js";
13
+ import { pageOption, sessionOption, withRequiredSession } from "./shared.js";
19
14
  import { runApiInterpret } from "../core/api-snapshot-analyzer.js";
20
15
  import { readAiConfig } from "../core/ai-config.js";
21
16
  import { resolveSnapshotApiModelOrThrow } from "../core/snapshot-api-config.js";
22
17
 
23
- const DEFAULT_SNAPSHOT_CONTEXT = "No additional user context provided.";
24
18
  const FALLBACK_SNAPSHOT_VIEWPORT = { width: 1280, height: 800 } as const;
25
19
 
26
20
  function generateSnapshotRunId(): string {
@@ -38,28 +32,28 @@ function isZeroViewport(value: number | null): boolean {
38
32
  return typeof value === "number" && value <= 0;
39
33
  }
40
34
 
41
- function shouldForceSnapshotViewport(metrics: SnapshotViewportMetrics): boolean {
35
+ function shouldForceSnapshotViewport(
36
+ metrics: SnapshotViewportMetrics,
37
+ ): boolean {
42
38
  return (
43
- isZeroViewport(metrics.configuredWidth)
44
- || isZeroViewport(metrics.configuredHeight)
45
- || isZeroViewport(metrics.innerWidth)
46
- || isZeroViewport(metrics.innerHeight)
39
+ isZeroViewport(metrics.configuredWidth) ||
40
+ isZeroViewport(metrics.configuredHeight) ||
41
+ isZeroViewport(metrics.innerWidth) ||
42
+ isZeroViewport(metrics.innerHeight)
47
43
  );
48
44
  }
49
45
 
50
46
  function isZeroWidthScreenshotError(error: unknown): boolean {
51
47
  return (
52
- error instanceof Error
53
- && error.message.includes("Cannot take screenshot with 0 width")
48
+ error instanceof Error &&
49
+ error.message.includes("Cannot take screenshot with 0 width")
54
50
  );
55
51
  }
56
52
 
57
- async function readSnapshotViewportMetrics(
58
- page: {
59
- viewportSize(): { width: number; height: number } | null;
60
- evaluate<T>(pageFunction: () => T | Promise<T>): Promise<T>;
61
- },
62
- ): Promise<SnapshotViewportMetrics> {
53
+ async function readSnapshotViewportMetrics(page: {
54
+ viewportSize(): { width: number; height: number } | null;
55
+ evaluate<T>(pageFunction: () => T | Promise<T>): Promise<T>;
56
+ }): Promise<SnapshotViewportMetrics> {
63
57
  const configuredViewport = page.viewportSize();
64
58
  let innerWidth: number | null = null;
65
59
  let innerHeight: number | null = null;
@@ -161,6 +155,12 @@ async function captureScreenshot(
161
155
  const htmlPath = `${snapshotRunDir}/page.html`;
162
156
  const condensedHtmlPath = `${snapshotRunDir}/page.condensed.html`;
163
157
 
158
+ const RENDER_SETTLE_TIMEOUT_MS = 10_000;
159
+ await Promise.race([
160
+ page.waitForLoadState("networkidle").catch(() => {}),
161
+ new Promise((resolve) => setTimeout(resolve, RENDER_SETTLE_TIMEOUT_MS)),
162
+ ]);
163
+
164
164
  const restoreViewport = resolveSnapshotViewport(session, logger);
165
165
  const viewportMetrics = await readSnapshotViewportMetrics(page);
166
166
  logger.info("screenshot-viewport-metrics", {
@@ -249,22 +249,15 @@ async function captureScreenshot(
249
249
  async function runSnapshot(
250
250
  session: string,
251
251
  logger: LoggerApi,
252
- pageId?: string,
253
- objective?: string,
254
- context?: string,
252
+ pageId: string | undefined,
253
+ objective: string,
254
+ context: string,
255
255
  ): Promise<void> {
256
- const normalizedObjective = objective?.trim();
257
- const normalizedContext = context?.trim();
258
- if (!normalizedObjective && normalizedContext) {
259
- throw new Error(
260
- "Couldn't run analysis: --objective is required when providing --context.",
261
- );
262
- }
256
+ const normalizedObjective = objective.trim();
257
+ const normalizedContext = context.trim();
263
258
 
264
- const configuredAi = normalizedObjective ? readAiConfig() : null;
265
- if (normalizedObjective) {
266
- resolveSnapshotApiModelOrThrow(configuredAi);
267
- }
259
+ const configuredAi = readAiConfig();
260
+ resolveSnapshotApiModelOrThrow(configuredAi);
268
261
 
269
262
  const { pngPath, htmlPath, condensedHtmlPath } = await captureScreenshot(
270
263
  session,
@@ -277,15 +270,10 @@ async function runSnapshot(
277
270
  console.log(` HTML: ${htmlPath}`);
278
271
  console.log(` Condensed HTML: ${condensedHtmlPath}`);
279
272
 
280
- if (!normalizedObjective) {
281
- console.log("Use --objective flag to analyze snapshots.");
282
- return;
283
- }
284
-
285
273
  const interpretArgs: InterpretArgs = {
286
274
  objective: normalizedObjective,
287
275
  session,
288
- context: normalizedContext ?? DEFAULT_SNAPSHOT_CONTEXT,
276
+ context: normalizedContext,
289
277
  pngPath,
290
278
  htmlPath,
291
279
  condensedHtmlPath,
@@ -303,25 +291,22 @@ export const snapshotInput = SimpleCLI.input({
303
291
  named: {
304
292
  session: sessionOption(),
305
293
  page: pageOption(),
306
- objective: SimpleCLI.option(z.string().optional()),
307
- context: SimpleCLI.option(z.string().optional()),
294
+ objective: SimpleCLI.option(z.string()),
295
+ context: SimpleCLI.option(z.string()),
308
296
  },
309
297
  });
310
298
 
311
- export function createSnapshotCommand(logger: LoggerApi) {
312
- return SimpleCLI.command({
313
- description: "Capture PNG + HTML; analyze when --objective is provided (--context optional)",
314
- })
315
- .input(snapshotInput)
316
- .use(resolveSessionMiddleware)
317
- .use(loadSessionStateMiddleware)
318
- .handle(async ({ input, ctx }) => {
319
- await runSnapshot(
320
- ctx.session,
321
- logger,
322
- input.page,
323
- input.objective,
324
- input.context,
325
- );
326
- });
327
- }
299
+ export const snapshotCommand = SimpleCLI.command({
300
+ description: "Capture PNG + HTML and analyze with --objective and --context",
301
+ })
302
+ .input(snapshotInput)
303
+ .use(withRequiredSession())
304
+ .handle(async ({ input, ctx }) => {
305
+ await runSnapshot(
306
+ ctx.session,
307
+ ctx.logger,
308
+ input.page,
309
+ input.objective,
310
+ input.context,
311
+ );
312
+ });
@@ -29,11 +29,18 @@ export const ViewportConfigSchema = z.object({
29
29
  });
30
30
  export type ViewportConfig = z.infer<typeof ViewportConfigSchema>;
31
31
 
32
+ export const WindowPositionConfigSchema = z.object({
33
+ x: z.number().int(),
34
+ y: z.number().int(),
35
+ });
36
+ export type WindowPositionConfig = z.infer<typeof WindowPositionConfigSchema>;
37
+
32
38
  export const LibrettoConfigSchema = z
33
39
  .object({
34
40
  version: z.literal(CURRENT_CONFIG_VERSION),
35
41
  ai: AiConfigSchema.optional(),
36
42
  viewport: ViewportConfigSchema.optional(),
43
+ windowPosition: WindowPositionConfigSchema.optional(),
37
44
  })
38
45
  .passthrough();
39
46
  export type LibrettoConfig = z.infer<typeof LibrettoConfigSchema>;
@@ -51,7 +58,12 @@ const PROVIDER_ALIASES: Record<string, string> = {
51
58
  google: DEFAULT_MODELS.gemini,
52
59
  };
53
60
 
54
- const CONFIGURE_PROVIDERS = ["openai", "anthropic", "gemini", "vertex"] as const;
61
+ const CONFIGURE_PROVIDERS = [
62
+ "openai",
63
+ "anthropic",
64
+ "gemini",
65
+ "vertex",
66
+ ] as const;
55
67
 
56
68
  function formatConfigureProviders(separator = " | "): string {
57
69
  return CONFIGURE_PROVIDERS.join(separator);
@@ -75,6 +87,10 @@ function formatExpectedConfigExample(): string {
75
87
  width: 1280,
76
88
  height: 800,
77
89
  },
90
+ windowPosition: {
91
+ x: 1600,
92
+ y: 120,
93
+ },
78
94
  },
79
95
  null,
80
96
  2,
@@ -89,11 +105,13 @@ function invalidConfigError(configPath: string, detail?: string): Error {
89
105
  "Expected config example:",
90
106
  formatExpectedConfigExample(),
91
107
  "Notes:",
92
- ' - "ai" and "viewport" are optional.',
108
+ ' - "ai", "viewport", and "windowPosition" are optional.',
93
109
  ' - "ai.model" must be a provider/model string like "openai/gpt-5.4" or "anthropic/claude-sonnet-4-6".',
94
110
  "Fix the file to match this shape, or delete it and rerun:",
95
111
  ` npx libretto ai configure ${formatConfigureProviders()}`,
96
- ].filter(Boolean).join("\n"),
112
+ ]
113
+ .filter(Boolean)
114
+ .join("\n"),
97
115
  );
98
116
  }
99
117
 
@@ -220,7 +238,9 @@ export function runAiConfigure(
220
238
  console.log(
221
239
  `No AI config set. Choose a default model: ${configureCommandName} ${formatConfigureProviders()}`,
222
240
  );
223
- console.log("Provider credentials still come from your shell or .env file.");
241
+ console.log(
242
+ "Provider credentials still come from your shell or .env file.",
243
+ );
224
244
  return;
225
245
  }
226
246
  printAiConfig(config, configPath);
@@ -10,7 +10,6 @@ import { readFileSync } from "node:fs";
10
10
  import type { LoggerApi } from "../../shared/logger/index.js";
11
11
  import { createLLMClient } from "../../shared/llm/client.js";
12
12
  import {
13
- formatInterpretationOutput,
14
13
  InterpretResultSchema,
15
14
  buildInlinePromptSelection,
16
15
  getMimeType,
@@ -19,9 +18,7 @@ import {
19
18
  type InterpretArgs,
20
19
  } from "./snapshot-analyzer.js";
21
20
  import { readAiConfig, type AiConfig } from "./ai-config.js";
22
- import {
23
- resolveSnapshotApiModelOrThrow,
24
- } from "./snapshot-api-config.js";
21
+ import { resolveSnapshotApiModelOrThrow } from "./snapshot-api-config.js";
25
22
 
26
23
  export async function runApiInterpret(
27
24
  args: InterpretArgs,
@@ -52,7 +49,8 @@ export async function runApiInterpret(
52
49
  logger.info("api-interpret-dom-selection", {
53
50
  configuredModel: promptSelection.stats.configuredModel,
54
51
  fullDomEstimatedTokens: promptSelection.stats.fullDomEstimatedTokens,
55
- condensedDomEstimatedTokens: promptSelection.stats.condensedDomEstimatedTokens,
52
+ condensedDomEstimatedTokens:
53
+ promptSelection.stats.condensedDomEstimatedTokens,
56
54
  contextWindowTokens: promptSelection.budget.contextWindowTokens,
57
55
  promptBudgetTokens: promptSelection.budget.promptBudgetTokens,
58
56
  selectedDom: promptSelection.domSource,
@@ -93,5 +91,18 @@ export async function runApiInterpret(
93
91
  answer: parsed.answer.slice(0, 200),
94
92
  });
95
93
 
96
- console.log(formatInterpretationOutput(parsed, "Interpretation (via API):"));
94
+ console.log("");
95
+ console.log("Analysis:");
96
+ console.log(parsed.answer);
97
+ if (parsed.selectors.length > 0) {
98
+ console.log("");
99
+ console.log("Selectors:");
100
+ parsed.selectors.forEach((selector, index) => {
101
+ console.log(` ${index + 1}. ${selector.label}: ${selector.selector}`);
102
+ });
103
+ }
104
+ if (parsed.notes?.trim()) {
105
+ console.log("");
106
+ console.log(`Notes: ${parsed.notes.trim()}`);
107
+ }
97
108
  }