libretto 0.6.12 → 0.6.14

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 (82) 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/auth.js +24 -33
  5. package/dist/cli/commands/billing.js +3 -5
  6. package/dist/cli/commands/browser.js +4 -13
  7. package/dist/cli/commands/deploy.js +54 -45
  8. package/dist/cli/commands/execution.js +6 -3
  9. package/dist/cli/commands/experiments.js +1 -1
  10. package/dist/cli/commands/setup.js +2 -295
  11. package/dist/cli/commands/shared.js +1 -1
  12. package/dist/cli/commands/snapshot.js +10 -100
  13. package/dist/cli/commands/status.js +2 -42
  14. package/dist/cli/core/auth-fetch.js +11 -6
  15. package/dist/cli/core/browser.js +13 -8
  16. package/dist/cli/core/config.js +3 -6
  17. package/dist/cli/core/daemon/daemon.js +88 -74
  18. package/dist/cli/core/daemon/exec-repl.js +133 -0
  19. package/dist/cli/core/daemon/exec.js +6 -21
  20. package/dist/cli/core/daemon/ipc.js +47 -4
  21. package/dist/cli/core/daemon/ipc.spec.js +21 -0
  22. package/dist/cli/core/daemon/snapshot.js +2 -29
  23. package/dist/cli/core/exec-compiler.js +8 -3
  24. package/dist/cli/core/experiments.js +1 -28
  25. package/dist/cli/core/providers/index.js +13 -4
  26. package/dist/cli/core/providers/libretto-cloud.js +178 -26
  27. package/dist/cli/index.js +0 -2
  28. package/dist/cli/router.js +9 -6
  29. package/dist/shared/instrumentation/instrument.js +4 -4
  30. package/dist/shared/ipc/socket-transport.d.ts +2 -1
  31. package/dist/shared/ipc/socket-transport.js +16 -5
  32. package/dist/shared/ipc/socket-transport.spec.js +5 -0
  33. package/docs/releasing.md +8 -6
  34. package/package.json +3 -2
  35. package/skills/libretto/SKILL.md +49 -47
  36. package/skills/libretto/references/code-generation-rules.md +6 -0
  37. package/skills/libretto/references/configuration-file-reference.md +14 -12
  38. package/skills/libretto/references/pages-and-page-targeting.md +1 -1
  39. package/skills/libretto/references/site-security-review.md +6 -6
  40. package/skills/libretto-readonly/SKILL.md +2 -9
  41. package/src/cli/cli.ts +0 -24
  42. package/src/cli/commands/auth.ts +24 -33
  43. package/src/cli/commands/billing.ts +3 -5
  44. package/src/cli/commands/browser.ts +6 -16
  45. package/src/cli/commands/deploy.ts +55 -49
  46. package/src/cli/commands/execution.ts +6 -3
  47. package/src/cli/commands/experiments.ts +1 -1
  48. package/src/cli/commands/setup.ts +2 -381
  49. package/src/cli/commands/shared.ts +1 -1
  50. package/src/cli/commands/snapshot.ts +9 -137
  51. package/src/cli/commands/status.ts +2 -50
  52. package/src/cli/core/auth-fetch.ts +9 -4
  53. package/src/cli/core/browser.ts +15 -8
  54. package/src/cli/core/config.ts +3 -6
  55. package/src/cli/core/daemon/daemon.ts +106 -76
  56. package/src/cli/core/daemon/exec-repl.ts +189 -0
  57. package/src/cli/core/daemon/exec.ts +8 -43
  58. package/src/cli/core/daemon/ipc.spec.ts +27 -0
  59. package/src/cli/core/daemon/ipc.ts +81 -23
  60. package/src/cli/core/daemon/snapshot.ts +1 -43
  61. package/src/cli/core/exec-compiler.ts +8 -3
  62. package/src/cli/core/experiments.ts +9 -38
  63. package/src/cli/core/providers/index.ts +17 -4
  64. package/src/cli/core/providers/libretto-cloud.ts +224 -36
  65. package/src/cli/core/resolve-model.ts +5 -0
  66. package/src/cli/core/workflow-runtime.ts +1 -0
  67. package/src/cli/index.ts +0 -1
  68. package/src/cli/router.ts +9 -6
  69. package/src/shared/instrumentation/instrument.ts +4 -4
  70. package/src/shared/ipc/socket-transport.spec.ts +6 -0
  71. package/src/shared/ipc/socket-transport.ts +20 -5
  72. package/dist/cli/commands/ai.js +0 -110
  73. package/dist/cli/core/ai-model.js +0 -195
  74. package/dist/cli/core/api-snapshot-analyzer.js +0 -86
  75. package/dist/cli/core/snapshot-analyzer.js +0 -667
  76. package/dist/cli/framework/simple-cli.js +0 -880
  77. package/scripts/summarize-evals.mjs +0 -135
  78. package/src/cli/commands/ai.ts +0 -144
  79. package/src/cli/core/ai-model.ts +0 -301
  80. package/src/cli/core/api-snapshot-analyzer.ts +0 -110
  81. package/src/cli/core/snapshot-analyzer.ts +0 -856
  82. package/src/cli/framework/simple-cli.ts +0 -1459
@@ -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";
21
- 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
- }
10
+ import { librettoCommand } from "../../shared/package-manager.js";
11
+ import { SimpleCLI } from "affordance";
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
  };
@@ -8,7 +8,7 @@ import {
8
8
  } from "../core/session.js";
9
9
  import {
10
10
  SimpleCLI
11
- } from "../framework/simple-cli.js";
11
+ } from "affordance";
12
12
  function sessionOption(help = "Session name") {
13
13
  return SimpleCLI.option(z.string().optional(), { help });
14
14
  }
@@ -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
- import { SimpleCLI } from "../framework/simple-cli.js";
3
+ import { SimpleCLI } from "affordance";
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
- 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
- }
2
+ import { SimpleCLI } from "affordance";
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
  });
@@ -1,9 +1,13 @@
1
1
  import { readAuthState, writeAuthState } from "./auth-storage.js";
2
- const HOSTED_API_URL = "https://api.libretto.sh";
2
+ const DEFAULT_HOSTED_API_URL = "https://api.libretto.sh";
3
+ function resolveHostedApiUrl() {
4
+ return process.env.LIBRETTO_API_URL?.trim() || DEFAULT_HOSTED_API_URL;
5
+ }
3
6
  const NOT_AUTHENTICATED_MESSAGE = [
4
7
  "Not authenticated.",
5
- " \u2022 Cookie expired or never set: run `libretto experimental auth login` to refresh it.",
6
- " \u2022 Or set LIBRETTO_API_KEY in your .env (issue one with `libretto experimental auth api-key issue --label <label>` after logging in)."
8
+ " \u2022 New account: run `libretto cloud auth signup`.",
9
+ " \u2022 Existing account: run `libretto cloud auth login`.",
10
+ " \u2022 Automation: set LIBRETTO_API_KEY in your env (issue one with `libretto cloud auth api-key issue --label <label>` after signing in)."
7
11
  ].join("\n");
8
12
  function pickCredential(state) {
9
13
  const envKey = process.env.LIBRETTO_API_KEY?.trim();
@@ -14,7 +18,7 @@ function pickCredential(state) {
14
18
  return { source: "none" };
15
19
  }
16
20
  function resolveApiUrl(_state) {
17
- return HOSTED_API_URL;
21
+ return resolveHostedApiUrl();
18
22
  }
19
23
  async function authFetch(options) {
20
24
  const headers = {
@@ -184,12 +188,13 @@ async function ensureAuthState(apiUrl) {
184
188
  }
185
189
  export {
186
190
  ApiCallError,
187
- HOSTED_API_URL,
191
+ DEFAULT_HOSTED_API_URL,
188
192
  NOT_AUTHENTICATED_MESSAGE,
189
193
  authFetch,
190
194
  betterAuthCall,
191
195
  ensureAuthState,
192
196
  orpcCall,
193
197
  pickCredential,
194
- resolveApiUrl
198
+ resolveApiUrl,
199
+ resolveHostedApiUrl
195
200
  };
@@ -2,12 +2,17 @@ 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";
8
+ import { isWindowsNamedPipePath } from "../../shared/ipc/socket-transport.js";
7
9
  import { getSessionProviderClosePath, PROFILES_DIR } from "./context.js";
8
10
  import { readLibrettoConfig } from "./config.js";
9
11
  import { librettoCommand } from "../../shared/package-manager.js";
10
- import { getCloudProviderApi } from "./providers/index.js";
12
+ import {
13
+ getCloudProviderApi,
14
+ getProviderStartupTimeoutMs
15
+ } from "./providers/index.js";
11
16
  import {
12
17
  assertSessionAvailableForStart,
13
18
  clearSessionState,
@@ -63,14 +68,14 @@ function normalizeUrl(url) {
63
68
  if (!parsedUrl) {
64
69
  return new URL(`https://${url}`);
65
70
  }
66
- if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:" || parsedUrl.protocol === "file:") {
71
+ if (parsedUrl.protocol === "http:" || parsedUrl.protocol === "https:" || parsedUrl.protocol === "file:" || parsedUrl.href === "about:blank") {
67
72
  return parsedUrl;
68
73
  }
69
74
  if (isLikelyHostWithPort(parsedUrl, url)) {
70
75
  return new URL(`https://${url}`);
71
76
  }
72
77
  throw new Error(
73
- `Unsupported URL protocol: ${parsedUrl.protocol}. Use http://, https://, or file://.`
78
+ `Unsupported URL protocol: ${parsedUrl.protocol}. Use http://, https://, file://, or about:blank.`
74
79
  );
75
80
  }
76
81
  function normalizeDomain(url) {
@@ -397,8 +402,8 @@ async function runOpenWithProvider(rawUrl, providerName, session, logger, access
397
402
  },
398
403
  logger,
399
404
  logPath: runLogPath,
400
- // Remote CDP connection + navigation; must cover both.
401
- startupTimeoutMs: 6e4
405
+ // Remote provider creation can wait for cloud capacity before CDP exists.
406
+ startupTimeoutMs: getProviderStartupTimeoutMs(providerName)
402
407
  });
403
408
  client.destroy();
404
409
  if (!providerSession) {
@@ -485,9 +490,8 @@ async function runSave(urlOrDomain, session, logger) {
485
490
  }
486
491
  }
487
492
  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));
493
+ await mkdir(dirname(profilePath), { recursive: true });
494
+ await writeFile(profilePath, JSON.stringify(state, null, 2));
491
495
  logger.info("save-success", {
492
496
  domain,
493
497
  profilePath,
@@ -727,6 +731,7 @@ function resolveClosableSessions(logger) {
727
731
  }
728
732
  function unlinkDaemonSocket(socketPath, logger, session) {
729
733
  if (!socketPath) return;
734
+ if (isWindowsNamedPipePath(socketPath)) return;
730
735
  try {
731
736
  unlinkSync(socketPath);
732
737
  } catch (err) {
@@ -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
  const CURRENT_CONFIG_VERSION = 1;
8
7
  const ViewportConfigSchema = z.object({
9
8
  width: z.number().int().min(1),
@@ -29,7 +28,6 @@ function formatExpectedConfigExample() {
29
28
  return JSON.stringify(
30
29
  {
31
30
  version: CURRENT_CONFIG_VERSION,
32
- snapshotModel: "openai/gpt-5.4",
33
31
  viewport: {
34
32
  width: 1280,
35
33
  height: 800
@@ -53,10 +51,9 @@ ${detail}` : null,
53
51
  "Expected config example:",
54
52
  formatExpectedConfigExample(),
55
53
  "Notes:",
56
- ' - "snapshotModel", "viewport", "windowPosition", and "sessionMode" are optional.',
57
- ' - "snapshotModel" must be a provider/model string like "openai/gpt-5.4" or "anthropic/claude-sonnet-4-6".',
58
- "Fix the file to match this shape, or delete it and rerun:",
59
- ` ${librettoCommand("ai configure openai | anthropic | gemini | vertex | openrouter")}`
54
+ ' - "viewport", "windowPosition", and "sessionMode" are optional.',
55
+ ' - "snapshotModel" is deprecated and ignored by snapshot.',
56
+ "Fix the file to match this shape, or delete it and rerun setup."
60
57
  ].filter(Boolean).join("\n")
61
58
  );
62
59
  }