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.
- package/README.md +3 -8
- package/README.template.md +3 -8
- package/dist/cli/cli.js +0 -23
- package/dist/cli/commands/browser.js +1 -7
- package/dist/cli/commands/setup.js +1 -294
- package/dist/cli/commands/snapshot.js +9 -99
- package/dist/cli/commands/status.js +1 -41
- package/dist/cli/core/browser.js +3 -3
- package/dist/cli/core/config.js +3 -6
- package/dist/cli/core/daemon/daemon.js +25 -64
- package/dist/cli/core/daemon/snapshot.js +2 -29
- package/dist/cli/core/experiments.js +1 -28
- package/dist/cli/index.js +0 -2
- package/dist/cli/router.js +0 -2
- package/dist/shared/instrumentation/instrument.js +4 -4
- package/docs/releasing.md +8 -6
- package/package.json +2 -1
- package/skills/libretto/SKILL.md +17 -19
- package/skills/libretto/references/configuration-file-reference.md +6 -12
- package/skills/libretto/references/pages-and-page-targeting.md +1 -1
- package/skills/libretto-readonly/SKILL.md +2 -9
- package/src/cli/cli.ts +0 -24
- package/src/cli/commands/browser.ts +1 -7
- package/src/cli/commands/setup.ts +1 -380
- package/src/cli/commands/snapshot.ts +8 -136
- package/src/cli/commands/status.ts +1 -49
- package/src/cli/core/browser.ts +3 -3
- package/src/cli/core/config.ts +3 -6
- package/src/cli/core/daemon/daemon.ts +25 -67
- package/src/cli/core/daemon/ipc.ts +5 -16
- package/src/cli/core/daemon/snapshot.ts +1 -43
- package/src/cli/core/experiments.ts +9 -38
- 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 +0 -2
- package/src/shared/instrumentation/instrument.ts +4 -4
- 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/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/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
|
|
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`
|
|
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 --
|
|
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
|
```
|
package/README.template.md
CHANGED
|
@@ -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
|
|
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`
|
|
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 --
|
|
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
|
|
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
|
|
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
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
|
|
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
|
});
|
package/dist/cli/core/browser.js
CHANGED
|
@@ -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
|
-
|
|
489
|
-
await
|
|
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,
|