libretto 0.6.11 → 0.6.13

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 (130) hide show
  1. package/README.md +7 -8
  2. package/README.template.md +7 -8
  3. package/dist/cli/cli.js +0 -22
  4. package/dist/cli/commands/browser.js +18 -24
  5. package/dist/cli/commands/execution.js +254 -234
  6. package/dist/cli/commands/experiments.js +100 -0
  7. package/dist/cli/commands/setup.js +3 -310
  8. package/dist/cli/commands/shared.js +10 -0
  9. package/dist/cli/commands/snapshot.js +46 -64
  10. package/dist/cli/commands/status.js +1 -40
  11. package/dist/cli/core/browser.js +303 -124
  12. package/dist/cli/core/config.js +5 -6
  13. package/dist/cli/core/context.js +4 -0
  14. package/dist/cli/core/daemon/config.js +0 -6
  15. package/dist/cli/core/daemon/daemon.js +497 -90
  16. package/dist/cli/core/daemon/ipc.js +170 -129
  17. package/dist/cli/core/daemon/snapshot.js +48 -9
  18. package/dist/cli/core/experiments.js +39 -0
  19. package/dist/cli/core/session.js +5 -4
  20. package/dist/cli/core/skill-version.js +2 -1
  21. package/dist/cli/core/workflow-runner/runner.js +147 -0
  22. package/dist/cli/core/workflow-runtime.js +60 -0
  23. package/dist/cli/index.js +0 -2
  24. package/dist/cli/router.js +4 -3
  25. package/dist/shared/debug/pause-handler.d.ts +9 -0
  26. package/dist/shared/debug/pause-handler.js +15 -0
  27. package/dist/shared/debug/pause.d.ts +1 -2
  28. package/dist/shared/debug/pause.js +13 -36
  29. package/dist/shared/instrumentation/instrument.js +4 -4
  30. package/dist/shared/ipc/child-process-transport.d.ts +7 -0
  31. package/dist/shared/ipc/child-process-transport.js +60 -0
  32. package/dist/shared/ipc/child-process-transport.spec.d.ts +2 -0
  33. package/dist/shared/ipc/child-process-transport.spec.js +68 -0
  34. package/dist/shared/ipc/ipc.d.ts +46 -0
  35. package/dist/shared/ipc/ipc.js +165 -0
  36. package/dist/shared/ipc/ipc.spec.d.ts +2 -0
  37. package/dist/shared/ipc/ipc.spec.js +114 -0
  38. package/dist/shared/ipc/socket-transport.d.ts +9 -0
  39. package/dist/shared/ipc/socket-transport.js +143 -0
  40. package/dist/shared/ipc/socket-transport.spec.d.ts +2 -0
  41. package/dist/shared/ipc/socket-transport.spec.js +117 -0
  42. package/dist/shared/package-manager.d.ts +7 -0
  43. package/dist/shared/package-manager.js +60 -0
  44. package/dist/shared/paths/paths.d.ts +1 -8
  45. package/dist/shared/paths/paths.js +1 -49
  46. package/dist/shared/snapshot/capture-snapshot.d.ts +9 -0
  47. package/dist/shared/snapshot/capture-snapshot.js +463 -0
  48. package/dist/shared/snapshot/diff-snapshots.d.ts +72 -0
  49. package/dist/shared/snapshot/diff-snapshots.js +358 -0
  50. package/dist/shared/snapshot/render-snapshot.d.ts +39 -0
  51. package/dist/shared/snapshot/render-snapshot.js +651 -0
  52. package/dist/shared/snapshot/snapshot.spec.d.ts +2 -0
  53. package/dist/shared/snapshot/snapshot.spec.js +333 -0
  54. package/dist/shared/snapshot/types.d.ts +40 -0
  55. package/dist/shared/snapshot/types.js +0 -0
  56. package/dist/shared/snapshot/wait-for-page-stable.d.ts +17 -0
  57. package/dist/shared/snapshot/wait-for-page-stable.js +281 -0
  58. package/dist/shared/state/session-state.d.ts +1 -0
  59. package/dist/shared/state/session-state.js +1 -0
  60. package/docs/experiments.md +67 -0
  61. package/docs/releasing.md +8 -6
  62. package/package.json +5 -2
  63. package/skills/libretto/SKILL.md +19 -19
  64. package/skills/libretto/references/configuration-file-reference.md +6 -12
  65. package/skills/libretto/references/pages-and-page-targeting.md +1 -1
  66. package/skills/libretto-readonly/SKILL.md +2 -9
  67. package/src/cli/AGENTS.md +7 -0
  68. package/src/cli/cli.ts +0 -23
  69. package/src/cli/commands/browser.ts +14 -18
  70. package/src/cli/commands/execution.ts +303 -271
  71. package/src/cli/commands/experiments.ts +120 -0
  72. package/src/cli/commands/setup.ts +3 -400
  73. package/src/cli/commands/shared.ts +20 -0
  74. package/src/cli/commands/snapshot.ts +54 -94
  75. package/src/cli/commands/status.ts +1 -48
  76. package/src/cli/core/browser.ts +372 -150
  77. package/src/cli/core/config.ts +4 -5
  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 +645 -107
  81. package/src/cli/core/daemon/ipc.ts +319 -214
  82. package/src/cli/core/daemon/snapshot.ts +71 -15
  83. package/src/cli/core/experiments.ts +56 -0
  84. package/src/cli/core/resolve-model.ts +5 -0
  85. package/src/cli/core/session.ts +5 -4
  86. package/src/cli/core/skill-version.ts +2 -1
  87. package/src/cli/core/workflow-runner/runner.ts +237 -0
  88. package/src/cli/core/workflow-runtime.ts +86 -0
  89. package/src/cli/index.ts +0 -1
  90. package/src/cli/router.ts +4 -3
  91. package/src/shared/debug/pause-handler.ts +20 -0
  92. package/src/shared/debug/pause.ts +14 -48
  93. package/src/shared/instrumentation/instrument.ts +4 -4
  94. package/src/shared/ipc/AGENTS.md +24 -0
  95. package/src/shared/ipc/child-process-transport.spec.ts +86 -0
  96. package/src/shared/ipc/child-process-transport.ts +96 -0
  97. package/src/shared/ipc/ipc.spec.ts +161 -0
  98. package/src/shared/ipc/ipc.ts +288 -0
  99. package/src/shared/ipc/socket-transport.spec.ts +141 -0
  100. package/src/shared/ipc/socket-transport.ts +189 -0
  101. package/src/shared/package-manager.ts +76 -0
  102. package/src/shared/paths/paths.ts +0 -72
  103. package/src/shared/snapshot/capture-snapshot.ts +615 -0
  104. package/src/shared/snapshot/diff-snapshots.ts +579 -0
  105. package/src/shared/snapshot/render-snapshot.ts +962 -0
  106. package/src/shared/snapshot/snapshot.spec.ts +388 -0
  107. package/src/shared/snapshot/types.ts +43 -0
  108. package/src/shared/snapshot/wait-for-page-stable.ts +425 -0
  109. package/src/shared/state/session-state.ts +1 -0
  110. package/dist/cli/commands/ai.js +0 -109
  111. package/dist/cli/core/ai-model.js +0 -192
  112. package/dist/cli/core/api-snapshot-analyzer.js +0 -86
  113. package/dist/cli/core/daemon/index.js +0 -16
  114. package/dist/cli/core/daemon/spawn.js +0 -90
  115. package/dist/cli/core/pause-signals.js +0 -29
  116. package/dist/cli/core/snapshot-analyzer.js +0 -666
  117. package/dist/cli/workers/run-integration-runtime.js +0 -235
  118. package/dist/cli/workers/run-integration-worker-protocol.js +0 -17
  119. package/dist/cli/workers/run-integration-worker.js +0 -64
  120. package/scripts/summarize-evals.mjs +0 -135
  121. package/src/cli/commands/ai.ts +0 -143
  122. package/src/cli/core/ai-model.ts +0 -298
  123. package/src/cli/core/api-snapshot-analyzer.ts +0 -110
  124. package/src/cli/core/daemon/index.ts +0 -24
  125. package/src/cli/core/daemon/spawn.ts +0 -171
  126. package/src/cli/core/pause-signals.ts +0 -35
  127. package/src/cli/core/snapshot-analyzer.ts +0 -855
  128. package/src/cli/workers/run-integration-runtime.ts +0 -326
  129. package/src/cli/workers/run-integration-worker-protocol.ts +0 -19
  130. 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,308 +1,14 @@
1
- import { createInterface } from "node:readline";
2
- import {
3
- cpSync,
4
- existsSync,
5
- readdirSync,
6
- rmSync
7
- } from "node:fs";
1
+ import { cpSync, existsSync, readdirSync, rmSync } from "node:fs";
8
2
  import { spawnSync } from "node:child_process";
9
3
  import { basename, dirname, join } from "node:path";
10
4
  import { fileURLToPath } from "node:url";
11
- import { writeSnapshotModel } from "../core/config.js";
12
5
  import {
13
6
  ensureLibrettoSetup,
14
7
  LIBRETTO_CONFIG_PATH,
15
8
  REPO_ROOT
16
9
  } from "../core/context.js";
17
- import {
18
- DEFAULT_SNAPSHOT_MODELS,
19
- resolveAiSetupStatus
20
- } from "../core/ai-model.js";
10
+ import { librettoCommand } from "../../shared/package-manager.js";
21
11
  import { SimpleCLI } from "../framework/simple-cli.js";
22
- const PROVIDER_SDK_PACKAGES = {
23
- openai: "@ai-sdk/openai",
24
- anthropic: "@ai-sdk/anthropic",
25
- google: "@ai-sdk/google",
26
- vertex: "@ai-sdk/google-vertex",
27
- openrouter: "@ai-sdk/openai"
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
- function isSdkInstalled(sdkPackage) {
48
- try {
49
- const result = spawnSync("node", ["-e", `require.resolve("${sdkPackage}")`], {
50
- cwd: REPO_ROOT,
51
- stdio: "pipe"
52
- });
53
- return result.status === 0;
54
- } catch {
55
- return false;
56
- }
57
- }
58
- function installSdkIfNeeded(provider) {
59
- const sdkPackage = PROVIDER_SDK_PACKAGES[provider];
60
- if (isSdkInstalled(sdkPackage)) return;
61
- const pkgManager = detectPackageManager();
62
- const cmd = installCommand(pkgManager);
63
- console.log(`
64
- Installing ${sdkPackage}...`);
65
- const result = spawnSync(cmd, [sdkPackage], {
66
- cwd: REPO_ROOT,
67
- stdio: "inherit",
68
- shell: true
69
- });
70
- if (result.status === 0) {
71
- console.log(`\u2713 Installed ${sdkPackage}`);
72
- } else {
73
- console.error(
74
- `\u2717 Failed to install ${sdkPackage}. Install it manually: ${cmd} ${sdkPackage}`
75
- );
76
- }
77
- }
78
- const PROVIDER_CHOICES = [
79
- {
80
- key: "1",
81
- label: "OpenAI",
82
- provider: "openai",
83
- envVar: "OPENAI_API_KEY",
84
- envHint: "Get your key at https://platform.openai.com/api-keys"
85
- },
86
- {
87
- key: "2",
88
- label: "Anthropic",
89
- provider: "anthropic",
90
- envVar: "ANTHROPIC_API_KEY",
91
- envHint: "Get your key at https://console.anthropic.com/settings/keys"
92
- },
93
- {
94
- key: "3",
95
- label: "Google Gemini",
96
- provider: "google",
97
- envVar: "GEMINI_API_KEY",
98
- envHint: "Get your key at https://aistudio.google.com/apikey"
99
- },
100
- {
101
- key: "4",
102
- label: "Google Vertex AI",
103
- provider: "vertex",
104
- envVar: "GOOGLE_CLOUD_PROJECT",
105
- envHint: "Requires `gcloud auth application-default login` and a GCP project ID"
106
- },
107
- {
108
- key: "5",
109
- label: "OpenRouter",
110
- provider: "openrouter",
111
- envVar: "OPENROUTER_API_KEY",
112
- envHint: "Get your key at https://openrouter.ai/settings/keys"
113
- }
114
- ];
115
- function promptUser(rl, question) {
116
- return new Promise((resolve) => {
117
- rl.question(question, (answer) => {
118
- resolve(answer.trim());
119
- });
120
- });
121
- }
122
- function providerLabel(provider) {
123
- const choice = PROVIDER_CHOICES.find((c) => c.provider === provider);
124
- return choice?.label ?? provider;
125
- }
126
- function sourceEnvVar(source) {
127
- if (source.startsWith("env:")) return source.slice(4);
128
- return null;
129
- }
130
- function ensurePinnedDefaultModel(status) {
131
- if (status.source !== "config") {
132
- writeSnapshotModel(status.model);
133
- return { ...status, source: "config" };
134
- }
135
- return status;
136
- }
137
- function printHealthySummary(status) {
138
- const envVar = sourceEnvVar(status.source);
139
- if (envVar) {
140
- console.log(
141
- `\u2713 Detected ${envVar}. Using ${providerLabel(status.provider)}.`
142
- );
143
- } else {
144
- console.log(`\u2713 Using ${providerLabel(status.provider)} (${status.model}).`);
145
- }
146
- console.log(
147
- "To change: npx libretto ai configure openai | anthropic | gemini | vertex | openrouter"
148
- );
149
- }
150
- function printInvalidAiConfigWarning(status) {
151
- if (status.kind !== "invalid-config") return;
152
- console.log("! Existing AI config is invalid:");
153
- for (const line of status.message.split("\n")) {
154
- console.log(` ${line}`);
155
- }
156
- }
157
- function buildRepairPlan(status) {
158
- if (status.kind === "configured-missing-credentials") {
159
- const choice = PROVIDER_CHOICES.find((c) => c.provider === status.provider);
160
- return {
161
- kind: "repair-missing-credentials",
162
- provider: status.provider,
163
- model: status.model,
164
- envVar: choice?.envVar ?? `${status.provider.toUpperCase()}_API_KEY`,
165
- choices: ["switch-provider", "skip"]
166
- };
167
- }
168
- if (status.kind === "invalid-config") {
169
- return { kind: "repair-invalid-config", message: status.message };
170
- }
171
- return { kind: "no-repair-needed" };
172
- }
173
- function formatMissingCredentialsMessage(plan) {
174
- return `\u2717 ${plan.provider} is configured (model: ${plan.model}), but ${plan.envVar} is not set.`;
175
- }
176
- function printSnapshotApiStatus() {
177
- const status = resolveAiSetupStatus();
178
- console.log(
179
- "\nLibretto uses a sub-agent to analyze DOM snapshots. The model is determined by environment variables."
180
- );
181
- if (status.kind === "ready") {
182
- console.log();
183
- printHealthySummary(status);
184
- ensurePinnedDefaultModel(status);
185
- return true;
186
- }
187
- const plan = buildRepairPlan(status);
188
- if (plan.kind === "repair-missing-credentials") {
189
- console.log();
190
- console.log(formatMissingCredentialsMessage(plan));
191
- console.log(
192
- ` To fix: add ${plan.envVar} to .env, or run \`npx libretto setup\` interactively to repair.`
193
- );
194
- return false;
195
- }
196
- if (plan.kind === "repair-invalid-config") {
197
- printInvalidAiConfigWarning(status);
198
- console.log(" Run `npx libretto setup` interactively to reconfigure.");
199
- return false;
200
- }
201
- console.log();
202
- console.log("\u2717 No snapshot API credentials detected.");
203
- console.log(" Add one provider to .env:");
204
- console.log(" OPENAI_API_KEY=...");
205
- console.log(" ANTHROPIC_API_KEY=...");
206
- console.log(" GEMINI_API_KEY=... # or GOOGLE_GENERATIVE_AI_API_KEY");
207
- console.log(
208
- " GOOGLE_CLOUD_PROJECT=... # plus application default credentials for Vertex"
209
- );
210
- console.log(
211
- " Or run `npx libretto ai configure openai | anthropic | gemini | vertex | openrouter` to set a specific model."
212
- );
213
- console.log(
214
- " Run `npx libretto setup` interactively to set up credentials."
215
- );
216
- return false;
217
- }
218
- async function promptProviderSelection(rl) {
219
- console.log(
220
- "Which model provider would you like to use for snapshot analysis?\n"
221
- );
222
- for (const choice of PROVIDER_CHOICES) {
223
- console.log(` ${choice.key}) ${choice.label}`);
224
- }
225
- console.log(" s) Skip for now\n");
226
- const answer = await promptUser(rl, "Choice: ");
227
- if (answer.toLowerCase() === "s" || !answer) {
228
- printSkipMessage();
229
- return false;
230
- }
231
- const selected = PROVIDER_CHOICES.find((choice) => choice.key === answer);
232
- if (!selected) {
233
- console.log(`
234
- Unknown choice "${answer}". Skipping API setup.`);
235
- return false;
236
- }
237
- const model = DEFAULT_SNAPSHOT_MODELS[selected.provider];
238
- writeSnapshotModel(model);
239
- console.log(`
240
- \u2713 ${selected.label} selected (model: ${model}).`);
241
- console.log(`
242
- Add ${selected.envVar} to your .env file:`);
243
- console.log(` ${selected.envHint}`);
244
- installSdkIfNeeded(selected.provider);
245
- return true;
246
- }
247
- function printSkipMessage() {
248
- console.log(
249
- "\nSkipped. You can set up API credentials later by rerunning `npx libretto setup`."
250
- );
251
- console.log("Or add credentials directly to your .env file:");
252
- console.log(" OPENAI_API_KEY=...");
253
- console.log(" ANTHROPIC_API_KEY=...");
254
- console.log(" GEMINI_API_KEY=...");
255
- console.log(
256
- " Or run `npx libretto ai configure openai | anthropic | gemini | vertex | openrouter` to set a specific model."
257
- );
258
- }
259
- async function runInteractiveApiSetup() {
260
- const status = resolveAiSetupStatus();
261
- console.log(
262
- "\nLibretto uses a sub-agent to analyze DOM snapshots. The model is determined by environment variables."
263
- );
264
- if (status.kind === "ready") {
265
- console.log();
266
- printHealthySummary(status);
267
- ensurePinnedDefaultModel(status);
268
- return;
269
- }
270
- const plan = buildRepairPlan(status);
271
- const rl = createInterface({
272
- input: process.stdin,
273
- output: process.stdout
274
- });
275
- try {
276
- if (plan.kind === "repair-missing-credentials") {
277
- console.log(formatMissingCredentialsMessage(plan));
278
- console.log(`
279
- Add ${plan.envVar} to your .env file to fix this.`);
280
- console.log("");
281
- console.log("Or switch to a different provider:\n");
282
- console.log(" 1) Switch to a different provider");
283
- console.log(" s) Skip for now\n");
284
- const answer = await promptUser(rl, "Choice: ");
285
- if (answer === "1") {
286
- await promptProviderSelection(rl);
287
- return;
288
- }
289
- printSkipMessage();
290
- return;
291
- }
292
- if (plan.kind === "repair-invalid-config") {
293
- printInvalidAiConfigWarning(status);
294
- console.log(
295
- "\nWould you like to reconfigure with a fresh provider selection?\n"
296
- );
297
- await promptProviderSelection(rl);
298
- return;
299
- }
300
- console.log("\u2717 No snapshot API credentials detected.\n");
301
- await promptProviderSelection(rl);
302
- } finally {
303
- rl.close();
304
- }
305
- }
306
12
  function installBrowsers() {
307
13
  console.log("Installing Playwright Chromium...");
308
14
  const result = spawnSync("npx", ["playwright", "install", "chromium"], {
@@ -341,7 +47,7 @@ function copySkills() {
341
47
  "\n\u26A0\uFE0F No .agents/ or .claude/ directory found. Libretto skills were not installed."
342
48
  );
343
49
  console.log(
344
- " Create one of these directories in your repo root and rerun `npx libretto setup` to install skills:"
50
+ ` Create one of these directories in your repo root and rerun \`${librettoCommand("setup")}\` to install skills:`
345
51
  );
346
52
  console.log(` mkdir ${join(REPO_ROOT, ".claude")}`);
347
53
  return;
@@ -389,24 +95,11 @@ const setupCommand = SimpleCLI.command({
389
95
  console.log("Skipping browser installation (--skip-browsers)");
390
96
  }
391
97
  copySkills();
392
- if (process.stdin.isTTY) {
393
- await runInteractiveApiSetup();
394
- } else {
395
- const ready = printSnapshotApiStatus();
396
- if (!ready) {
397
- console.log(
398
- "\nIf you're an agent, request the user to run `npx libretto setup`."
399
- );
400
- }
401
- }
402
98
  console.log(`
403
99
  Config set up at ${LIBRETTO_CONFIG_PATH}`);
404
100
  console.log("\n\u2713 libretto setup complete");
405
101
  });
406
102
  export {
407
- PROVIDER_CHOICES,
408
- buildRepairPlan,
409
- formatMissingCredentialsMessage,
410
103
  setupCommand,
411
104
  setupInput
412
105
  };
@@ -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
  };
@@ -1,13 +1,14 @@
1
- import { readFileSync, writeFileSync } from "node:fs";
2
1
  import { z } from "zod";
3
- import { condenseDom } from "../../shared/condense-dom/condense-dom.js";
4
2
  import { readSessionState } from "../core/session.js";
5
3
  import { SimpleCLI } from "../framework/simple-cli.js";
6
- import { pageOption, sessionOption, withRequiredSession } from "./shared.js";
7
- import { runApiInterpret } from "../core/api-snapshot-analyzer.js";
8
- import { readSnapshotModel } from "../core/config.js";
9
- import { resolveSnapshotApiModelOrThrow } from "../core/ai-model.js";
10
- import { DaemonClient } from "../core/daemon/index.js";
4
+ import {
5
+ pageOption,
6
+ sessionOption,
7
+ withRequiredSession
8
+ } from "./shared.js";
9
+ import { DaemonClient } from "../core/daemon/ipc.js";
10
+ import { librettoCommand } from "../../shared/package-manager.js";
11
+ import { renderSnapshot } from "../../shared/snapshot/render-snapshot.js";
11
12
  const FALLBACK_SNAPSHOT_VIEWPORT = { width: 1280, height: 800 };
12
13
  function isZeroViewport(value) {
13
14
  return typeof value === "number" && value <= 0;
@@ -63,75 +64,56 @@ async function forceSnapshotViewport(page, viewport, logger, session, pageId, re
63
64
  viewport
64
65
  });
65
66
  }
66
- async function captureSnapshot(session, logger, daemonSocketPath, pageId) {
67
- logger.info("snapshot-via-daemon", { session, pageId });
68
- const client = new DaemonClient(daemonSocketPath);
69
- const { pngPath, htmlPath, snapshotRunId, pageUrl, title } = await client.snapshot({ pageId });
70
- const htmlContent = readFileSync(htmlPath, "utf8");
71
- const condenseResult = condenseDom(htmlContent);
72
- const condensedHtmlPath = htmlPath.replace(/\.html$/, ".condensed.html");
73
- writeFileSync(condensedHtmlPath, condenseResult.html);
74
- logger.info("snapshot-daemon-success", {
75
- session,
76
- pageUrl,
77
- title,
78
- pngPath,
79
- htmlPath,
80
- condensedHtmlPath,
81
- snapshotRunId,
82
- domCondenseStats: {
83
- originalLength: condenseResult.originalLength,
84
- condensedLength: condenseResult.condensedLength,
85
- reductions: condenseResult.reductions
86
- }
87
- });
88
- return { pngPath, htmlPath, condensedHtmlPath, baseName: snapshotRunId };
89
- }
90
- async function runSnapshot(session, logger, pageId, objective, context) {
91
- const normalizedObjective = objective.trim();
92
- const normalizedContext = context.trim();
93
- const snapshotModel = readSnapshotModel();
94
- resolveSnapshotApiModelOrThrow(snapshotModel);
95
- const state = readSessionState(session, logger);
96
- if (!state?.daemonSocketPath) {
67
+ async function runCompactSnapshot(args) {
68
+ if (!args.daemonSocketPath) {
97
69
  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}`
70
+ `Session "${args.session}" has no daemon socket. The browser daemon may have crashed. Close and reopen the session: ${librettoCommand(`close --session ${args.session}`)}`
99
71
  );
100
72
  }
101
- const { pngPath, htmlPath, condensedHtmlPath } = await captureSnapshot(session, logger, state.daemonSocketPath, pageId);
102
- console.log("Screenshot saved:");
103
- console.log(` PNG: ${pngPath}`);
104
- console.log(` HTML: ${htmlPath}`);
105
- console.log(` Condensed HTML: ${condensedHtmlPath}`);
106
- const interpretArgs = {
107
- objective: normalizedObjective,
108
- session,
109
- context: normalizedContext,
110
- pngPath,
111
- htmlPath,
112
- condensedHtmlPath
113
- };
114
- await runApiInterpret(interpretArgs, logger, snapshotModel);
73
+ args.logger.info("compact-snapshot-via-daemon", {
74
+ session: args.session,
75
+ pageId: args.pageId,
76
+ ref: args.ref
77
+ });
78
+ const client = await DaemonClient.connect(args.daemonSocketPath);
79
+ let result;
80
+ try {
81
+ result = await client.snapshot({
82
+ pageId: args.pageId,
83
+ useCachedSnapshot: args.ref !== void 0
84
+ });
85
+ } finally {
86
+ client.destroy();
87
+ }
88
+ console.log(`Screenshot at ${result.pngPath}`);
89
+ console.log(renderSnapshot(result.snapshot, args.ref));
90
+ console.log(
91
+ `Hint: Use ${librettoCommand(`snapshot <ref> --session ${args.session}`)} to inspect a subtree.`
92
+ );
115
93
  }
116
94
  const snapshotInput = SimpleCLI.input({
117
- positionals: [],
95
+ positionals: [
96
+ SimpleCLI.positional("ref", z.string().optional(), {
97
+ help: "Optional element ref to scope output to that subtree (for example, l16 or e16)"
98
+ })
99
+ ],
118
100
  named: {
119
101
  session: sessionOption(),
120
102
  page: pageOption(),
121
- objective: SimpleCLI.option(z.string()),
122
- context: SimpleCLI.option(z.string())
103
+ objective: SimpleCLI.option(z.string().optional()),
104
+ context: SimpleCLI.option(z.string().optional())
123
105
  }
124
106
  });
125
107
  const snapshotCommand = SimpleCLI.command({
126
- description: "Capture PNG + HTML and analyze with --objective and --context"
108
+ description: "Capture a screenshot and compact accessibility snapshot"
127
109
  }).input(snapshotInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
128
- await runSnapshot(
129
- ctx.session,
130
- ctx.logger,
131
- input.page,
132
- input.objective,
133
- input.context
134
- );
110
+ await runCompactSnapshot({
111
+ session: ctx.session,
112
+ daemonSocketPath: ctx.sessionState.daemonSocketPath,
113
+ logger: ctx.logger,
114
+ pageId: input.page,
115
+ ref: input.ref
116
+ });
135
117
  });
136
118
  export {
137
119
  FALLBACK_SNAPSHOT_VIEWPORT,
@@ -1,42 +1,5 @@
1
- import { LIBRETTO_CONFIG_PATH } from "../core/context.js";
2
- import { resolveAiSetupStatus } from "../core/ai-model.js";
3
1
  import { listRunningSessions } from "../core/session.js";
4
2
  import { SimpleCLI } from "../framework/simple-cli.js";
5
- function printAiStatus(status) {
6
- console.log("AI configuration:");
7
- switch (status.kind) {
8
- case "ready":
9
- console.log(` \u2713 Snapshot model: ${status.model}`);
10
- if (status.source === "config") {
11
- console.log(` Config: ${LIBRETTO_CONFIG_PATH}`);
12
- } else {
13
- console.log(` Source: ${status.source}`);
14
- }
15
- console.log(
16
- " To change: npx libretto ai configure openai | anthropic | gemini | vertex | openrouter"
17
- );
18
- break;
19
- case "configured-missing-credentials":
20
- console.log(
21
- ` \u2717 ${status.provider} is configured (model: ${status.model}), but credentials are missing.`
22
- );
23
- console.log(" Run `npx libretto setup` to repair.");
24
- break;
25
- case "invalid-config":
26
- console.log(" \u2717 Config is invalid:");
27
- for (const line of status.message.split("\n")) {
28
- console.log(` ${line}`);
29
- }
30
- console.log(" Run `npx libretto setup` to reconfigure.");
31
- break;
32
- case "unconfigured":
33
- console.log(" \u2717 No AI model configured.");
34
- console.log(
35
- " Run `npx libretto setup` or `npx libretto ai configure` to set up."
36
- );
37
- break;
38
- }
39
- }
40
3
  function printOpenSessions(sessions) {
41
4
  console.log("\nOpen sessions:");
42
5
  if (sessions.length === 0) {
@@ -50,10 +13,8 @@ function printOpenSessions(sessions) {
50
13
  }
51
14
  }
52
15
  const statusCommand = SimpleCLI.command({
53
- description: "Show workspace status: AI configuration and open sessions"
16
+ description: "Show workspace status and open sessions"
54
17
  }).input(SimpleCLI.input({ positionals: [], named: {} })).handle(async () => {
55
- const aiStatus = resolveAiSetupStatus();
56
- printAiStatus(aiStatus);
57
18
  const sessions = listRunningSessions();
58
19
  printOpenSessions(sessions);
59
20
  });