libretto 0.6.10 → 0.6.12

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 (119) hide show
  1. package/README.md +4 -0
  2. package/README.template.md +4 -0
  3. package/dist/cli/cli.js +4 -3
  4. package/dist/cli/commands/ai.js +3 -2
  5. package/dist/cli/commands/browser.js +17 -17
  6. package/dist/cli/commands/execution.js +254 -234
  7. package/dist/cli/commands/experiments.js +100 -0
  8. package/dist/cli/commands/setup.js +20 -34
  9. package/dist/cli/commands/shared.js +10 -0
  10. package/dist/cli/commands/snapshot.js +81 -9
  11. package/dist/cli/commands/status.js +5 -4
  12. package/dist/cli/core/ai-model.js +6 -3
  13. package/dist/cli/core/browser.js +300 -121
  14. package/dist/cli/core/config.js +4 -2
  15. package/dist/cli/core/context.js +4 -0
  16. package/dist/cli/core/daemon/config.js +0 -6
  17. package/dist/cli/core/daemon/daemon.js +535 -89
  18. package/dist/cli/core/daemon/ipc.js +170 -129
  19. package/dist/cli/core/daemon/snapshot.js +72 -6
  20. package/dist/cli/core/experiments.js +66 -0
  21. package/dist/cli/core/session.js +5 -4
  22. package/dist/cli/core/skill-version.js +2 -1
  23. package/dist/cli/core/snapshot-analyzer.js +4 -3
  24. package/dist/cli/core/workflow-runner/runner.js +147 -0
  25. package/dist/cli/core/workflow-runtime.js +60 -0
  26. package/dist/cli/router.js +4 -1
  27. package/dist/shared/debug/pause-handler.d.ts +9 -0
  28. package/dist/shared/debug/pause-handler.js +15 -0
  29. package/dist/shared/debug/pause.d.ts +1 -2
  30. package/dist/shared/debug/pause.js +13 -36
  31. package/dist/shared/ipc/child-process-transport.d.ts +7 -0
  32. package/dist/shared/ipc/child-process-transport.js +60 -0
  33. package/dist/shared/ipc/child-process-transport.spec.d.ts +2 -0
  34. package/dist/shared/ipc/child-process-transport.spec.js +68 -0
  35. package/dist/shared/ipc/ipc.d.ts +46 -0
  36. package/dist/shared/ipc/ipc.js +165 -0
  37. package/dist/shared/ipc/ipc.spec.d.ts +2 -0
  38. package/dist/shared/ipc/ipc.spec.js +114 -0
  39. package/dist/shared/ipc/socket-transport.d.ts +9 -0
  40. package/dist/shared/ipc/socket-transport.js +143 -0
  41. package/dist/shared/ipc/socket-transport.spec.d.ts +2 -0
  42. package/dist/shared/ipc/socket-transport.spec.js +117 -0
  43. package/dist/shared/package-manager.d.ts +7 -0
  44. package/dist/shared/package-manager.js +60 -0
  45. package/dist/shared/paths/paths.d.ts +1 -8
  46. package/dist/shared/paths/paths.js +1 -49
  47. package/dist/shared/snapshot/capture-snapshot.d.ts +9 -0
  48. package/dist/shared/snapshot/capture-snapshot.js +463 -0
  49. package/dist/shared/snapshot/diff-snapshots.d.ts +72 -0
  50. package/dist/shared/snapshot/diff-snapshots.js +358 -0
  51. package/dist/shared/snapshot/render-snapshot.d.ts +39 -0
  52. package/dist/shared/snapshot/render-snapshot.js +651 -0
  53. package/dist/shared/snapshot/snapshot.spec.d.ts +2 -0
  54. package/dist/shared/snapshot/snapshot.spec.js +333 -0
  55. package/dist/shared/snapshot/types.d.ts +40 -0
  56. package/dist/shared/snapshot/types.js +0 -0
  57. package/dist/shared/snapshot/wait-for-page-stable.d.ts +17 -0
  58. package/dist/shared/snapshot/wait-for-page-stable.js +281 -0
  59. package/dist/shared/state/session-state.d.ts +1 -0
  60. package/dist/shared/state/session-state.js +1 -0
  61. package/docs/experiments.md +67 -0
  62. package/package.json +4 -2
  63. package/skills/libretto/SKILL.md +3 -1
  64. package/skills/libretto-readonly/SKILL.md +1 -1
  65. package/src/cli/AGENTS.md +7 -0
  66. package/src/cli/cli.ts +4 -3
  67. package/src/cli/commands/ai.ts +3 -2
  68. package/src/cli/commands/browser.ts +13 -11
  69. package/src/cli/commands/execution.ts +303 -271
  70. package/src/cli/commands/experiments.ts +120 -0
  71. package/src/cli/commands/setup.ts +18 -36
  72. package/src/cli/commands/shared.ts +20 -0
  73. package/src/cli/commands/snapshot.ts +99 -11
  74. package/src/cli/commands/status.ts +5 -4
  75. package/src/cli/core/ai-model.ts +6 -3
  76. package/src/cli/core/browser.ts +369 -147
  77. package/src/cli/core/config.ts +3 -1
  78. package/src/cli/core/context.ts +4 -0
  79. package/src/cli/core/daemon/config.ts +35 -19
  80. package/src/cli/core/daemon/daemon.ts +686 -106
  81. package/src/cli/core/daemon/ipc.ts +330 -214
  82. package/src/cli/core/daemon/snapshot.ts +106 -8
  83. package/src/cli/core/experiments.ts +85 -0
  84. package/src/cli/core/session.ts +5 -4
  85. package/src/cli/core/skill-version.ts +2 -1
  86. package/src/cli/core/snapshot-analyzer.ts +4 -3
  87. package/src/cli/core/workflow-runner/runner.ts +237 -0
  88. package/src/cli/core/workflow-runtime.ts +85 -0
  89. package/src/cli/router.ts +4 -1
  90. package/src/shared/debug/pause-handler.ts +20 -0
  91. package/src/shared/debug/pause.ts +14 -48
  92. package/src/shared/ipc/AGENTS.md +24 -0
  93. package/src/shared/ipc/child-process-transport.spec.ts +86 -0
  94. package/src/shared/ipc/child-process-transport.ts +96 -0
  95. package/src/shared/ipc/ipc.spec.ts +161 -0
  96. package/src/shared/ipc/ipc.ts +288 -0
  97. package/src/shared/ipc/socket-transport.spec.ts +141 -0
  98. package/src/shared/ipc/socket-transport.ts +189 -0
  99. package/src/shared/package-manager.ts +76 -0
  100. package/src/shared/paths/paths.ts +0 -72
  101. package/src/shared/snapshot/capture-snapshot.ts +615 -0
  102. package/src/shared/snapshot/diff-snapshots.ts +579 -0
  103. package/src/shared/snapshot/render-snapshot.ts +962 -0
  104. package/src/shared/snapshot/snapshot.spec.ts +388 -0
  105. package/src/shared/snapshot/types.ts +43 -0
  106. package/src/shared/snapshot/wait-for-page-stable.ts +425 -0
  107. package/src/shared/state/session-state.ts +1 -0
  108. package/dist/cli/core/daemon/index.js +0 -16
  109. package/dist/cli/core/daemon/spawn.js +0 -90
  110. package/dist/cli/core/pause-signals.js +0 -29
  111. package/dist/cli/workers/run-integration-runtime.js +0 -235
  112. package/dist/cli/workers/run-integration-worker-protocol.js +0 -17
  113. package/dist/cli/workers/run-integration-worker.js +0 -64
  114. package/src/cli/core/daemon/index.ts +0 -24
  115. package/src/cli/core/daemon/spawn.ts +0 -171
  116. package/src/cli/core/pause-signals.ts +0 -35
  117. package/src/cli/workers/run-integration-runtime.ts +0 -326
  118. package/src/cli/workers/run-integration-worker-protocol.ts +0 -19
  119. package/src/cli/workers/run-integration-worker.ts +0 -72
@@ -0,0 +1,100 @@
1
+ import { z } from "zod";
2
+ import { librettoCommand } from "../../shared/package-manager.js";
3
+ import {
4
+ EXPERIMENTS,
5
+ isExperimentName,
6
+ resolveExperiments,
7
+ setExperimentEnabled
8
+ } from "../core/experiments.js";
9
+ import { SimpleCLI } from "../framework/simple-cli.js";
10
+ const experimentNames = Object.keys(EXPERIMENTS);
11
+ const experimentsUsage = [
12
+ "Usage:",
13
+ ` ${librettoCommand("experiments")}`,
14
+ ` ${librettoCommand("experiments describe <experiment>")}`,
15
+ ` ${librettoCommand("experiments enable <experiment>")}`,
16
+ ` ${librettoCommand("experiments disable <experiment>")}`
17
+ ].join("\n");
18
+ const experimentsInput = SimpleCLI.input({
19
+ positionals: [
20
+ SimpleCLI.positional("action", z.string().optional(), {
21
+ help: "Action to apply"
22
+ }),
23
+ SimpleCLI.positional("experiment", z.string().optional(), {
24
+ help: "Experiment name"
25
+ })
26
+ ],
27
+ named: {}
28
+ });
29
+ function formatAvailableExperiments() {
30
+ return [
31
+ "Available experiments:",
32
+ ...experimentNames.map((name) => ` ${name}`)
33
+ ].join("\n");
34
+ }
35
+ function experimentUsageError(message) {
36
+ return new Error(
37
+ [message, "", experimentsUsage, "", formatAvailableExperiments()].join(
38
+ "\n"
39
+ )
40
+ );
41
+ }
42
+ function printExperiments(experiments) {
43
+ console.log("Libretto experiments:");
44
+ for (const name of experimentNames) {
45
+ const metadata = EXPERIMENTS[name];
46
+ console.log(
47
+ `- ${name}: ${experiments[name] ? "enabled" : "disabled"} \u2014 ${metadata.title}`
48
+ );
49
+ console.log(` ${metadata.oneSentenceDescription}`);
50
+ }
51
+ }
52
+ function printExperimentDescription(name, experiments) {
53
+ const metadata = EXPERIMENTS[name];
54
+ console.log(`${metadata.title} (${name})`);
55
+ console.log(`Status: ${experiments[name] ? "enabled" : "disabled"}`);
56
+ console.log("");
57
+ if (experiments[name]) {
58
+ console.log(
59
+ "Since this experiment is enabled, Libretto\u2019s expected usage deviates from the skill. Use these instructions where they differ:"
60
+ );
61
+ console.log("");
62
+ }
63
+ console.log(metadata.docs ?? metadata.oneSentenceDescription);
64
+ }
65
+ const experimentsCommand = SimpleCLI.command({
66
+ description: "List or update Libretto experiment flags"
67
+ }).input(experimentsInput).handle(async ({ input }) => {
68
+ if (!input.action) {
69
+ printExperiments(resolveExperiments());
70
+ return;
71
+ }
72
+ if (input.action !== "describe" && input.action !== "enable" && input.action !== "disable") {
73
+ throw experimentUsageError(`Unknown experiments action "${input.action}".`);
74
+ }
75
+ if (!input.experiment) {
76
+ throw experimentUsageError(
77
+ `Missing experiment name for ${input.action}.`
78
+ );
79
+ }
80
+ if (!isExperimentName(input.experiment)) {
81
+ throw experimentUsageError(`Unknown experiment "${input.experiment}".`);
82
+ }
83
+ if (input.action === "describe") {
84
+ printExperimentDescription(input.experiment, resolveExperiments());
85
+ return;
86
+ }
87
+ const experiments = setExperimentEnabled(
88
+ input.experiment,
89
+ input.action === "enable"
90
+ );
91
+ console.log(`Experiment "${input.experiment}" ${input.action}d.`);
92
+ if (input.action === "enable") {
93
+ console.log("");
94
+ printExperimentDescription(input.experiment, experiments);
95
+ }
96
+ });
97
+ export {
98
+ experimentsCommand,
99
+ experimentsInput
100
+ };
@@ -1,10 +1,5 @@
1
1
  import { createInterface } from "node:readline";
2
- import {
3
- cpSync,
4
- existsSync,
5
- readdirSync,
6
- rmSync
7
- } from "node:fs";
2
+ import { cpSync, existsSync, readdirSync, rmSync } from "node:fs";
8
3
  import { spawnSync } from "node:child_process";
9
4
  import { basename, dirname, join } from "node:path";
10
5
  import { fileURLToPath } from "node:url";
@@ -18,6 +13,11 @@ import {
18
13
  DEFAULT_SNAPSHOT_MODELS,
19
14
  resolveAiSetupStatus
20
15
  } from "../core/ai-model.js";
16
+ import {
17
+ detectProjectPackageManager,
18
+ installCommand,
19
+ librettoCommand
20
+ } from "../../shared/package-manager.js";
21
21
  import { SimpleCLI } from "../framework/simple-cli.js";
22
22
  const PROVIDER_SDK_PACKAGES = {
23
23
  openai: "@ai-sdk/openai",
@@ -26,24 +26,6 @@ const PROVIDER_SDK_PACKAGES = {
26
26
  vertex: "@ai-sdk/google-vertex",
27
27
  openrouter: "@ai-sdk/openai"
28
28
  };
29
- function detectPackageManager() {
30
- if (existsSync(join(REPO_ROOT, "pnpm-lock.yaml"))) return "pnpm";
31
- if (existsSync(join(REPO_ROOT, "yarn.lock"))) return "yarn";
32
- if (existsSync(join(REPO_ROOT, "bun.lockb"))) return "bun";
33
- return "npm";
34
- }
35
- function installCommand(pkgManager) {
36
- switch (pkgManager) {
37
- case "yarn":
38
- return "yarn add";
39
- case "bun":
40
- return "bun add";
41
- case "pnpm":
42
- return "pnpm add";
43
- default:
44
- return "npm install";
45
- }
46
- }
47
29
  function isSdkInstalled(sdkPackage) {
48
30
  try {
49
31
  const result = spawnSync("node", ["-e", `require.resolve("${sdkPackage}")`], {
@@ -58,7 +40,7 @@ function isSdkInstalled(sdkPackage) {
58
40
  function installSdkIfNeeded(provider) {
59
41
  const sdkPackage = PROVIDER_SDK_PACKAGES[provider];
60
42
  if (isSdkInstalled(sdkPackage)) return;
61
- const pkgManager = detectPackageManager();
43
+ const pkgManager = detectProjectPackageManager();
62
44
  const cmd = installCommand(pkgManager);
63
45
  console.log(`
64
46
  Installing ${sdkPackage}...`);
@@ -144,7 +126,7 @@ function printHealthySummary(status) {
144
126
  console.log(`\u2713 Using ${providerLabel(status.provider)} (${status.model}).`);
145
127
  }
146
128
  console.log(
147
- "To change: npx libretto ai configure openai | anthropic | gemini | vertex | openrouter"
129
+ `To change: ${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}`
148
130
  );
149
131
  }
150
132
  function printInvalidAiConfigWarning(status) {
@@ -189,13 +171,15 @@ function printSnapshotApiStatus() {
189
171
  console.log();
190
172
  console.log(formatMissingCredentialsMessage(plan));
191
173
  console.log(
192
- ` To fix: add ${plan.envVar} to .env, or run \`npx libretto setup\` interactively to repair.`
174
+ ` To fix: add ${plan.envVar} to .env, or run \`${librettoCommand("setup")}\` interactively to repair.`
193
175
  );
194
176
  return false;
195
177
  }
196
178
  if (plan.kind === "repair-invalid-config") {
197
179
  printInvalidAiConfigWarning(status);
198
- console.log(" Run `npx libretto setup` interactively to reconfigure.");
180
+ console.log(
181
+ ` Run \`${librettoCommand("setup")}\` interactively to reconfigure.`
182
+ );
199
183
  return false;
200
184
  }
201
185
  console.log();
@@ -208,10 +192,10 @@ function printSnapshotApiStatus() {
208
192
  " GOOGLE_CLOUD_PROJECT=... # plus application default credentials for Vertex"
209
193
  );
210
194
  console.log(
211
- " Or run `npx libretto ai configure openai | anthropic | gemini | vertex | openrouter` to set a specific model."
195
+ ` Or run \`${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}\` to set a specific model.`
212
196
  );
213
197
  console.log(
214
- " Run `npx libretto setup` interactively to set up credentials."
198
+ ` Run \`${librettoCommand("setup")}\` interactively to set up credentials.`
215
199
  );
216
200
  return false;
217
201
  }
@@ -246,14 +230,15 @@ Add ${selected.envVar} to your .env file:`);
246
230
  }
247
231
  function printSkipMessage() {
248
232
  console.log(
249
- "\nSkipped. You can set up API credentials later by rerunning `npx libretto setup`."
233
+ `
234
+ Skipped. You can set up API credentials later by rerunning \`${librettoCommand("setup")}\`.`
250
235
  );
251
236
  console.log("Or add credentials directly to your .env file:");
252
237
  console.log(" OPENAI_API_KEY=...");
253
238
  console.log(" ANTHROPIC_API_KEY=...");
254
239
  console.log(" GEMINI_API_KEY=...");
255
240
  console.log(
256
- " Or run `npx libretto ai configure openai | anthropic | gemini | vertex | openrouter` to set a specific model."
241
+ ` Or run \`${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}\` to set a specific model.`
257
242
  );
258
243
  }
259
244
  async function runInteractiveApiSetup() {
@@ -341,7 +326,7 @@ function copySkills() {
341
326
  "\n\u26A0\uFE0F No .agents/ or .claude/ directory found. Libretto skills were not installed."
342
327
  );
343
328
  console.log(
344
- " Create one of these directories in your repo root and rerun `npx libretto setup` to install skills:"
329
+ ` Create one of these directories in your repo root and rerun \`${librettoCommand("setup")}\` to install skills:`
345
330
  );
346
331
  console.log(` mkdir ${join(REPO_ROOT, ".claude")}`);
347
332
  return;
@@ -395,7 +380,8 @@ const setupCommand = SimpleCLI.command({
395
380
  const ready = printSnapshotApiStatus();
396
381
  if (!ready) {
397
382
  console.log(
398
- "\nIf you're an agent, request the user to run `npx libretto setup`."
383
+ `
384
+ If you're an agent, request the user to run \`${librettoCommand("setup")}\`.`
399
385
  );
400
386
  }
401
387
  }
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { resolveExperiments } from "../core/experiments.js";
2
3
  import { createLoggerForSession } from "../core/context.js";
3
4
  import {
4
5
  generateSessionName,
@@ -17,6 +18,14 @@ function pageOption(help = "Target a specific page id") {
17
18
  function integerOption(help) {
18
19
  return SimpleCLI.option(z.coerce.number().int().optional(), { help });
19
20
  }
21
+ function withExperiments() {
22
+ return async ({
23
+ ctx
24
+ }) => ({
25
+ ...ctx,
26
+ experiments: resolveExperiments()
27
+ });
28
+ }
20
29
  function withRequiredSession() {
21
30
  return async ({ input, ctx }) => {
22
31
  if (!input.session) {
@@ -47,5 +56,6 @@ export {
47
56
  pageOption,
48
57
  sessionOption,
49
58
  withAutoSession,
59
+ withExperiments,
50
60
  withRequiredSession
51
61
  };
@@ -3,11 +3,18 @@ import { z } from "zod";
3
3
  import { condenseDom } from "../../shared/condense-dom/condense-dom.js";
4
4
  import { readSessionState } from "../core/session.js";
5
5
  import { SimpleCLI } from "../framework/simple-cli.js";
6
- import { pageOption, sessionOption, withRequiredSession } from "./shared.js";
6
+ import {
7
+ pageOption,
8
+ sessionOption,
9
+ withExperiments,
10
+ withRequiredSession
11
+ } from "./shared.js";
7
12
  import { runApiInterpret } from "../core/api-snapshot-analyzer.js";
8
13
  import { readSnapshotModel } from "../core/config.js";
9
14
  import { resolveSnapshotApiModelOrThrow } from "../core/ai-model.js";
10
- import { DaemonClient } from "../core/daemon/index.js";
15
+ import { DaemonClient } from "../core/daemon/ipc.js";
16
+ import { librettoCommand } from "../../shared/package-manager.js";
17
+ import { renderSnapshot } from "../../shared/snapshot/render-snapshot.js";
11
18
  const FALLBACK_SNAPSHOT_VIEWPORT = { width: 1280, height: 800 };
12
19
  function isZeroViewport(value) {
13
20
  return typeof value === "number" && value <= 0;
@@ -65,8 +72,17 @@ async function forceSnapshotViewport(page, viewport, logger, session, pageId, re
65
72
  }
66
73
  async function captureSnapshot(session, logger, daemonSocketPath, pageId) {
67
74
  logger.info("snapshot-via-daemon", { session, pageId });
68
- const client = new DaemonClient(daemonSocketPath);
69
- const { pngPath, htmlPath, snapshotRunId, pageUrl, title } = await client.snapshot({ pageId });
75
+ const client = await DaemonClient.connect(daemonSocketPath);
76
+ let snapshotResult;
77
+ try {
78
+ snapshotResult = await client.snapshot({ pageId });
79
+ } finally {
80
+ client.destroy();
81
+ }
82
+ if (!("htmlPath" in snapshotResult)) {
83
+ throw new Error("Daemon returned a compact snapshot for a legacy request.");
84
+ }
85
+ const { pngPath, htmlPath, snapshotRunId, pageUrl, title } = snapshotResult;
70
86
  const htmlContent = readFileSync(htmlPath, "utf8");
71
87
  const condenseResult = condenseDom(htmlContent);
72
88
  const condensedHtmlPath = htmlPath.replace(/\.html$/, ".condensed.html");
@@ -88,6 +104,12 @@ async function captureSnapshot(session, logger, daemonSocketPath, pageId) {
88
104
  return { pngPath, htmlPath, condensedHtmlPath, baseName: snapshotRunId };
89
105
  }
90
106
  async function runSnapshot(session, logger, pageId, objective, context) {
107
+ if (objective === void 0) {
108
+ throw new Error("Missing required option --objective.");
109
+ }
110
+ if (context === void 0) {
111
+ throw new Error("Missing required option --context.");
112
+ }
91
113
  const normalizedObjective = objective.trim();
92
114
  const normalizedContext = context.trim();
93
115
  const snapshotModel = readSnapshotModel();
@@ -95,7 +117,7 @@ async function runSnapshot(session, logger, pageId, objective, context) {
95
117
  const state = readSessionState(session, logger);
96
118
  if (!state?.daemonSocketPath) {
97
119
  throw new Error(
98
- `Session "${session}" has no daemon socket. The browser daemon may have crashed. Close and reopen the session: libretto close --session ${session}`
120
+ `Session "${session}" has no daemon socket. The browser daemon may have crashed. Close and reopen the session: ${librettoCommand(`close --session ${session}`)}`
99
121
  );
100
122
  }
101
123
  const { pngPath, htmlPath, condensedHtmlPath } = await captureSnapshot(session, logger, state.daemonSocketPath, pageId);
@@ -113,18 +135,68 @@ async function runSnapshot(session, logger, pageId, objective, context) {
113
135
  };
114
136
  await runApiInterpret(interpretArgs, logger, snapshotModel);
115
137
  }
138
+ async function runCompactSnapshot(args) {
139
+ if (!args.daemonSocketPath) {
140
+ throw new Error(
141
+ `Session "${args.session}" has no daemon socket. The browser daemon may have crashed. Close and reopen the session: ${librettoCommand(`close --session ${args.session}`)}`
142
+ );
143
+ }
144
+ args.logger.info("compact-snapshot-via-daemon", {
145
+ session: args.session,
146
+ pageId: args.pageId,
147
+ ref: args.ref
148
+ });
149
+ const client = await DaemonClient.connect(args.daemonSocketPath);
150
+ let result;
151
+ try {
152
+ result = await client.snapshot({
153
+ mode: "compact",
154
+ pageId: args.pageId,
155
+ useCachedSnapshot: args.ref !== void 0
156
+ });
157
+ } finally {
158
+ client.destroy();
159
+ }
160
+ if (!("mode" in result) || result.mode !== "compact") {
161
+ throw new Error("Daemon returned a legacy snapshot for a compact request.");
162
+ }
163
+ console.log(`Screenshot at ${result.pngPath}`);
164
+ console.log(renderSnapshot(result.snapshot, args.ref));
165
+ console.log(
166
+ `Hint: Use ${librettoCommand(`snapshot <ref> --session ${args.session}`)} to inspect a subtree.`
167
+ );
168
+ }
116
169
  const snapshotInput = SimpleCLI.input({
117
- positionals: [],
170
+ positionals: [
171
+ SimpleCLI.positional("ref", z.string().optional(), {
172
+ help: "Optional element ref to scope output to that subtree (for example, l16 or e16)"
173
+ })
174
+ ],
118
175
  named: {
119
176
  session: sessionOption(),
120
177
  page: pageOption(),
121
- objective: SimpleCLI.option(z.string()),
122
- context: SimpleCLI.option(z.string())
178
+ objective: SimpleCLI.option(z.string().optional()),
179
+ context: SimpleCLI.option(z.string().optional())
123
180
  }
124
181
  });
125
182
  const snapshotCommand = SimpleCLI.command({
126
183
  description: "Capture PNG + HTML and analyze with --objective and --context"
127
- }).input(snapshotInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
184
+ }).input(snapshotInput).use(withRequiredSession()).use(withExperiments()).handle(async ({ input, ctx }) => {
185
+ if (ctx.experiments["compact-snapshot-format"]) {
186
+ await runCompactSnapshot({
187
+ session: ctx.session,
188
+ daemonSocketPath: ctx.sessionState.daemonSocketPath,
189
+ logger: ctx.logger,
190
+ pageId: input.page,
191
+ ref: input.ref
192
+ });
193
+ return;
194
+ }
195
+ if (input.ref) {
196
+ throw new Error(
197
+ `Snapshot refs require the compact-snapshot-format experiment. Enable it with ${librettoCommand("experiments enable compact-snapshot-format")}.`
198
+ );
199
+ }
128
200
  await runSnapshot(
129
201
  ctx.session,
130
202
  ctx.logger,
@@ -1,5 +1,6 @@
1
1
  import { LIBRETTO_CONFIG_PATH } from "../core/context.js";
2
2
  import { resolveAiSetupStatus } from "../core/ai-model.js";
3
+ import { librettoCommand } from "../../shared/package-manager.js";
3
4
  import { listRunningSessions } from "../core/session.js";
4
5
  import { SimpleCLI } from "../framework/simple-cli.js";
5
6
  function printAiStatus(status) {
@@ -13,26 +14,26 @@ function printAiStatus(status) {
13
14
  console.log(` Source: ${status.source}`);
14
15
  }
15
16
  console.log(
16
- " To change: npx libretto ai configure openai | anthropic | gemini | vertex | openrouter"
17
+ ` To change: ${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}`
17
18
  );
18
19
  break;
19
20
  case "configured-missing-credentials":
20
21
  console.log(
21
22
  ` \u2717 ${status.provider} is configured (model: ${status.model}), but credentials are missing.`
22
23
  );
23
- console.log(" Run `npx libretto setup` to repair.");
24
+ console.log(` Run \`${librettoCommand("setup")}\` to repair.`);
24
25
  break;
25
26
  case "invalid-config":
26
27
  console.log(" \u2717 Config is invalid:");
27
28
  for (const line of status.message.split("\n")) {
28
29
  console.log(` ${line}`);
29
30
  }
30
- console.log(" Run `npx libretto setup` to reconfigure.");
31
+ console.log(` Run \`${librettoCommand("setup")}\` to reconfigure.`);
31
32
  break;
32
33
  case "unconfigured":
33
34
  console.log(" \u2717 No AI model configured.");
34
35
  console.log(
35
- " Run `npx libretto setup` or `npx libretto ai configure` to set up."
36
+ ` Run \`${librettoCommand("setup")}\` or \`${librettoCommand("ai configure")}\` to set up.`
36
37
  );
37
38
  break;
38
39
  }
@@ -1,5 +1,6 @@
1
1
  import { readSnapshotModel } from "./config.js";
2
2
  import { LIBRETTO_CONFIG_PATH } from "./context.js";
3
+ import { librettoCommand } from "../../shared/package-manager.js";
3
4
  import {
4
5
  hasProviderCredentials,
5
6
  parseModel
@@ -52,7 +53,9 @@ function providerSetupSentence(provider) {
52
53
  }
53
54
  }
54
55
  function defaultModelCommandLine() {
55
- return "npx libretto ai configure openai | anthropic | gemini | vertex | openrouter";
56
+ return librettoCommand(
57
+ "ai configure openai | anthropic | gemini | vertex | openrouter"
58
+ );
56
59
  }
57
60
  function providerMissingCredentialSummary(provider) {
58
61
  switch (provider) {
@@ -72,7 +75,7 @@ function noSnapshotApiConfiguredMessage() {
72
75
  return [
73
76
  "Failed to analyze snapshot because no snapshot analyzer is configured.",
74
77
  `Add OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY, GOOGLE_CLOUD_PROJECT, or OPENROUTER_API_KEY to .env or as a shell environment variable, or choose a default model with \`${defaultModelCommandLine()}\`.`,
75
- "For more info, run `npx libretto setup`."
78
+ `For more info, run \`${librettoCommand("setup")}\`.`
76
79
  ].join(" ");
77
80
  }
78
81
  function missingProviderSnapshotMessage(selection) {
@@ -80,7 +83,7 @@ function missingProviderSnapshotMessage(selection) {
80
83
  return [
81
84
  `Failed to analyze snapshot because ${selection.provider} is configured${configuredSource}, but ${providerMissingCredentialSummary(selection.provider)}.`,
82
85
  providerSetupSentence(selection.provider),
83
- "For more info, run `npx libretto setup`."
86
+ `For more info, run \`${librettoCommand("setup")}\`.`
84
87
  ].join(" ");
85
88
  }
86
89
  function inferAutoSnapshotModel() {