libretto 0.6.12 → 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 (46) hide show
  1. package/README.md +3 -8
  2. package/README.template.md +3 -8
  3. package/dist/cli/cli.js +0 -23
  4. package/dist/cli/commands/browser.js +1 -7
  5. package/dist/cli/commands/setup.js +1 -294
  6. package/dist/cli/commands/snapshot.js +9 -99
  7. package/dist/cli/commands/status.js +1 -41
  8. package/dist/cli/core/browser.js +3 -3
  9. package/dist/cli/core/config.js +3 -6
  10. package/dist/cli/core/daemon/daemon.js +25 -64
  11. package/dist/cli/core/daemon/snapshot.js +2 -29
  12. package/dist/cli/core/experiments.js +1 -28
  13. package/dist/cli/index.js +0 -2
  14. package/dist/cli/router.js +0 -2
  15. package/dist/shared/instrumentation/instrument.js +4 -4
  16. package/docs/releasing.md +8 -6
  17. package/package.json +2 -1
  18. package/skills/libretto/SKILL.md +17 -19
  19. package/skills/libretto/references/configuration-file-reference.md +6 -12
  20. package/skills/libretto/references/pages-and-page-targeting.md +1 -1
  21. package/skills/libretto-readonly/SKILL.md +2 -9
  22. package/src/cli/cli.ts +0 -24
  23. package/src/cli/commands/browser.ts +1 -7
  24. package/src/cli/commands/setup.ts +1 -380
  25. package/src/cli/commands/snapshot.ts +8 -136
  26. package/src/cli/commands/status.ts +1 -49
  27. package/src/cli/core/browser.ts +3 -3
  28. package/src/cli/core/config.ts +3 -6
  29. package/src/cli/core/daemon/daemon.ts +25 -67
  30. package/src/cli/core/daemon/ipc.ts +5 -16
  31. package/src/cli/core/daemon/snapshot.ts +1 -43
  32. package/src/cli/core/experiments.ts +9 -38
  33. package/src/cli/core/resolve-model.ts +5 -0
  34. package/src/cli/core/workflow-runtime.ts +1 -0
  35. package/src/cli/index.ts +0 -1
  36. package/src/cli/router.ts +0 -2
  37. package/src/shared/instrumentation/instrument.ts +4 -4
  38. package/dist/cli/commands/ai.js +0 -110
  39. package/dist/cli/core/ai-model.js +0 -195
  40. package/dist/cli/core/api-snapshot-analyzer.js +0 -86
  41. package/dist/cli/core/snapshot-analyzer.js +0 -667
  42. package/scripts/summarize-evals.mjs +0 -135
  43. package/src/cli/commands/ai.ts +0 -144
  44. package/src/cli/core/ai-model.ts +0 -301
  45. package/src/cli/core/api-snapshot-analyzer.ts +0 -110
  46. package/src/cli/core/snapshot-analyzer.ts +0 -856
package/README.md CHANGED
@@ -30,19 +30,14 @@ https://github.com/user-attachments/assets/9b9a0ab3-5133-4b20-b3be-459943349d18
30
30
  ```bash
31
31
  npm install libretto
32
32
 
33
- # First-time onboarding: install skill, download Chromium, and pin the default snapshot model
33
+ # First-time onboarding: install skills and download Chromium
34
34
  npx libretto setup
35
35
 
36
36
  # Check workspace readiness at any time
37
37
  npx libretto status
38
-
39
- # Manually change the snapshot analysis model (advanced override)
40
- npx libretto ai configure <openai | anthropic | gemini | vertex>
41
38
  ```
42
39
 
43
- `setup` detects available provider credentials (e.g. `OPENAI_API_KEY`) and automatically pins the default model to `.libretto/config.json`. Re-running `setup` on a healthy workspace shows the current configuration instead of re-prompting. If credentials are missing for a previously configured provider, `setup` offers an interactive repair flow.
44
-
45
- Use `ai configure` when you want to explicitly switch providers or set a custom model string.
40
+ `setup` creates the `.libretto/` directory, installs agent skills, and downloads Chromium unless you pass `--skip-browsers`.
46
41
 
47
42
  ## Use cases
48
43
 
@@ -80,7 +75,7 @@ You can also use Libretto directly from the command line. All commands accept `-
80
75
  npx libretto open <url> # launch browser and open a URL
81
76
  npx libretto run ./integration.ts --headless # run a workflow and close on success
82
77
  npx libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
83
- npx libretto snapshot --objective "..." # capture PNG + HTML and analyze with an LLM
78
+ npx libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
84
79
  npx libretto exec "<code>" # execute Playwright TypeScript against the open page
85
80
  npx libretto close # close the browser
86
81
  ```
@@ -28,19 +28,14 @@ https://github.com/user-attachments/assets/9b9a0ab3-5133-4b20-b3be-459943349d18
28
28
  ```bash
29
29
  npm install libretto
30
30
 
31
- # First-time onboarding: install skill, download Chromium, and pin the default snapshot model
31
+ # First-time onboarding: install skills and download Chromium
32
32
  npx libretto setup
33
33
 
34
34
  # Check workspace readiness at any time
35
35
  npx libretto status
36
-
37
- # Manually change the snapshot analysis model (advanced override)
38
- npx libretto ai configure <openai | anthropic | gemini | vertex>
39
36
  ```
40
37
 
41
- `setup` detects available provider credentials (e.g. `OPENAI_API_KEY`) and automatically pins the default model to `.libretto/config.json`. Re-running `setup` on a healthy workspace shows the current configuration instead of re-prompting. If credentials are missing for a previously configured provider, `setup` offers an interactive repair flow.
42
-
43
- Use `ai configure` when you want to explicitly switch providers or set a custom model string.
38
+ `setup` creates the `.libretto/` directory, installs agent skills, and downloads Chromium unless you pass `--skip-browsers`.
44
39
 
45
40
  ## Use cases
46
41
 
@@ -78,7 +73,7 @@ You can also use Libretto directly from the command line. All commands accept `-
78
73
  npx libretto open <url> # launch browser and open a URL
79
74
  npx libretto run ./integration.ts --headless # run a workflow and close on success
80
75
  npx libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
81
- npx libretto snapshot --objective "..." # capture PNG + HTML and analyze with an LLM
76
+ npx libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
82
77
  npx libretto exec "<code>" # execute Playwright TypeScript against the open page
83
78
  npx libretto close # close the browser
84
79
  ```
package/dist/cli/cli.js CHANGED
@@ -1,6 +1,4 @@
1
- import { resolveAiSetupStatus } from "./core/ai-model.js";
2
1
  import { ensureLibrettoSetup } from "./core/context.js";
3
- import { librettoCommand } from "../shared/package-manager.js";
4
2
  import { createCLIApp } from "./router.js";
5
3
  import { warnIfInstalledSkillOutOfDate } from "./core/skill-version.js";
6
4
  import { loadEnv } from "../shared/env/load-env.js";
@@ -15,27 +13,6 @@ Docs (agent-friendly): https://libretto.sh/docs
15
13
  }
16
14
  function printSetupAudit() {
17
15
  warnIfInstalledSkillOutOfDate();
18
- const status = resolveAiSetupStatus();
19
- switch (status.kind) {
20
- case "ready":
21
- console.log(`\u2713 Snapshot model: ${status.model}`);
22
- break;
23
- case "configured-missing-credentials":
24
- console.log(
25
- `\u2717 ${status.provider} configured (model: ${status.model}), but credentials are missing. Run \`${librettoCommand("setup")}\` to repair.`
26
- );
27
- break;
28
- case "invalid-config":
29
- console.log(
30
- `\u2717 AI config is invalid. Run \`${librettoCommand("setup")}\` to reconfigure.`
31
- );
32
- break;
33
- case "unconfigured":
34
- console.log(
35
- `\u2717 No AI model configured. Run \`${librettoCommand("setup")}\` or \`${librettoCommand("ai configure")}\` to set up.`
36
- );
37
- break;
38
- }
39
16
  }
40
17
  function isRootHelpRequest(rawArgs) {
41
18
  if (rawArgs.length === 0) return true;
@@ -11,7 +11,7 @@ import {
11
11
  } from "../core/browser.js";
12
12
  import { resolveProviderName } from "../core/providers/index.js";
13
13
  import { readLibrettoConfig } from "../core/config.js";
14
- import { createLoggerForSession, withSessionLogger } from "../core/context.js";
14
+ import { createLoggerForSession } from "../core/context.js";
15
15
  import { librettoCommand } from "../../shared/package-manager.js";
16
16
  import {
17
17
  assertSessionAvailableForStart,
@@ -246,11 +246,6 @@ const browserCommands = {
246
246
  "session-mode": sessionModeCommand,
247
247
  close: closeCommand
248
248
  };
249
- async function runClose(session) {
250
- await withSessionLogger(session, async (logger) => {
251
- await runCloseWithLogger(session, logger);
252
- });
253
- }
254
249
  export {
255
250
  browserCommands,
256
251
  closeCommand,
@@ -262,7 +257,6 @@ export {
262
257
  pagesCommand,
263
258
  pagesInput,
264
259
  parseViewportArg,
265
- runClose,
266
260
  saveCommand,
267
261
  saveInput,
268
262
  sessionModeCommand,
@@ -1,293 +1,14 @@
1
- import { createInterface } from "node:readline";
2
1
  import { cpSync, existsSync, readdirSync, rmSync } from "node:fs";
3
2
  import { spawnSync } from "node:child_process";
4
3
  import { basename, dirname, join } from "node:path";
5
4
  import { fileURLToPath } from "node:url";
6
- import { writeSnapshotModel } from "../core/config.js";
7
5
  import {
8
6
  ensureLibrettoSetup,
9
7
  LIBRETTO_CONFIG_PATH,
10
8
  REPO_ROOT
11
9
  } from "../core/context.js";
12
- import {
13
- DEFAULT_SNAPSHOT_MODELS,
14
- resolveAiSetupStatus
15
- } from "../core/ai-model.js";
16
- import {
17
- detectProjectPackageManager,
18
- installCommand,
19
- librettoCommand
20
- } from "../../shared/package-manager.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 isSdkInstalled(sdkPackage) {
30
- try {
31
- const result = spawnSync("node", ["-e", `require.resolve("${sdkPackage}")`], {
32
- cwd: REPO_ROOT,
33
- stdio: "pipe"
34
- });
35
- return result.status === 0;
36
- } catch {
37
- return false;
38
- }
39
- }
40
- function installSdkIfNeeded(provider) {
41
- const sdkPackage = PROVIDER_SDK_PACKAGES[provider];
42
- if (isSdkInstalled(sdkPackage)) return;
43
- const pkgManager = detectProjectPackageManager();
44
- const cmd = installCommand(pkgManager);
45
- console.log(`
46
- Installing ${sdkPackage}...`);
47
- const result = spawnSync(cmd, [sdkPackage], {
48
- cwd: REPO_ROOT,
49
- stdio: "inherit",
50
- shell: true
51
- });
52
- if (result.status === 0) {
53
- console.log(`\u2713 Installed ${sdkPackage}`);
54
- } else {
55
- console.error(
56
- `\u2717 Failed to install ${sdkPackage}. Install it manually: ${cmd} ${sdkPackage}`
57
- );
58
- }
59
- }
60
- const PROVIDER_CHOICES = [
61
- {
62
- key: "1",
63
- label: "OpenAI",
64
- provider: "openai",
65
- envVar: "OPENAI_API_KEY",
66
- envHint: "Get your key at https://platform.openai.com/api-keys"
67
- },
68
- {
69
- key: "2",
70
- label: "Anthropic",
71
- provider: "anthropic",
72
- envVar: "ANTHROPIC_API_KEY",
73
- envHint: "Get your key at https://console.anthropic.com/settings/keys"
74
- },
75
- {
76
- key: "3",
77
- label: "Google Gemini",
78
- provider: "google",
79
- envVar: "GEMINI_API_KEY",
80
- envHint: "Get your key at https://aistudio.google.com/apikey"
81
- },
82
- {
83
- key: "4",
84
- label: "Google Vertex AI",
85
- provider: "vertex",
86
- envVar: "GOOGLE_CLOUD_PROJECT",
87
- envHint: "Requires `gcloud auth application-default login` and a GCP project ID"
88
- },
89
- {
90
- key: "5",
91
- label: "OpenRouter",
92
- provider: "openrouter",
93
- envVar: "OPENROUTER_API_KEY",
94
- envHint: "Get your key at https://openrouter.ai/settings/keys"
95
- }
96
- ];
97
- function promptUser(rl, question) {
98
- return new Promise((resolve) => {
99
- rl.question(question, (answer) => {
100
- resolve(answer.trim());
101
- });
102
- });
103
- }
104
- function providerLabel(provider) {
105
- const choice = PROVIDER_CHOICES.find((c) => c.provider === provider);
106
- return choice?.label ?? provider;
107
- }
108
- function sourceEnvVar(source) {
109
- if (source.startsWith("env:")) return source.slice(4);
110
- return null;
111
- }
112
- function ensurePinnedDefaultModel(status) {
113
- if (status.source !== "config") {
114
- writeSnapshotModel(status.model);
115
- return { ...status, source: "config" };
116
- }
117
- return status;
118
- }
119
- function printHealthySummary(status) {
120
- const envVar = sourceEnvVar(status.source);
121
- if (envVar) {
122
- console.log(
123
- `\u2713 Detected ${envVar}. Using ${providerLabel(status.provider)}.`
124
- );
125
- } else {
126
- console.log(`\u2713 Using ${providerLabel(status.provider)} (${status.model}).`);
127
- }
128
- console.log(
129
- `To change: ${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}`
130
- );
131
- }
132
- function printInvalidAiConfigWarning(status) {
133
- if (status.kind !== "invalid-config") return;
134
- console.log("! Existing AI config is invalid:");
135
- for (const line of status.message.split("\n")) {
136
- console.log(` ${line}`);
137
- }
138
- }
139
- function buildRepairPlan(status) {
140
- if (status.kind === "configured-missing-credentials") {
141
- const choice = PROVIDER_CHOICES.find((c) => c.provider === status.provider);
142
- return {
143
- kind: "repair-missing-credentials",
144
- provider: status.provider,
145
- model: status.model,
146
- envVar: choice?.envVar ?? `${status.provider.toUpperCase()}_API_KEY`,
147
- choices: ["switch-provider", "skip"]
148
- };
149
- }
150
- if (status.kind === "invalid-config") {
151
- return { kind: "repair-invalid-config", message: status.message };
152
- }
153
- return { kind: "no-repair-needed" };
154
- }
155
- function formatMissingCredentialsMessage(plan) {
156
- return `\u2717 ${plan.provider} is configured (model: ${plan.model}), but ${plan.envVar} is not set.`;
157
- }
158
- function printSnapshotApiStatus() {
159
- const status = resolveAiSetupStatus();
160
- console.log(
161
- "\nLibretto uses a sub-agent to analyze DOM snapshots. The model is determined by environment variables."
162
- );
163
- if (status.kind === "ready") {
164
- console.log();
165
- printHealthySummary(status);
166
- ensurePinnedDefaultModel(status);
167
- return true;
168
- }
169
- const plan = buildRepairPlan(status);
170
- if (plan.kind === "repair-missing-credentials") {
171
- console.log();
172
- console.log(formatMissingCredentialsMessage(plan));
173
- console.log(
174
- ` To fix: add ${plan.envVar} to .env, or run \`${librettoCommand("setup")}\` interactively to repair.`
175
- );
176
- return false;
177
- }
178
- if (plan.kind === "repair-invalid-config") {
179
- printInvalidAiConfigWarning(status);
180
- console.log(
181
- ` Run \`${librettoCommand("setup")}\` interactively to reconfigure.`
182
- );
183
- return false;
184
- }
185
- console.log();
186
- console.log("\u2717 No snapshot API credentials detected.");
187
- console.log(" Add one provider to .env:");
188
- console.log(" OPENAI_API_KEY=...");
189
- console.log(" ANTHROPIC_API_KEY=...");
190
- console.log(" GEMINI_API_KEY=... # or GOOGLE_GENERATIVE_AI_API_KEY");
191
- console.log(
192
- " GOOGLE_CLOUD_PROJECT=... # plus application default credentials for Vertex"
193
- );
194
- console.log(
195
- ` Or run \`${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}\` to set a specific model.`
196
- );
197
- console.log(
198
- ` Run \`${librettoCommand("setup")}\` interactively to set up credentials.`
199
- );
200
- return false;
201
- }
202
- async function promptProviderSelection(rl) {
203
- console.log(
204
- "Which model provider would you like to use for snapshot analysis?\n"
205
- );
206
- for (const choice of PROVIDER_CHOICES) {
207
- console.log(` ${choice.key}) ${choice.label}`);
208
- }
209
- console.log(" s) Skip for now\n");
210
- const answer = await promptUser(rl, "Choice: ");
211
- if (answer.toLowerCase() === "s" || !answer) {
212
- printSkipMessage();
213
- return false;
214
- }
215
- const selected = PROVIDER_CHOICES.find((choice) => choice.key === answer);
216
- if (!selected) {
217
- console.log(`
218
- Unknown choice "${answer}". Skipping API setup.`);
219
- return false;
220
- }
221
- const model = DEFAULT_SNAPSHOT_MODELS[selected.provider];
222
- writeSnapshotModel(model);
223
- console.log(`
224
- \u2713 ${selected.label} selected (model: ${model}).`);
225
- console.log(`
226
- Add ${selected.envVar} to your .env file:`);
227
- console.log(` ${selected.envHint}`);
228
- installSdkIfNeeded(selected.provider);
229
- return true;
230
- }
231
- function printSkipMessage() {
232
- console.log(
233
- `
234
- Skipped. You can set up API credentials later by rerunning \`${librettoCommand("setup")}\`.`
235
- );
236
- console.log("Or add credentials directly to your .env file:");
237
- console.log(" OPENAI_API_KEY=...");
238
- console.log(" ANTHROPIC_API_KEY=...");
239
- console.log(" GEMINI_API_KEY=...");
240
- console.log(
241
- ` Or run \`${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}\` to set a specific model.`
242
- );
243
- }
244
- async function runInteractiveApiSetup() {
245
- const status = resolveAiSetupStatus();
246
- console.log(
247
- "\nLibretto uses a sub-agent to analyze DOM snapshots. The model is determined by environment variables."
248
- );
249
- if (status.kind === "ready") {
250
- console.log();
251
- printHealthySummary(status);
252
- ensurePinnedDefaultModel(status);
253
- return;
254
- }
255
- const plan = buildRepairPlan(status);
256
- const rl = createInterface({
257
- input: process.stdin,
258
- output: process.stdout
259
- });
260
- try {
261
- if (plan.kind === "repair-missing-credentials") {
262
- console.log(formatMissingCredentialsMessage(plan));
263
- console.log(`
264
- Add ${plan.envVar} to your .env file to fix this.`);
265
- console.log("");
266
- console.log("Or switch to a different provider:\n");
267
- console.log(" 1) Switch to a different provider");
268
- console.log(" s) Skip for now\n");
269
- const answer = await promptUser(rl, "Choice: ");
270
- if (answer === "1") {
271
- await promptProviderSelection(rl);
272
- return;
273
- }
274
- printSkipMessage();
275
- return;
276
- }
277
- if (plan.kind === "repair-invalid-config") {
278
- printInvalidAiConfigWarning(status);
279
- console.log(
280
- "\nWould you like to reconfigure with a fresh provider selection?\n"
281
- );
282
- await promptProviderSelection(rl);
283
- return;
284
- }
285
- console.log("\u2717 No snapshot API credentials detected.\n");
286
- await promptProviderSelection(rl);
287
- } finally {
288
- rl.close();
289
- }
290
- }
291
12
  function installBrowsers() {
292
13
  console.log("Installing Playwright Chromium...");
293
14
  const result = spawnSync("npx", ["playwright", "install", "chromium"], {
@@ -374,25 +95,11 @@ const setupCommand = SimpleCLI.command({
374
95
  console.log("Skipping browser installation (--skip-browsers)");
375
96
  }
376
97
  copySkills();
377
- if (process.stdin.isTTY) {
378
- await runInteractiveApiSetup();
379
- } else {
380
- const ready = printSnapshotApiStatus();
381
- if (!ready) {
382
- console.log(
383
- `
384
- If you're an agent, request the user to run \`${librettoCommand("setup")}\`.`
385
- );
386
- }
387
- }
388
98
  console.log(`
389
99
  Config set up at ${LIBRETTO_CONFIG_PATH}`);
390
100
  console.log("\n\u2713 libretto setup complete");
391
101
  });
392
102
  export {
393
- PROVIDER_CHOICES,
394
- buildRepairPlan,
395
- formatMissingCredentialsMessage,
396
103
  setupCommand,
397
104
  setupInput
398
105
  };
@@ -1,17 +1,11 @@
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
4
  import {
7
5
  pageOption,
8
6
  sessionOption,
9
- withExperiments,
10
7
  withRequiredSession
11
8
  } from "./shared.js";
12
- import { runApiInterpret } from "../core/api-snapshot-analyzer.js";
13
- import { readSnapshotModel } from "../core/config.js";
14
- import { resolveSnapshotApiModelOrThrow } from "../core/ai-model.js";
15
9
  import { DaemonClient } from "../core/daemon/ipc.js";
16
10
  import { librettoCommand } from "../../shared/package-manager.js";
17
11
  import { renderSnapshot } from "../../shared/snapshot/render-snapshot.js";
@@ -70,71 +64,6 @@ async function forceSnapshotViewport(page, viewport, logger, session, pageId, re
70
64
  viewport
71
65
  });
72
66
  }
73
- async function captureSnapshot(session, logger, daemonSocketPath, pageId) {
74
- logger.info("snapshot-via-daemon", { session, 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;
86
- const htmlContent = readFileSync(htmlPath, "utf8");
87
- const condenseResult = condenseDom(htmlContent);
88
- const condensedHtmlPath = htmlPath.replace(/\.html$/, ".condensed.html");
89
- writeFileSync(condensedHtmlPath, condenseResult.html);
90
- logger.info("snapshot-daemon-success", {
91
- session,
92
- pageUrl,
93
- title,
94
- pngPath,
95
- htmlPath,
96
- condensedHtmlPath,
97
- snapshotRunId,
98
- domCondenseStats: {
99
- originalLength: condenseResult.originalLength,
100
- condensedLength: condenseResult.condensedLength,
101
- reductions: condenseResult.reductions
102
- }
103
- });
104
- return { pngPath, htmlPath, condensedHtmlPath, baseName: snapshotRunId };
105
- }
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
- }
113
- const normalizedObjective = objective.trim();
114
- const normalizedContext = context.trim();
115
- const snapshotModel = readSnapshotModel();
116
- resolveSnapshotApiModelOrThrow(snapshotModel);
117
- const state = readSessionState(session, logger);
118
- if (!state?.daemonSocketPath) {
119
- throw new Error(
120
- `Session "${session}" has no daemon socket. The browser daemon may have crashed. Close and reopen the session: ${librettoCommand(`close --session ${session}`)}`
121
- );
122
- }
123
- const { pngPath, htmlPath, condensedHtmlPath } = await captureSnapshot(session, logger, state.daemonSocketPath, pageId);
124
- console.log("Screenshot saved:");
125
- console.log(` PNG: ${pngPath}`);
126
- console.log(` HTML: ${htmlPath}`);
127
- console.log(` Condensed HTML: ${condensedHtmlPath}`);
128
- const interpretArgs = {
129
- objective: normalizedObjective,
130
- session,
131
- context: normalizedContext,
132
- pngPath,
133
- htmlPath,
134
- condensedHtmlPath
135
- };
136
- await runApiInterpret(interpretArgs, logger, snapshotModel);
137
- }
138
67
  async function runCompactSnapshot(args) {
139
68
  if (!args.daemonSocketPath) {
140
69
  throw new Error(
@@ -150,16 +79,12 @@ async function runCompactSnapshot(args) {
150
79
  let result;
151
80
  try {
152
81
  result = await client.snapshot({
153
- mode: "compact",
154
82
  pageId: args.pageId,
155
83
  useCachedSnapshot: args.ref !== void 0
156
84
  });
157
85
  } finally {
158
86
  client.destroy();
159
87
  }
160
- if (!("mode" in result) || result.mode !== "compact") {
161
- throw new Error("Daemon returned a legacy snapshot for a compact request.");
162
- }
163
88
  console.log(`Screenshot at ${result.pngPath}`);
164
89
  console.log(renderSnapshot(result.snapshot, args.ref));
165
90
  console.log(
@@ -180,30 +105,15 @@ const snapshotInput = SimpleCLI.input({
180
105
  }
181
106
  });
182
107
  const snapshotCommand = SimpleCLI.command({
183
- description: "Capture PNG + HTML and analyze with --objective and --context"
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
- }
200
- await runSnapshot(
201
- ctx.session,
202
- ctx.logger,
203
- input.page,
204
- input.objective,
205
- input.context
206
- );
108
+ description: "Capture a screenshot and compact accessibility snapshot"
109
+ }).input(snapshotInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
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
+ });
207
117
  });
208
118
  export {
209
119
  FALLBACK_SNAPSHOT_VIEWPORT,
@@ -1,43 +1,5 @@
1
- import { LIBRETTO_CONFIG_PATH } from "../core/context.js";
2
- import { resolveAiSetupStatus } from "../core/ai-model.js";
3
- import { librettoCommand } from "../../shared/package-manager.js";
4
1
  import { listRunningSessions } from "../core/session.js";
5
2
  import { SimpleCLI } from "../framework/simple-cli.js";
6
- function printAiStatus(status) {
7
- console.log("AI configuration:");
8
- switch (status.kind) {
9
- case "ready":
10
- console.log(` \u2713 Snapshot model: ${status.model}`);
11
- if (status.source === "config") {
12
- console.log(` Config: ${LIBRETTO_CONFIG_PATH}`);
13
- } else {
14
- console.log(` Source: ${status.source}`);
15
- }
16
- console.log(
17
- ` To change: ${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}`
18
- );
19
- break;
20
- case "configured-missing-credentials":
21
- console.log(
22
- ` \u2717 ${status.provider} is configured (model: ${status.model}), but credentials are missing.`
23
- );
24
- console.log(` Run \`${librettoCommand("setup")}\` to repair.`);
25
- break;
26
- case "invalid-config":
27
- console.log(" \u2717 Config is invalid:");
28
- for (const line of status.message.split("\n")) {
29
- console.log(` ${line}`);
30
- }
31
- console.log(` Run \`${librettoCommand("setup")}\` to reconfigure.`);
32
- break;
33
- case "unconfigured":
34
- console.log(" \u2717 No AI model configured.");
35
- console.log(
36
- ` Run \`${librettoCommand("setup")}\` or \`${librettoCommand("ai configure")}\` to set up.`
37
- );
38
- break;
39
- }
40
- }
41
3
  function printOpenSessions(sessions) {
42
4
  console.log("\nOpen sessions:");
43
5
  if (sessions.length === 0) {
@@ -51,10 +13,8 @@ function printOpenSessions(sessions) {
51
13
  }
52
14
  }
53
15
  const statusCommand = SimpleCLI.command({
54
- description: "Show workspace status: AI configuration and open sessions"
16
+ description: "Show workspace status and open sessions"
55
17
  }).input(SimpleCLI.input({ positionals: [], named: {} })).handle(async () => {
56
- const aiStatus = resolveAiSetupStatus();
57
- printAiStatus(aiStatus);
58
18
  const sessions = listRunningSessions();
59
19
  printOpenSessions(sessions);
60
20
  });
@@ -2,6 +2,7 @@ import {
2
2
  chromium
3
3
  } from "playwright";
4
4
  import { existsSync, readFileSync, unlinkSync } from "node:fs";
5
+ import { mkdir, writeFile } from "node:fs/promises";
5
6
  import { dirname, join } from "node:path";
6
7
  import { createServer } from "node:net";
7
8
  import { getSessionProviderClosePath, PROFILES_DIR } from "./context.js";
@@ -485,9 +486,8 @@ async function runSave(urlOrDomain, session, logger) {
485
486
  }
486
487
  }
487
488
  const state = { cookies, origins };
488
- const fs = await import("node:fs/promises");
489
- await fs.mkdir(dirname(profilePath), { recursive: true });
490
- await fs.writeFile(profilePath, JSON.stringify(state, null, 2));
489
+ await mkdir(dirname(profilePath), { recursive: true });
490
+ await writeFile(profilePath, JSON.stringify(state, null, 2));
491
491
  logger.info("save-success", {
492
492
  domain,
493
493
  profilePath,