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
@@ -1,383 +1,15 @@
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
- type AiSetupStatus,
14
- DEFAULT_SNAPSHOT_MODELS,
15
- resolveAiSetupStatus,
16
- } from "../core/ai-model.js";
17
- import {
18
- detectProjectPackageManager,
19
- installCommand,
20
- librettoCommand,
21
- } from "../../shared/package-manager.js";
22
- import type { Provider } from "../core/resolve-model.js";
10
+ import { librettoCommand } from "../../shared/package-manager.js";
23
11
  import { SimpleCLI } from "../framework/simple-cli.js";
24
12
 
25
- const PROVIDER_SDK_PACKAGES: Record<Provider, string> = {
26
- openai: "@ai-sdk/openai",
27
- anthropic: "@ai-sdk/anthropic",
28
- google: "@ai-sdk/google",
29
- vertex: "@ai-sdk/google-vertex",
30
- openrouter: "@ai-sdk/openai",
31
- };
32
-
33
- function isSdkInstalled(sdkPackage: string): boolean {
34
- try {
35
- const result = spawnSync("node", ["-e", `require.resolve("${sdkPackage}")`], {
36
- cwd: REPO_ROOT,
37
- stdio: "pipe",
38
- });
39
- return result.status === 0;
40
- } catch {
41
- return false;
42
- }
43
- }
44
-
45
- function installSdkIfNeeded(provider: Provider): void {
46
- const sdkPackage = PROVIDER_SDK_PACKAGES[provider];
47
- if (isSdkInstalled(sdkPackage)) return;
48
-
49
- const pkgManager = detectProjectPackageManager();
50
- const cmd = installCommand(pkgManager);
51
- console.log(`\nInstalling ${sdkPackage}...`);
52
- const result = spawnSync(cmd, [sdkPackage], {
53
- cwd: REPO_ROOT,
54
- stdio: "inherit",
55
- shell: true,
56
- });
57
- if (result.status === 0) {
58
- console.log(`✓ Installed ${sdkPackage}`);
59
- } else {
60
- console.error(
61
- `✗ Failed to install ${sdkPackage}. Install it manually: ${cmd} ${sdkPackage}`,
62
- );
63
- }
64
- }
65
-
66
- export type ProviderChoice = {
67
- key: string;
68
- label: string;
69
- provider: Provider;
70
- envVar: string;
71
- envHint: string;
72
- };
73
-
74
- export const PROVIDER_CHOICES: ProviderChoice[] = [
75
- {
76
- key: "1",
77
- label: "OpenAI",
78
- provider: "openai",
79
- envVar: "OPENAI_API_KEY",
80
- envHint: "Get your key at https://platform.openai.com/api-keys",
81
- },
82
- {
83
- key: "2",
84
- label: "Anthropic",
85
- provider: "anthropic",
86
- envVar: "ANTHROPIC_API_KEY",
87
- envHint: "Get your key at https://console.anthropic.com/settings/keys",
88
- },
89
- {
90
- key: "3",
91
- label: "Google Gemini",
92
- provider: "google",
93
- envVar: "GEMINI_API_KEY",
94
- envHint: "Get your key at https://aistudio.google.com/apikey",
95
- },
96
- {
97
- key: "4",
98
- label: "Google Vertex AI",
99
- provider: "vertex",
100
- envVar: "GOOGLE_CLOUD_PROJECT",
101
- envHint:
102
- "Requires `gcloud auth application-default login` and a GCP project ID",
103
- },
104
- {
105
- key: "5",
106
- label: "OpenRouter",
107
- provider: "openrouter",
108
- envVar: "OPENROUTER_API_KEY",
109
- envHint: "Get your key at https://openrouter.ai/settings/keys",
110
- },
111
- ];
112
-
113
- function promptUser(
114
- rl: ReturnType<typeof createInterface>,
115
- question: string,
116
- ): Promise<string> {
117
- return new Promise((resolve) => {
118
- rl.question(question, (answer) => {
119
- resolve(answer.trim());
120
- });
121
- });
122
- }
123
-
124
- /** Map provider to a human-readable label for status messages. */
125
- function providerLabel(provider: Provider): string {
126
- const choice = PROVIDER_CHOICES.find((c) => c.provider === provider);
127
- return choice?.label ?? provider;
128
- }
129
-
130
- /** Extract the env var name from source like "env:GOOGLE_CLOUD_PROJECT". */
131
- function sourceEnvVar(source: string): string | null {
132
- if (source.startsWith("env:")) return source.slice(4);
133
- return null;
134
- }
135
-
136
- /**
137
- * If the workspace has usable credentials but no pinned model in config,
138
- * write the resolved default model to `.libretto/config.json`.
139
- */
140
- function ensurePinnedDefaultModel(
141
- status: AiSetupStatus & { kind: "ready" },
142
- ): AiSetupStatus & { kind: "ready" } {
143
- if (status.source !== "config") {
144
- writeSnapshotModel(status.model);
145
- return { ...status, source: "config" as const };
146
- }
147
- return status;
148
- }
149
-
150
- function printHealthySummary(status: AiSetupStatus & { kind: "ready" }): void {
151
- const envVar = sourceEnvVar(status.source);
152
- if (envVar) {
153
- console.log(
154
- `✓ Detected ${envVar}. Using ${providerLabel(status.provider)}.`,
155
- );
156
- } else {
157
- console.log(`✓ Using ${providerLabel(status.provider)} (${status.model}).`);
158
- }
159
- console.log(
160
- `To change: ${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}`,
161
- );
162
- }
163
-
164
- function printInvalidAiConfigWarning(status: AiSetupStatus): void {
165
- if (status.kind !== "invalid-config") return;
166
- console.log("! Existing AI config is invalid:");
167
- for (const line of status.message.split("\n")) {
168
- console.log(` ${line}`);
169
- }
170
- }
171
-
172
- // ── Repair plan helpers (exported for testing) ──────────────────────────────
173
-
174
- export type RepairChoice = "switch-provider" | "skip";
175
-
176
- export type RepairPlan =
177
- | {
178
- kind: "repair-missing-credentials";
179
- provider: Provider;
180
- model: string;
181
- envVar: string;
182
- choices: RepairChoice[];
183
- }
184
- | { kind: "repair-invalid-config"; message: string }
185
- | { kind: "no-repair-needed" };
186
-
187
- /**
188
- * Determine what repair action setup should take for the current AI status.
189
- * Pure function — no I/O, no prompts.
190
- */
191
- export function buildRepairPlan(status: AiSetupStatus): RepairPlan {
192
- if (status.kind === "configured-missing-credentials") {
193
- const choice = PROVIDER_CHOICES.find((c) => c.provider === status.provider);
194
- return {
195
- kind: "repair-missing-credentials",
196
- provider: status.provider,
197
- model: status.model,
198
- envVar: choice?.envVar ?? `${status.provider.toUpperCase()}_API_KEY`,
199
- choices: ["switch-provider", "skip"],
200
- };
201
- }
202
- if (status.kind === "invalid-config") {
203
- return { kind: "repair-invalid-config", message: status.message };
204
- }
205
- return { kind: "no-repair-needed" };
206
- }
207
-
208
- /**
209
- * Format a provider-specific explanation for missing credentials.
210
- */
211
- export function formatMissingCredentialsMessage(
212
- plan: RepairPlan & { kind: "repair-missing-credentials" },
213
- ): string {
214
- return `✗ ${plan.provider} is configured (model: ${plan.model}), but ${plan.envVar} is not set.`;
215
- }
216
-
217
- function printSnapshotApiStatus(): boolean {
218
- const status = resolveAiSetupStatus();
219
-
220
- console.log(
221
- "\nLibretto uses a sub-agent to analyze DOM snapshots. The model is determined by environment variables.",
222
- );
223
-
224
- if (status.kind === "ready") {
225
- console.log();
226
- printHealthySummary(status);
227
- ensurePinnedDefaultModel(status);
228
- return true;
229
- }
230
-
231
- // Provider-specific missing-credentials message
232
- const plan = buildRepairPlan(status);
233
- if (plan.kind === "repair-missing-credentials") {
234
- console.log();
235
- console.log(formatMissingCredentialsMessage(plan));
236
- console.log(
237
- ` To fix: add ${plan.envVar} to .env, or run \`${librettoCommand("setup")}\` interactively to repair.`,
238
- );
239
- return false;
240
- }
241
-
242
- if (plan.kind === "repair-invalid-config") {
243
- printInvalidAiConfigWarning(status);
244
- console.log(
245
- ` Run \`${librettoCommand("setup")}\` interactively to reconfigure.`,
246
- );
247
- return false;
248
- }
249
-
250
- console.log();
251
- console.log("✗ No snapshot API credentials detected.");
252
- console.log(" Add one provider to .env:");
253
- console.log(" OPENAI_API_KEY=...");
254
- console.log(" ANTHROPIC_API_KEY=...");
255
- console.log(" GEMINI_API_KEY=... # or GOOGLE_GENERATIVE_AI_API_KEY");
256
- console.log(
257
- " GOOGLE_CLOUD_PROJECT=... # plus application default credentials for Vertex",
258
- );
259
- console.log(
260
- ` Or run \`${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}\` to set a specific model.`,
261
- );
262
- console.log(
263
- ` Run \`${librettoCommand("setup")}\` interactively to set up credentials.`,
264
- );
265
- return false;
266
- }
267
-
268
- /**
269
- * Run the full provider selection menu.
270
- * Pins the selected provider's default model to config and prints
271
- * instructions for the user to add the credential to .env themselves.
272
- * Returns true if a provider was successfully configured.
273
- */
274
- async function promptProviderSelection(
275
- rl: ReturnType<typeof createInterface>,
276
- ): Promise<boolean> {
277
- console.log(
278
- "Which model provider would you like to use for snapshot analysis?\n",
279
- );
280
- for (const choice of PROVIDER_CHOICES) {
281
- console.log(` ${choice.key}) ${choice.label}`);
282
- }
283
- console.log(" s) Skip for now\n");
284
-
285
- const answer = await promptUser(rl, "Choice: ");
286
-
287
- if (answer.toLowerCase() === "s" || !answer) {
288
- printSkipMessage();
289
- return false;
290
- }
291
-
292
- const selected = PROVIDER_CHOICES.find((choice) => choice.key === answer);
293
- if (!selected) {
294
- console.log(`\nUnknown choice "${answer}". Skipping API setup.`);
295
- return false;
296
- }
297
-
298
- const model = DEFAULT_SNAPSHOT_MODELS[selected.provider];
299
- writeSnapshotModel(model);
300
- console.log(`\n✓ ${selected.label} selected (model: ${model}).`);
301
- console.log(`\nAdd ${selected.envVar} to your .env file:`);
302
- console.log(` ${selected.envHint}`);
303
- installSdkIfNeeded(selected.provider);
304
- return true;
305
- }
306
-
307
- function printSkipMessage(): void {
308
- console.log(
309
- `\nSkipped. You can set up API credentials later by rerunning \`${librettoCommand("setup")}\`.`,
310
- );
311
- console.log("Or add credentials directly to your .env file:");
312
- console.log(" OPENAI_API_KEY=...");
313
- console.log(" ANTHROPIC_API_KEY=...");
314
- console.log(" GEMINI_API_KEY=...");
315
- console.log(
316
- ` Or run \`${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}\` to set a specific model.`,
317
- );
318
- }
319
-
320
- async function runInteractiveApiSetup(): Promise<void> {
321
- const status = resolveAiSetupStatus();
322
-
323
- console.log(
324
- "\nLibretto uses a sub-agent to analyze DOM snapshots. The model is determined by environment variables.",
325
- );
326
-
327
- if (status.kind === "ready") {
328
- console.log();
329
- printHealthySummary(status);
330
- ensurePinnedDefaultModel(status);
331
- return;
332
- }
333
-
334
- const plan = buildRepairPlan(status);
335
-
336
- const rl = createInterface({
337
- input: process.stdin,
338
- output: process.stdout,
339
- });
340
-
341
- try {
342
- // ── Repair: configured provider with missing credentials ──
343
- if (plan.kind === "repair-missing-credentials") {
344
- console.log(formatMissingCredentialsMessage(plan));
345
- console.log(`\nAdd ${plan.envVar} to your .env file to fix this.`);
346
- console.log("");
347
- console.log("Or switch to a different provider:\n");
348
- console.log(" 1) Switch to a different provider");
349
- console.log(" s) Skip for now\n");
350
-
351
- const answer = await promptUser(rl, "Choice: ");
352
-
353
- if (answer === "1") {
354
- await promptProviderSelection(rl);
355
- return;
356
- }
357
-
358
- // skip or empty
359
- printSkipMessage();
360
- return;
361
- }
362
-
363
- // ── Repair: invalid config → let user pick a provider ──
364
- if (plan.kind === "repair-invalid-config") {
365
- printInvalidAiConfigWarning(status);
366
- console.log(
367
- "\nWould you like to reconfigure with a fresh provider selection?\n",
368
- );
369
- await promptProviderSelection(rl);
370
- return;
371
- }
372
-
373
- // ── Unconfigured: standard first-run flow ──
374
- console.log("✗ No snapshot API credentials detected.\n");
375
- await promptProviderSelection(rl);
376
- } finally {
377
- rl.close();
378
- }
379
- }
380
-
381
13
  function installBrowsers(): void {
382
14
  console.log("Installing Playwright Chromium...");
383
15
  const result = spawnSync("npx", ["playwright", "install", "chromium"], {
@@ -486,17 +118,6 @@ export const setupCommand = SimpleCLI.command({
486
118
 
487
119
  copySkills();
488
120
 
489
- if (process.stdin.isTTY) {
490
- await runInteractiveApiSetup();
491
- } else {
492
- const ready = printSnapshotApiStatus();
493
- if (!ready) {
494
- console.log(
495
- `\nIf you're an agent, request the user to run \`${librettoCommand("setup")}\`.`,
496
- );
497
- }
498
- }
499
-
500
121
  console.log(`\nConfig set up at ${LIBRETTO_CONFIG_PATH}`);
501
122
  console.log("\n✓ libretto setup complete");
502
123
  });
@@ -1,22 +1,12 @@
1
- import { readFileSync, writeFileSync } from "node:fs";
2
1
  import { z } from "zod";
3
2
  import type { LoggerApi } from "../../shared/logger/index.js";
4
- import { condenseDom } from "../../shared/condense-dom/condense-dom.js";
5
3
  import { readSessionState } from "../core/session.js";
6
- import {
7
- type InterpretArgs,
8
- type ScreenshotPair,
9
- } from "../core/snapshot-analyzer.js";
10
4
  import { SimpleCLI } from "../framework/simple-cli.js";
11
5
  import {
12
6
  pageOption,
13
7
  sessionOption,
14
- withExperiments,
15
8
  withRequiredSession,
16
9
  } from "./shared.js";
17
- import { runApiInterpret } from "../core/api-snapshot-analyzer.js";
18
- import { readSnapshotModel } from "../core/config.js";
19
- import { resolveSnapshotApiModelOrThrow } from "../core/ai-model.js";
20
10
  import { DaemonClient } from "../core/daemon/ipc.js";
21
11
  import { librettoCommand } from "../../shared/package-manager.js";
22
12
  import { renderSnapshot } from "../../shared/snapshot/render-snapshot.js";
@@ -116,101 +106,6 @@ export async function forceSnapshotViewport(
116
106
  });
117
107
  }
118
108
 
119
- async function captureSnapshot(
120
- session: string,
121
- logger: LoggerApi,
122
- daemonSocketPath: string,
123
- pageId?: string,
124
- ): Promise<ScreenshotPair> {
125
- logger.info("snapshot-via-daemon", { session, pageId });
126
- const client = await DaemonClient.connect(daemonSocketPath);
127
- let snapshotResult: Awaited<ReturnType<DaemonClient["snapshot"]>>;
128
- try {
129
- snapshotResult = await client.snapshot({ pageId });
130
- } finally {
131
- client.destroy();
132
- }
133
- if (!("htmlPath" in snapshotResult)) {
134
- throw new Error("Daemon returned a compact snapshot for a legacy request.");
135
- }
136
- const { pngPath, htmlPath, snapshotRunId, pageUrl, title } = snapshotResult;
137
-
138
- // condenseDom runs in the CLI process, not the daemon.
139
- const htmlContent = readFileSync(htmlPath, "utf8");
140
- const condenseResult = condenseDom(htmlContent);
141
- const condensedHtmlPath = htmlPath.replace(/\.html$/, ".condensed.html");
142
- writeFileSync(condensedHtmlPath, condenseResult.html);
143
-
144
- logger.info("snapshot-daemon-success", {
145
- session,
146
- pageUrl,
147
- title,
148
- pngPath,
149
- htmlPath,
150
- condensedHtmlPath,
151
- snapshotRunId,
152
- domCondenseStats: {
153
- originalLength: condenseResult.originalLength,
154
- condensedLength: condenseResult.condensedLength,
155
- reductions: condenseResult.reductions,
156
- },
157
- });
158
-
159
- return { pngPath, htmlPath, condensedHtmlPath, baseName: snapshotRunId };
160
- }
161
-
162
- async function runSnapshot(
163
- session: string,
164
- logger: LoggerApi,
165
- pageId: string | undefined,
166
- objective: string | undefined,
167
- context: string | undefined,
168
- ): Promise<void> {
169
- if (objective === undefined) {
170
- throw new Error("Missing required option --objective.");
171
- }
172
- if (context === undefined) {
173
- throw new Error("Missing required option --context.");
174
- }
175
-
176
- const normalizedObjective = objective.trim();
177
- const normalizedContext = context.trim();
178
-
179
- const snapshotModel = readSnapshotModel();
180
- resolveSnapshotApiModelOrThrow(snapshotModel);
181
-
182
- const state = readSessionState(session, logger);
183
- if (!state?.daemonSocketPath) {
184
- throw new Error(
185
- `Session "${session}" has no daemon socket. The browser daemon may have crashed. ` +
186
- `Close and reopen the session: ${librettoCommand(`close --session ${session}`)}`,
187
- );
188
- }
189
-
190
- const { pngPath, htmlPath, condensedHtmlPath } =
191
- await captureSnapshot(session, logger, state.daemonSocketPath, pageId);
192
-
193
- console.log("Screenshot saved:");
194
- console.log(` PNG: ${pngPath}`);
195
- console.log(` HTML: ${htmlPath}`);
196
- console.log(` Condensed HTML: ${condensedHtmlPath}`);
197
-
198
- const interpretArgs: InterpretArgs = {
199
- objective: normalizedObjective,
200
- session,
201
- context: normalizedContext,
202
- pngPath,
203
- htmlPath,
204
- condensedHtmlPath,
205
- };
206
-
207
- // Analysis uses direct API calls via the Vercel AI SDK (see api-snapshot-analyzer.ts).
208
- // The legacy CLI-agent path (spawning codex/claude/gemini as a subprocess) is preserved
209
- // in snapshot-analyzer.ts — to switch back, replace this call with:
210
- // await runInterpret(interpretArgs, logger);
211
- await runApiInterpret(interpretArgs, logger, snapshotModel);
212
- }
213
-
214
109
  async function runCompactSnapshot(
215
110
  args: {
216
111
  session: string;
@@ -237,17 +132,12 @@ async function runCompactSnapshot(
237
132
  let result: Awaited<ReturnType<DaemonClient["snapshot"]>>;
238
133
  try {
239
134
  result = await client.snapshot({
240
- mode: "compact",
241
135
  pageId: args.pageId,
242
136
  useCachedSnapshot: args.ref !== undefined,
243
137
  });
244
138
  } finally {
245
139
  client.destroy();
246
140
  }
247
- if (!("mode" in result) || result.mode !== "compact") {
248
- throw new Error("Daemon returned a legacy snapshot for a compact request.");
249
- }
250
-
251
141
  console.log(`Screenshot at ${result.pngPath}`);
252
142
  console.log(renderSnapshot(result.snapshot, args.ref));
253
143
  console.log(
@@ -270,34 +160,16 @@ export const snapshotInput = SimpleCLI.input({
270
160
  });
271
161
 
272
162
  export const snapshotCommand = SimpleCLI.command({
273
- description: "Capture PNG + HTML and analyze with --objective and --context",
163
+ description: "Capture a screenshot and compact accessibility snapshot",
274
164
  })
275
165
  .input(snapshotInput)
276
166
  .use(withRequiredSession())
277
- .use(withExperiments())
278
167
  .handle(async ({ input, ctx }) => {
279
- if (ctx.experiments["compact-snapshot-format"]) {
280
- await runCompactSnapshot({
281
- session: ctx.session,
282
- daemonSocketPath: ctx.sessionState.daemonSocketPath,
283
- logger: ctx.logger,
284
- pageId: input.page,
285
- ref: input.ref,
286
- });
287
- return;
288
- }
289
-
290
- if (input.ref) {
291
- throw new Error(
292
- `Snapshot refs require the compact-snapshot-format experiment. Enable it with ${librettoCommand("experiments enable compact-snapshot-format")}.`,
293
- );
294
- }
295
-
296
- await runSnapshot(
297
- ctx.session,
298
- ctx.logger,
299
- input.page,
300
- input.objective,
301
- input.context,
302
- );
168
+ await runCompactSnapshot({
169
+ session: ctx.session,
170
+ daemonSocketPath: ctx.sessionState.daemonSocketPath,
171
+ logger: ctx.logger,
172
+ pageId: input.page,
173
+ ref: input.ref,
174
+ });
303
175
  });
@@ -1,51 +1,6 @@
1
- import { LIBRETTO_CONFIG_PATH } from "../core/context.js";
2
- import { type AiSetupStatus, resolveAiSetupStatus } from "../core/ai-model.js";
3
- import { librettoCommand } from "../../shared/package-manager.js";
4
1
  import { listRunningSessions, type SessionState } from "../core/session.js";
5
2
  import { SimpleCLI } from "../framework/simple-cli.js";
6
3
 
7
- // ── AI status printing ──────────────────────────────────────────────────────
8
-
9
- function printAiStatus(status: AiSetupStatus): void {
10
- console.log("AI configuration:");
11
-
12
- switch (status.kind) {
13
- case "ready":
14
- console.log(` ✓ Snapshot model: ${status.model}`);
15
- if (status.source === "config") {
16
- console.log(` Config: ${LIBRETTO_CONFIG_PATH}`);
17
- } else {
18
- console.log(` Source: ${status.source}`);
19
- }
20
- console.log(
21
- ` To change: ${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}`,
22
- );
23
- break;
24
-
25
- case "configured-missing-credentials":
26
- console.log(
27
- ` ✗ ${status.provider} is configured (model: ${status.model}), but credentials are missing.`,
28
- );
29
- console.log(` Run \`${librettoCommand("setup")}\` to repair.`);
30
- break;
31
-
32
- case "invalid-config":
33
- console.log(" ✗ Config is invalid:");
34
- for (const line of status.message.split("\n")) {
35
- console.log(` ${line}`);
36
- }
37
- console.log(` Run \`${librettoCommand("setup")}\` to reconfigure.`);
38
- break;
39
-
40
- case "unconfigured":
41
- console.log(" ✗ No AI model configured.");
42
- console.log(
43
- ` Run \`${librettoCommand("setup")}\` or \`${librettoCommand("ai configure")}\` to set up.`,
44
- );
45
- break;
46
- }
47
- }
48
-
49
4
  // ── Session status printing ─────────────────────────────────────────────────
50
5
 
51
6
  function printOpenSessions(sessions: SessionState[]): void {
@@ -68,13 +23,10 @@ function printOpenSessions(sessions: SessionState[]): void {
68
23
  // ── Command ─────────────────────────────────────────────────────────────────
69
24
 
70
25
  export const statusCommand = SimpleCLI.command({
71
- description: "Show workspace status: AI configuration and open sessions",
26
+ description: "Show workspace status and open sessions",
72
27
  })
73
28
  .input(SimpleCLI.input({ positionals: [], named: {} }))
74
29
  .handle(async () => {
75
- const aiStatus = resolveAiSetupStatus();
76
- printAiStatus(aiStatus);
77
-
78
30
  const sessions = listRunningSessions();
79
31
  printOpenSessions(sessions);
80
32
  });
@@ -6,6 +6,7 @@ import {
6
6
  type Page,
7
7
  } from "playwright";
8
8
  import { existsSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
9
+ import { mkdir, writeFile } from "node:fs/promises";
9
10
  import { dirname, join } from "node:path";
10
11
  import { createServer } from "node:net";
11
12
  import type { LoggerApi } from "../../shared/logger/index.js";
@@ -657,9 +658,8 @@ export async function runSave(
657
658
  }
658
659
 
659
660
  const state = { cookies, origins };
660
- const fs = await import("node:fs/promises");
661
- await fs.mkdir(dirname(profilePath), { recursive: true });
662
- await fs.writeFile(profilePath, JSON.stringify(state, null, 2));
661
+ await mkdir(dirname(profilePath), { recursive: true });
662
+ await writeFile(profilePath, JSON.stringify(state, null, 2));
663
663
 
664
664
  logger.info("save-success", {
665
665
  domain,
@@ -3,7 +3,6 @@ import { dirname } from "node:path";
3
3
  import { z } from "zod";
4
4
  import { SessionAccessModeSchema } from "../../shared/state/index.js";
5
5
  import { LIBRETTO_CONFIG_PATH } from "./context.js";
6
- import { librettoCommand } from "../../shared/package-manager.js";
7
6
 
8
7
  export const CURRENT_CONFIG_VERSION = 1;
9
8
 
@@ -42,7 +41,6 @@ function formatExpectedConfigExample(): string {
42
41
  return JSON.stringify(
43
42
  {
44
43
  version: CURRENT_CONFIG_VERSION,
45
- snapshotModel: "openai/gpt-5.4",
46
44
  viewport: {
47
45
  width: 1280,
48
46
  height: 800,
@@ -66,10 +64,9 @@ function invalidConfigError(configPath: string, detail?: string): Error {
66
64
  "Expected config example:",
67
65
  formatExpectedConfigExample(),
68
66
  "Notes:",
69
- ' - "snapshotModel", "viewport", "windowPosition", and "sessionMode" are optional.',
70
- ' - "snapshotModel" must be a provider/model string like "openai/gpt-5.4" or "anthropic/claude-sonnet-4-6".',
71
- "Fix the file to match this shape, or delete it and rerun:",
72
- ` ${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}`,
67
+ ' - "viewport", "windowPosition", and "sessionMode" are optional.',
68
+ ' - "snapshotModel" is deprecated and ignored by snapshot.',
69
+ "Fix the file to match this shape, or delete it and rerun setup.",
73
70
  ]
74
71
  .filter(Boolean)
75
72
  .join("\n"),