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.
- package/README.md +3 -8
- package/README.template.md +3 -8
- package/dist/cli/cli.js +0 -23
- package/dist/cli/commands/auth.js +24 -33
- package/dist/cli/commands/billing.js +3 -5
- package/dist/cli/commands/browser.js +4 -13
- package/dist/cli/commands/deploy.js +54 -45
- package/dist/cli/commands/execution.js +6 -3
- package/dist/cli/commands/experiments.js +1 -1
- package/dist/cli/commands/setup.js +2 -295
- package/dist/cli/commands/shared.js +1 -1
- package/dist/cli/commands/snapshot.js +10 -100
- package/dist/cli/commands/status.js +2 -42
- package/dist/cli/core/auth-fetch.js +11 -6
- package/dist/cli/core/browser.js +13 -8
- package/dist/cli/core/config.js +3 -6
- package/dist/cli/core/daemon/daemon.js +88 -74
- package/dist/cli/core/daemon/exec-repl.js +133 -0
- package/dist/cli/core/daemon/exec.js +6 -21
- package/dist/cli/core/daemon/ipc.js +47 -4
- package/dist/cli/core/daemon/ipc.spec.js +21 -0
- package/dist/cli/core/daemon/snapshot.js +2 -29
- package/dist/cli/core/exec-compiler.js +8 -3
- package/dist/cli/core/experiments.js +1 -28
- package/dist/cli/core/providers/index.js +13 -4
- package/dist/cli/core/providers/libretto-cloud.js +178 -26
- package/dist/cli/index.js +0 -2
- package/dist/cli/router.js +9 -6
- package/dist/shared/instrumentation/instrument.js +4 -4
- package/dist/shared/ipc/socket-transport.d.ts +2 -1
- package/dist/shared/ipc/socket-transport.js +16 -5
- package/dist/shared/ipc/socket-transport.spec.js +5 -0
- package/docs/releasing.md +8 -6
- package/package.json +3 -2
- package/skills/libretto/SKILL.md +49 -47
- package/skills/libretto/references/code-generation-rules.md +6 -0
- package/skills/libretto/references/configuration-file-reference.md +14 -12
- package/skills/libretto/references/pages-and-page-targeting.md +1 -1
- package/skills/libretto/references/site-security-review.md +6 -6
- package/skills/libretto-readonly/SKILL.md +2 -9
- package/src/cli/cli.ts +0 -24
- package/src/cli/commands/auth.ts +24 -33
- package/src/cli/commands/billing.ts +3 -5
- package/src/cli/commands/browser.ts +6 -16
- package/src/cli/commands/deploy.ts +55 -49
- package/src/cli/commands/execution.ts +6 -3
- package/src/cli/commands/experiments.ts +1 -1
- package/src/cli/commands/setup.ts +2 -381
- package/src/cli/commands/shared.ts +1 -1
- package/src/cli/commands/snapshot.ts +9 -137
- package/src/cli/commands/status.ts +2 -50
- package/src/cli/core/auth-fetch.ts +9 -4
- package/src/cli/core/browser.ts +15 -8
- package/src/cli/core/config.ts +3 -6
- package/src/cli/core/daemon/daemon.ts +106 -76
- package/src/cli/core/daemon/exec-repl.ts +189 -0
- package/src/cli/core/daemon/exec.ts +8 -43
- package/src/cli/core/daemon/ipc.spec.ts +27 -0
- package/src/cli/core/daemon/ipc.ts +81 -23
- package/src/cli/core/daemon/snapshot.ts +1 -43
- package/src/cli/core/exec-compiler.ts +8 -3
- package/src/cli/core/experiments.ts +9 -38
- package/src/cli/core/providers/index.ts +17 -4
- package/src/cli/core/providers/libretto-cloud.ts +224 -36
- package/src/cli/core/resolve-model.ts +5 -0
- package/src/cli/core/workflow-runtime.ts +1 -0
- package/src/cli/index.ts +0 -1
- package/src/cli/router.ts +9 -6
- package/src/shared/instrumentation/instrument.ts +4 -4
- package/src/shared/ipc/socket-transport.spec.ts +6 -0
- package/src/shared/ipc/socket-transport.ts +20 -5
- package/dist/cli/commands/ai.js +0 -110
- package/dist/cli/core/ai-model.js +0 -195
- package/dist/cli/core/api-snapshot-analyzer.js +0 -86
- package/dist/cli/core/snapshot-analyzer.js +0 -667
- package/dist/cli/framework/simple-cli.js +0 -880
- package/scripts/summarize-evals.mjs +0 -135
- package/src/cli/commands/ai.ts +0 -144
- package/src/cli/core/ai-model.ts +0 -301
- package/src/cli/core/api-snapshot-analyzer.ts +0 -110
- package/src/cli/core/snapshot-analyzer.ts +0 -856
- 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
|
-
|
|
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
|
};
|
|
@@ -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 "
|
|
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
|
|
184
|
-
}).input(snapshotInput).use(withRequiredSession()).
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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 "
|
|
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
|
|
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
|
|
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
|
|
6
|
-
" \u2022
|
|
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
|
|
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
|
-
|
|
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
|
};
|
package/dist/cli/core/browser.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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
|
|
401
|
-
startupTimeoutMs:
|
|
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
|
-
|
|
489
|
-
await
|
|
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) {
|
package/dist/cli/core/config.js
CHANGED
|
@@ -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
|
-
' - "
|
|
57
|
-
' - "snapshotModel"
|
|
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
|
}
|