libretto 0.6.11 → 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 +7 -8
- package/README.template.md +7 -8
- package/dist/cli/cli.js +0 -22
- package/dist/cli/commands/browser.js +18 -24
- package/dist/cli/commands/execution.js +254 -234
- package/dist/cli/commands/experiments.js +100 -0
- package/dist/cli/commands/setup.js +3 -310
- package/dist/cli/commands/shared.js +10 -0
- package/dist/cli/commands/snapshot.js +46 -64
- package/dist/cli/commands/status.js +1 -40
- package/dist/cli/core/browser.js +303 -124
- package/dist/cli/core/config.js +5 -6
- package/dist/cli/core/context.js +4 -0
- package/dist/cli/core/daemon/config.js +0 -6
- package/dist/cli/core/daemon/daemon.js +497 -90
- package/dist/cli/core/daemon/ipc.js +170 -129
- package/dist/cli/core/daemon/snapshot.js +48 -9
- package/dist/cli/core/experiments.js +39 -0
- package/dist/cli/core/session.js +5 -4
- package/dist/cli/core/skill-version.js +2 -1
- package/dist/cli/core/workflow-runner/runner.js +147 -0
- package/dist/cli/core/workflow-runtime.js +60 -0
- package/dist/cli/index.js +0 -2
- package/dist/cli/router.js +4 -3
- package/dist/shared/debug/pause-handler.d.ts +9 -0
- package/dist/shared/debug/pause-handler.js +15 -0
- package/dist/shared/debug/pause.d.ts +1 -2
- package/dist/shared/debug/pause.js +13 -36
- package/dist/shared/instrumentation/instrument.js +4 -4
- package/dist/shared/ipc/child-process-transport.d.ts +7 -0
- package/dist/shared/ipc/child-process-transport.js +60 -0
- package/dist/shared/ipc/child-process-transport.spec.d.ts +2 -0
- package/dist/shared/ipc/child-process-transport.spec.js +68 -0
- package/dist/shared/ipc/ipc.d.ts +46 -0
- package/dist/shared/ipc/ipc.js +165 -0
- package/dist/shared/ipc/ipc.spec.d.ts +2 -0
- package/dist/shared/ipc/ipc.spec.js +114 -0
- package/dist/shared/ipc/socket-transport.d.ts +9 -0
- package/dist/shared/ipc/socket-transport.js +143 -0
- package/dist/shared/ipc/socket-transport.spec.d.ts +2 -0
- package/dist/shared/ipc/socket-transport.spec.js +117 -0
- package/dist/shared/package-manager.d.ts +7 -0
- package/dist/shared/package-manager.js +60 -0
- package/dist/shared/paths/paths.d.ts +1 -8
- package/dist/shared/paths/paths.js +1 -49
- package/dist/shared/snapshot/capture-snapshot.d.ts +9 -0
- package/dist/shared/snapshot/capture-snapshot.js +463 -0
- package/dist/shared/snapshot/diff-snapshots.d.ts +72 -0
- package/dist/shared/snapshot/diff-snapshots.js +358 -0
- package/dist/shared/snapshot/render-snapshot.d.ts +39 -0
- package/dist/shared/snapshot/render-snapshot.js +651 -0
- package/dist/shared/snapshot/snapshot.spec.d.ts +2 -0
- package/dist/shared/snapshot/snapshot.spec.js +333 -0
- package/dist/shared/snapshot/types.d.ts +40 -0
- package/dist/shared/snapshot/types.js +0 -0
- package/dist/shared/snapshot/wait-for-page-stable.d.ts +17 -0
- package/dist/shared/snapshot/wait-for-page-stable.js +281 -0
- package/dist/shared/state/session-state.d.ts +1 -0
- package/dist/shared/state/session-state.js +1 -0
- package/docs/experiments.md +67 -0
- package/docs/releasing.md +8 -6
- package/package.json +5 -2
- package/skills/libretto/SKILL.md +19 -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/AGENTS.md +7 -0
- package/src/cli/cli.ts +0 -23
- package/src/cli/commands/browser.ts +14 -18
- package/src/cli/commands/execution.ts +303 -271
- package/src/cli/commands/experiments.ts +120 -0
- package/src/cli/commands/setup.ts +3 -400
- package/src/cli/commands/shared.ts +20 -0
- package/src/cli/commands/snapshot.ts +54 -94
- package/src/cli/commands/status.ts +1 -48
- package/src/cli/core/browser.ts +372 -150
- package/src/cli/core/config.ts +4 -5
- package/src/cli/core/context.ts +4 -0
- package/src/cli/core/daemon/config.ts +35 -19
- package/src/cli/core/daemon/daemon.ts +645 -107
- package/src/cli/core/daemon/ipc.ts +319 -214
- package/src/cli/core/daemon/snapshot.ts +71 -15
- package/src/cli/core/experiments.ts +56 -0
- package/src/cli/core/resolve-model.ts +5 -0
- package/src/cli/core/session.ts +5 -4
- package/src/cli/core/skill-version.ts +2 -1
- package/src/cli/core/workflow-runner/runner.ts +237 -0
- package/src/cli/core/workflow-runtime.ts +86 -0
- package/src/cli/index.ts +0 -1
- package/src/cli/router.ts +4 -3
- package/src/shared/debug/pause-handler.ts +20 -0
- package/src/shared/debug/pause.ts +14 -48
- package/src/shared/instrumentation/instrument.ts +4 -4
- package/src/shared/ipc/AGENTS.md +24 -0
- package/src/shared/ipc/child-process-transport.spec.ts +86 -0
- package/src/shared/ipc/child-process-transport.ts +96 -0
- package/src/shared/ipc/ipc.spec.ts +161 -0
- package/src/shared/ipc/ipc.ts +288 -0
- package/src/shared/ipc/socket-transport.spec.ts +141 -0
- package/src/shared/ipc/socket-transport.ts +189 -0
- package/src/shared/package-manager.ts +76 -0
- package/src/shared/paths/paths.ts +0 -72
- package/src/shared/snapshot/capture-snapshot.ts +615 -0
- package/src/shared/snapshot/diff-snapshots.ts +579 -0
- package/src/shared/snapshot/render-snapshot.ts +962 -0
- package/src/shared/snapshot/snapshot.spec.ts +388 -0
- package/src/shared/snapshot/types.ts +43 -0
- package/src/shared/snapshot/wait-for-page-stable.ts +425 -0
- package/src/shared/state/session-state.ts +1 -0
- package/dist/cli/commands/ai.js +0 -109
- package/dist/cli/core/ai-model.js +0 -192
- package/dist/cli/core/api-snapshot-analyzer.js +0 -86
- package/dist/cli/core/daemon/index.js +0 -16
- package/dist/cli/core/daemon/spawn.js +0 -90
- package/dist/cli/core/pause-signals.js +0 -29
- package/dist/cli/core/snapshot-analyzer.js +0 -666
- package/dist/cli/workers/run-integration-runtime.js +0 -235
- package/dist/cli/workers/run-integration-worker-protocol.js +0 -17
- package/dist/cli/workers/run-integration-worker.js +0 -64
- package/scripts/summarize-evals.mjs +0 -135
- package/src/cli/commands/ai.ts +0 -143
- package/src/cli/core/ai-model.ts +0 -298
- package/src/cli/core/api-snapshot-analyzer.ts +0 -110
- package/src/cli/core/daemon/index.ts +0 -24
- package/src/cli/core/daemon/spawn.ts +0 -171
- package/src/cli/core/pause-signals.ts +0 -35
- package/src/cli/core/snapshot-analyzer.ts +0 -855
- package/src/cli/workers/run-integration-runtime.ts +0 -326
- package/src/cli/workers/run-integration-worker-protocol.ts +0 -19
- package/src/cli/workers/run-integration-worker.ts +0 -72
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { librettoCommand } from "../../shared/package-manager.js";
|
|
3
|
+
import {
|
|
4
|
+
EXPERIMENTS,
|
|
5
|
+
isExperimentName,
|
|
6
|
+
resolveExperiments,
|
|
7
|
+
setExperimentEnabled
|
|
8
|
+
} from "../core/experiments.js";
|
|
9
|
+
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
10
|
+
const experimentNames = Object.keys(EXPERIMENTS);
|
|
11
|
+
const experimentsUsage = [
|
|
12
|
+
"Usage:",
|
|
13
|
+
` ${librettoCommand("experiments")}`,
|
|
14
|
+
` ${librettoCommand("experiments describe <experiment>")}`,
|
|
15
|
+
` ${librettoCommand("experiments enable <experiment>")}`,
|
|
16
|
+
` ${librettoCommand("experiments disable <experiment>")}`
|
|
17
|
+
].join("\n");
|
|
18
|
+
const experimentsInput = SimpleCLI.input({
|
|
19
|
+
positionals: [
|
|
20
|
+
SimpleCLI.positional("action", z.string().optional(), {
|
|
21
|
+
help: "Action to apply"
|
|
22
|
+
}),
|
|
23
|
+
SimpleCLI.positional("experiment", z.string().optional(), {
|
|
24
|
+
help: "Experiment name"
|
|
25
|
+
})
|
|
26
|
+
],
|
|
27
|
+
named: {}
|
|
28
|
+
});
|
|
29
|
+
function formatAvailableExperiments() {
|
|
30
|
+
return [
|
|
31
|
+
"Available experiments:",
|
|
32
|
+
...experimentNames.map((name) => ` ${name}`)
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
function experimentUsageError(message) {
|
|
36
|
+
return new Error(
|
|
37
|
+
[message, "", experimentsUsage, "", formatAvailableExperiments()].join(
|
|
38
|
+
"\n"
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
function printExperiments(experiments) {
|
|
43
|
+
console.log("Libretto experiments:");
|
|
44
|
+
for (const name of experimentNames) {
|
|
45
|
+
const metadata = EXPERIMENTS[name];
|
|
46
|
+
console.log(
|
|
47
|
+
`- ${name}: ${experiments[name] ? "enabled" : "disabled"} \u2014 ${metadata.title}`
|
|
48
|
+
);
|
|
49
|
+
console.log(` ${metadata.oneSentenceDescription}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function printExperimentDescription(name, experiments) {
|
|
53
|
+
const metadata = EXPERIMENTS[name];
|
|
54
|
+
console.log(`${metadata.title} (${name})`);
|
|
55
|
+
console.log(`Status: ${experiments[name] ? "enabled" : "disabled"}`);
|
|
56
|
+
console.log("");
|
|
57
|
+
if (experiments[name]) {
|
|
58
|
+
console.log(
|
|
59
|
+
"Since this experiment is enabled, Libretto\u2019s expected usage deviates from the skill. Use these instructions where they differ:"
|
|
60
|
+
);
|
|
61
|
+
console.log("");
|
|
62
|
+
}
|
|
63
|
+
console.log(metadata.docs ?? metadata.oneSentenceDescription);
|
|
64
|
+
}
|
|
65
|
+
const experimentsCommand = SimpleCLI.command({
|
|
66
|
+
description: "List or update Libretto experiment flags"
|
|
67
|
+
}).input(experimentsInput).handle(async ({ input }) => {
|
|
68
|
+
if (!input.action) {
|
|
69
|
+
printExperiments(resolveExperiments());
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (input.action !== "describe" && input.action !== "enable" && input.action !== "disable") {
|
|
73
|
+
throw experimentUsageError(`Unknown experiments action "${input.action}".`);
|
|
74
|
+
}
|
|
75
|
+
if (!input.experiment) {
|
|
76
|
+
throw experimentUsageError(
|
|
77
|
+
`Missing experiment name for ${input.action}.`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (!isExperimentName(input.experiment)) {
|
|
81
|
+
throw experimentUsageError(`Unknown experiment "${input.experiment}".`);
|
|
82
|
+
}
|
|
83
|
+
if (input.action === "describe") {
|
|
84
|
+
printExperimentDescription(input.experiment, resolveExperiments());
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const experiments = setExperimentEnabled(
|
|
88
|
+
input.experiment,
|
|
89
|
+
input.action === "enable"
|
|
90
|
+
);
|
|
91
|
+
console.log(`Experiment "${input.experiment}" ${input.action}d.`);
|
|
92
|
+
if (input.action === "enable") {
|
|
93
|
+
console.log("");
|
|
94
|
+
printExperimentDescription(input.experiment, experiments);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
export {
|
|
98
|
+
experimentsCommand,
|
|
99
|
+
experimentsInput
|
|
100
|
+
};
|
|
@@ -1,308 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
cpSync,
|
|
4
|
-
existsSync,
|
|
5
|
-
readdirSync,
|
|
6
|
-
rmSync
|
|
7
|
-
} from "node:fs";
|
|
1
|
+
import { cpSync, existsSync, readdirSync, rmSync } from "node:fs";
|
|
8
2
|
import { spawnSync } from "node:child_process";
|
|
9
3
|
import { basename, dirname, join } from "node:path";
|
|
10
4
|
import { fileURLToPath } from "node:url";
|
|
11
|
-
import { writeSnapshotModel } from "../core/config.js";
|
|
12
5
|
import {
|
|
13
6
|
ensureLibrettoSetup,
|
|
14
7
|
LIBRETTO_CONFIG_PATH,
|
|
15
8
|
REPO_ROOT
|
|
16
9
|
} from "../core/context.js";
|
|
17
|
-
import {
|
|
18
|
-
DEFAULT_SNAPSHOT_MODELS,
|
|
19
|
-
resolveAiSetupStatus
|
|
20
|
-
} from "../core/ai-model.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 detectPackageManager() {
|
|
30
|
-
if (existsSync(join(REPO_ROOT, "pnpm-lock.yaml"))) return "pnpm";
|
|
31
|
-
if (existsSync(join(REPO_ROOT, "yarn.lock"))) return "yarn";
|
|
32
|
-
if (existsSync(join(REPO_ROOT, "bun.lockb"))) return "bun";
|
|
33
|
-
return "npm";
|
|
34
|
-
}
|
|
35
|
-
function installCommand(pkgManager) {
|
|
36
|
-
switch (pkgManager) {
|
|
37
|
-
case "yarn":
|
|
38
|
-
return "yarn add";
|
|
39
|
-
case "bun":
|
|
40
|
-
return "bun add";
|
|
41
|
-
case "pnpm":
|
|
42
|
-
return "pnpm add";
|
|
43
|
-
default:
|
|
44
|
-
return "npm install";
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function isSdkInstalled(sdkPackage) {
|
|
48
|
-
try {
|
|
49
|
-
const result = spawnSync("node", ["-e", `require.resolve("${sdkPackage}")`], {
|
|
50
|
-
cwd: REPO_ROOT,
|
|
51
|
-
stdio: "pipe"
|
|
52
|
-
});
|
|
53
|
-
return result.status === 0;
|
|
54
|
-
} catch {
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
function installSdkIfNeeded(provider) {
|
|
59
|
-
const sdkPackage = PROVIDER_SDK_PACKAGES[provider];
|
|
60
|
-
if (isSdkInstalled(sdkPackage)) return;
|
|
61
|
-
const pkgManager = detectPackageManager();
|
|
62
|
-
const cmd = installCommand(pkgManager);
|
|
63
|
-
console.log(`
|
|
64
|
-
Installing ${sdkPackage}...`);
|
|
65
|
-
const result = spawnSync(cmd, [sdkPackage], {
|
|
66
|
-
cwd: REPO_ROOT,
|
|
67
|
-
stdio: "inherit",
|
|
68
|
-
shell: true
|
|
69
|
-
});
|
|
70
|
-
if (result.status === 0) {
|
|
71
|
-
console.log(`\u2713 Installed ${sdkPackage}`);
|
|
72
|
-
} else {
|
|
73
|
-
console.error(
|
|
74
|
-
`\u2717 Failed to install ${sdkPackage}. Install it manually: ${cmd} ${sdkPackage}`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const PROVIDER_CHOICES = [
|
|
79
|
-
{
|
|
80
|
-
key: "1",
|
|
81
|
-
label: "OpenAI",
|
|
82
|
-
provider: "openai",
|
|
83
|
-
envVar: "OPENAI_API_KEY",
|
|
84
|
-
envHint: "Get your key at https://platform.openai.com/api-keys"
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
key: "2",
|
|
88
|
-
label: "Anthropic",
|
|
89
|
-
provider: "anthropic",
|
|
90
|
-
envVar: "ANTHROPIC_API_KEY",
|
|
91
|
-
envHint: "Get your key at https://console.anthropic.com/settings/keys"
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
key: "3",
|
|
95
|
-
label: "Google Gemini",
|
|
96
|
-
provider: "google",
|
|
97
|
-
envVar: "GEMINI_API_KEY",
|
|
98
|
-
envHint: "Get your key at https://aistudio.google.com/apikey"
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
key: "4",
|
|
102
|
-
label: "Google Vertex AI",
|
|
103
|
-
provider: "vertex",
|
|
104
|
-
envVar: "GOOGLE_CLOUD_PROJECT",
|
|
105
|
-
envHint: "Requires `gcloud auth application-default login` and a GCP project ID"
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
key: "5",
|
|
109
|
-
label: "OpenRouter",
|
|
110
|
-
provider: "openrouter",
|
|
111
|
-
envVar: "OPENROUTER_API_KEY",
|
|
112
|
-
envHint: "Get your key at https://openrouter.ai/settings/keys"
|
|
113
|
-
}
|
|
114
|
-
];
|
|
115
|
-
function promptUser(rl, question) {
|
|
116
|
-
return new Promise((resolve) => {
|
|
117
|
-
rl.question(question, (answer) => {
|
|
118
|
-
resolve(answer.trim());
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
function providerLabel(provider) {
|
|
123
|
-
const choice = PROVIDER_CHOICES.find((c) => c.provider === provider);
|
|
124
|
-
return choice?.label ?? provider;
|
|
125
|
-
}
|
|
126
|
-
function sourceEnvVar(source) {
|
|
127
|
-
if (source.startsWith("env:")) return source.slice(4);
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
function ensurePinnedDefaultModel(status) {
|
|
131
|
-
if (status.source !== "config") {
|
|
132
|
-
writeSnapshotModel(status.model);
|
|
133
|
-
return { ...status, source: "config" };
|
|
134
|
-
}
|
|
135
|
-
return status;
|
|
136
|
-
}
|
|
137
|
-
function printHealthySummary(status) {
|
|
138
|
-
const envVar = sourceEnvVar(status.source);
|
|
139
|
-
if (envVar) {
|
|
140
|
-
console.log(
|
|
141
|
-
`\u2713 Detected ${envVar}. Using ${providerLabel(status.provider)}.`
|
|
142
|
-
);
|
|
143
|
-
} else {
|
|
144
|
-
console.log(`\u2713 Using ${providerLabel(status.provider)} (${status.model}).`);
|
|
145
|
-
}
|
|
146
|
-
console.log(
|
|
147
|
-
"To change: npx libretto ai configure openai | anthropic | gemini | vertex | openrouter"
|
|
148
|
-
);
|
|
149
|
-
}
|
|
150
|
-
function printInvalidAiConfigWarning(status) {
|
|
151
|
-
if (status.kind !== "invalid-config") return;
|
|
152
|
-
console.log("! Existing AI config is invalid:");
|
|
153
|
-
for (const line of status.message.split("\n")) {
|
|
154
|
-
console.log(` ${line}`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
function buildRepairPlan(status) {
|
|
158
|
-
if (status.kind === "configured-missing-credentials") {
|
|
159
|
-
const choice = PROVIDER_CHOICES.find((c) => c.provider === status.provider);
|
|
160
|
-
return {
|
|
161
|
-
kind: "repair-missing-credentials",
|
|
162
|
-
provider: status.provider,
|
|
163
|
-
model: status.model,
|
|
164
|
-
envVar: choice?.envVar ?? `${status.provider.toUpperCase()}_API_KEY`,
|
|
165
|
-
choices: ["switch-provider", "skip"]
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
if (status.kind === "invalid-config") {
|
|
169
|
-
return { kind: "repair-invalid-config", message: status.message };
|
|
170
|
-
}
|
|
171
|
-
return { kind: "no-repair-needed" };
|
|
172
|
-
}
|
|
173
|
-
function formatMissingCredentialsMessage(plan) {
|
|
174
|
-
return `\u2717 ${plan.provider} is configured (model: ${plan.model}), but ${plan.envVar} is not set.`;
|
|
175
|
-
}
|
|
176
|
-
function printSnapshotApiStatus() {
|
|
177
|
-
const status = resolveAiSetupStatus();
|
|
178
|
-
console.log(
|
|
179
|
-
"\nLibretto uses a sub-agent to analyze DOM snapshots. The model is determined by environment variables."
|
|
180
|
-
);
|
|
181
|
-
if (status.kind === "ready") {
|
|
182
|
-
console.log();
|
|
183
|
-
printHealthySummary(status);
|
|
184
|
-
ensurePinnedDefaultModel(status);
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
const plan = buildRepairPlan(status);
|
|
188
|
-
if (plan.kind === "repair-missing-credentials") {
|
|
189
|
-
console.log();
|
|
190
|
-
console.log(formatMissingCredentialsMessage(plan));
|
|
191
|
-
console.log(
|
|
192
|
-
` To fix: add ${plan.envVar} to .env, or run \`npx libretto setup\` interactively to repair.`
|
|
193
|
-
);
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
if (plan.kind === "repair-invalid-config") {
|
|
197
|
-
printInvalidAiConfigWarning(status);
|
|
198
|
-
console.log(" Run `npx libretto setup` interactively to reconfigure.");
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
console.log();
|
|
202
|
-
console.log("\u2717 No snapshot API credentials detected.");
|
|
203
|
-
console.log(" Add one provider to .env:");
|
|
204
|
-
console.log(" OPENAI_API_KEY=...");
|
|
205
|
-
console.log(" ANTHROPIC_API_KEY=...");
|
|
206
|
-
console.log(" GEMINI_API_KEY=... # or GOOGLE_GENERATIVE_AI_API_KEY");
|
|
207
|
-
console.log(
|
|
208
|
-
" GOOGLE_CLOUD_PROJECT=... # plus application default credentials for Vertex"
|
|
209
|
-
);
|
|
210
|
-
console.log(
|
|
211
|
-
" Or run `npx libretto ai configure openai | anthropic | gemini | vertex | openrouter` to set a specific model."
|
|
212
|
-
);
|
|
213
|
-
console.log(
|
|
214
|
-
" Run `npx libretto setup` interactively to set up credentials."
|
|
215
|
-
);
|
|
216
|
-
return false;
|
|
217
|
-
}
|
|
218
|
-
async function promptProviderSelection(rl) {
|
|
219
|
-
console.log(
|
|
220
|
-
"Which model provider would you like to use for snapshot analysis?\n"
|
|
221
|
-
);
|
|
222
|
-
for (const choice of PROVIDER_CHOICES) {
|
|
223
|
-
console.log(` ${choice.key}) ${choice.label}`);
|
|
224
|
-
}
|
|
225
|
-
console.log(" s) Skip for now\n");
|
|
226
|
-
const answer = await promptUser(rl, "Choice: ");
|
|
227
|
-
if (answer.toLowerCase() === "s" || !answer) {
|
|
228
|
-
printSkipMessage();
|
|
229
|
-
return false;
|
|
230
|
-
}
|
|
231
|
-
const selected = PROVIDER_CHOICES.find((choice) => choice.key === answer);
|
|
232
|
-
if (!selected) {
|
|
233
|
-
console.log(`
|
|
234
|
-
Unknown choice "${answer}". Skipping API setup.`);
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
const model = DEFAULT_SNAPSHOT_MODELS[selected.provider];
|
|
238
|
-
writeSnapshotModel(model);
|
|
239
|
-
console.log(`
|
|
240
|
-
\u2713 ${selected.label} selected (model: ${model}).`);
|
|
241
|
-
console.log(`
|
|
242
|
-
Add ${selected.envVar} to your .env file:`);
|
|
243
|
-
console.log(` ${selected.envHint}`);
|
|
244
|
-
installSdkIfNeeded(selected.provider);
|
|
245
|
-
return true;
|
|
246
|
-
}
|
|
247
|
-
function printSkipMessage() {
|
|
248
|
-
console.log(
|
|
249
|
-
"\nSkipped. You can set up API credentials later by rerunning `npx libretto setup`."
|
|
250
|
-
);
|
|
251
|
-
console.log("Or add credentials directly to your .env file:");
|
|
252
|
-
console.log(" OPENAI_API_KEY=...");
|
|
253
|
-
console.log(" ANTHROPIC_API_KEY=...");
|
|
254
|
-
console.log(" GEMINI_API_KEY=...");
|
|
255
|
-
console.log(
|
|
256
|
-
" Or run `npx libretto ai configure openai | anthropic | gemini | vertex | openrouter` to set a specific model."
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
async function runInteractiveApiSetup() {
|
|
260
|
-
const status = resolveAiSetupStatus();
|
|
261
|
-
console.log(
|
|
262
|
-
"\nLibretto uses a sub-agent to analyze DOM snapshots. The model is determined by environment variables."
|
|
263
|
-
);
|
|
264
|
-
if (status.kind === "ready") {
|
|
265
|
-
console.log();
|
|
266
|
-
printHealthySummary(status);
|
|
267
|
-
ensurePinnedDefaultModel(status);
|
|
268
|
-
return;
|
|
269
|
-
}
|
|
270
|
-
const plan = buildRepairPlan(status);
|
|
271
|
-
const rl = createInterface({
|
|
272
|
-
input: process.stdin,
|
|
273
|
-
output: process.stdout
|
|
274
|
-
});
|
|
275
|
-
try {
|
|
276
|
-
if (plan.kind === "repair-missing-credentials") {
|
|
277
|
-
console.log(formatMissingCredentialsMessage(plan));
|
|
278
|
-
console.log(`
|
|
279
|
-
Add ${plan.envVar} to your .env file to fix this.`);
|
|
280
|
-
console.log("");
|
|
281
|
-
console.log("Or switch to a different provider:\n");
|
|
282
|
-
console.log(" 1) Switch to a different provider");
|
|
283
|
-
console.log(" s) Skip for now\n");
|
|
284
|
-
const answer = await promptUser(rl, "Choice: ");
|
|
285
|
-
if (answer === "1") {
|
|
286
|
-
await promptProviderSelection(rl);
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
printSkipMessage();
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
if (plan.kind === "repair-invalid-config") {
|
|
293
|
-
printInvalidAiConfigWarning(status);
|
|
294
|
-
console.log(
|
|
295
|
-
"\nWould you like to reconfigure with a fresh provider selection?\n"
|
|
296
|
-
);
|
|
297
|
-
await promptProviderSelection(rl);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
console.log("\u2717 No snapshot API credentials detected.\n");
|
|
301
|
-
await promptProviderSelection(rl);
|
|
302
|
-
} finally {
|
|
303
|
-
rl.close();
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
12
|
function installBrowsers() {
|
|
307
13
|
console.log("Installing Playwright Chromium...");
|
|
308
14
|
const result = spawnSync("npx", ["playwright", "install", "chromium"], {
|
|
@@ -341,7 +47,7 @@ function copySkills() {
|
|
|
341
47
|
"\n\u26A0\uFE0F No .agents/ or .claude/ directory found. Libretto skills were not installed."
|
|
342
48
|
);
|
|
343
49
|
console.log(
|
|
344
|
-
|
|
50
|
+
` Create one of these directories in your repo root and rerun \`${librettoCommand("setup")}\` to install skills:`
|
|
345
51
|
);
|
|
346
52
|
console.log(` mkdir ${join(REPO_ROOT, ".claude")}`);
|
|
347
53
|
return;
|
|
@@ -389,24 +95,11 @@ const setupCommand = SimpleCLI.command({
|
|
|
389
95
|
console.log("Skipping browser installation (--skip-browsers)");
|
|
390
96
|
}
|
|
391
97
|
copySkills();
|
|
392
|
-
if (process.stdin.isTTY) {
|
|
393
|
-
await runInteractiveApiSetup();
|
|
394
|
-
} else {
|
|
395
|
-
const ready = printSnapshotApiStatus();
|
|
396
|
-
if (!ready) {
|
|
397
|
-
console.log(
|
|
398
|
-
"\nIf you're an agent, request the user to run `npx libretto setup`."
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
98
|
console.log(`
|
|
403
99
|
Config set up at ${LIBRETTO_CONFIG_PATH}`);
|
|
404
100
|
console.log("\n\u2713 libretto setup complete");
|
|
405
101
|
});
|
|
406
102
|
export {
|
|
407
|
-
PROVIDER_CHOICES,
|
|
408
|
-
buildRepairPlan,
|
|
409
|
-
formatMissingCredentialsMessage,
|
|
410
103
|
setupCommand,
|
|
411
104
|
setupInput
|
|
412
105
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { resolveExperiments } from "../core/experiments.js";
|
|
2
3
|
import { createLoggerForSession } from "../core/context.js";
|
|
3
4
|
import {
|
|
4
5
|
generateSessionName,
|
|
@@ -17,6 +18,14 @@ function pageOption(help = "Target a specific page id") {
|
|
|
17
18
|
function integerOption(help) {
|
|
18
19
|
return SimpleCLI.option(z.coerce.number().int().optional(), { help });
|
|
19
20
|
}
|
|
21
|
+
function withExperiments() {
|
|
22
|
+
return async ({
|
|
23
|
+
ctx
|
|
24
|
+
}) => ({
|
|
25
|
+
...ctx,
|
|
26
|
+
experiments: resolveExperiments()
|
|
27
|
+
});
|
|
28
|
+
}
|
|
20
29
|
function withRequiredSession() {
|
|
21
30
|
return async ({ input, ctx }) => {
|
|
22
31
|
if (!input.session) {
|
|
@@ -47,5 +56,6 @@ export {
|
|
|
47
56
|
pageOption,
|
|
48
57
|
sessionOption,
|
|
49
58
|
withAutoSession,
|
|
59
|
+
withExperiments,
|
|
50
60
|
withRequiredSession
|
|
51
61
|
};
|
|
@@ -1,13 +1,14 @@
|
|
|
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
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
import {
|
|
5
|
+
pageOption,
|
|
6
|
+
sessionOption,
|
|
7
|
+
withRequiredSession
|
|
8
|
+
} from "./shared.js";
|
|
9
|
+
import { DaemonClient } from "../core/daemon/ipc.js";
|
|
10
|
+
import { librettoCommand } from "../../shared/package-manager.js";
|
|
11
|
+
import { renderSnapshot } from "../../shared/snapshot/render-snapshot.js";
|
|
11
12
|
const FALLBACK_SNAPSHOT_VIEWPORT = { width: 1280, height: 800 };
|
|
12
13
|
function isZeroViewport(value) {
|
|
13
14
|
return typeof value === "number" && value <= 0;
|
|
@@ -63,75 +64,56 @@ async function forceSnapshotViewport(page, viewport, logger, session, pageId, re
|
|
|
63
64
|
viewport
|
|
64
65
|
});
|
|
65
66
|
}
|
|
66
|
-
async function
|
|
67
|
-
|
|
68
|
-
const client = new DaemonClient(daemonSocketPath);
|
|
69
|
-
const { pngPath, htmlPath, snapshotRunId, pageUrl, title } = await client.snapshot({ pageId });
|
|
70
|
-
const htmlContent = readFileSync(htmlPath, "utf8");
|
|
71
|
-
const condenseResult = condenseDom(htmlContent);
|
|
72
|
-
const condensedHtmlPath = htmlPath.replace(/\.html$/, ".condensed.html");
|
|
73
|
-
writeFileSync(condensedHtmlPath, condenseResult.html);
|
|
74
|
-
logger.info("snapshot-daemon-success", {
|
|
75
|
-
session,
|
|
76
|
-
pageUrl,
|
|
77
|
-
title,
|
|
78
|
-
pngPath,
|
|
79
|
-
htmlPath,
|
|
80
|
-
condensedHtmlPath,
|
|
81
|
-
snapshotRunId,
|
|
82
|
-
domCondenseStats: {
|
|
83
|
-
originalLength: condenseResult.originalLength,
|
|
84
|
-
condensedLength: condenseResult.condensedLength,
|
|
85
|
-
reductions: condenseResult.reductions
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
return { pngPath, htmlPath, condensedHtmlPath, baseName: snapshotRunId };
|
|
89
|
-
}
|
|
90
|
-
async function runSnapshot(session, logger, pageId, objective, context) {
|
|
91
|
-
const normalizedObjective = objective.trim();
|
|
92
|
-
const normalizedContext = context.trim();
|
|
93
|
-
const snapshotModel = readSnapshotModel();
|
|
94
|
-
resolveSnapshotApiModelOrThrow(snapshotModel);
|
|
95
|
-
const state = readSessionState(session, logger);
|
|
96
|
-
if (!state?.daemonSocketPath) {
|
|
67
|
+
async function runCompactSnapshot(args) {
|
|
68
|
+
if (!args.daemonSocketPath) {
|
|
97
69
|
throw new Error(
|
|
98
|
-
`Session "${session}" has no daemon socket. The browser daemon may have crashed. Close and reopen the session:
|
|
70
|
+
`Session "${args.session}" has no daemon socket. The browser daemon may have crashed. Close and reopen the session: ${librettoCommand(`close --session ${args.session}`)}`
|
|
99
71
|
);
|
|
100
72
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
73
|
+
args.logger.info("compact-snapshot-via-daemon", {
|
|
74
|
+
session: args.session,
|
|
75
|
+
pageId: args.pageId,
|
|
76
|
+
ref: args.ref
|
|
77
|
+
});
|
|
78
|
+
const client = await DaemonClient.connect(args.daemonSocketPath);
|
|
79
|
+
let result;
|
|
80
|
+
try {
|
|
81
|
+
result = await client.snapshot({
|
|
82
|
+
pageId: args.pageId,
|
|
83
|
+
useCachedSnapshot: args.ref !== void 0
|
|
84
|
+
});
|
|
85
|
+
} finally {
|
|
86
|
+
client.destroy();
|
|
87
|
+
}
|
|
88
|
+
console.log(`Screenshot at ${result.pngPath}`);
|
|
89
|
+
console.log(renderSnapshot(result.snapshot, args.ref));
|
|
90
|
+
console.log(
|
|
91
|
+
`Hint: Use ${librettoCommand(`snapshot <ref> --session ${args.session}`)} to inspect a subtree.`
|
|
92
|
+
);
|
|
115
93
|
}
|
|
116
94
|
const snapshotInput = SimpleCLI.input({
|
|
117
|
-
positionals: [
|
|
95
|
+
positionals: [
|
|
96
|
+
SimpleCLI.positional("ref", z.string().optional(), {
|
|
97
|
+
help: "Optional element ref to scope output to that subtree (for example, l16 or e16)"
|
|
98
|
+
})
|
|
99
|
+
],
|
|
118
100
|
named: {
|
|
119
101
|
session: sessionOption(),
|
|
120
102
|
page: pageOption(),
|
|
121
|
-
objective: SimpleCLI.option(z.string()),
|
|
122
|
-
context: SimpleCLI.option(z.string())
|
|
103
|
+
objective: SimpleCLI.option(z.string().optional()),
|
|
104
|
+
context: SimpleCLI.option(z.string().optional())
|
|
123
105
|
}
|
|
124
106
|
});
|
|
125
107
|
const snapshotCommand = SimpleCLI.command({
|
|
126
|
-
description: "Capture
|
|
108
|
+
description: "Capture a screenshot and compact accessibility snapshot"
|
|
127
109
|
}).input(snapshotInput).use(withRequiredSession()).handle(async ({ input, ctx }) => {
|
|
128
|
-
await
|
|
129
|
-
ctx.session,
|
|
130
|
-
ctx.
|
|
131
|
-
|
|
132
|
-
input.
|
|
133
|
-
input.
|
|
134
|
-
);
|
|
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
|
+
});
|
|
135
117
|
});
|
|
136
118
|
export {
|
|
137
119
|
FALLBACK_SNAPSHOT_VIEWPORT,
|
|
@@ -1,42 +1,5 @@
|
|
|
1
|
-
import { LIBRETTO_CONFIG_PATH } from "../core/context.js";
|
|
2
|
-
import { resolveAiSetupStatus } from "../core/ai-model.js";
|
|
3
1
|
import { listRunningSessions } from "../core/session.js";
|
|
4
2
|
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
5
|
-
function printAiStatus(status) {
|
|
6
|
-
console.log("AI configuration:");
|
|
7
|
-
switch (status.kind) {
|
|
8
|
-
case "ready":
|
|
9
|
-
console.log(` \u2713 Snapshot model: ${status.model}`);
|
|
10
|
-
if (status.source === "config") {
|
|
11
|
-
console.log(` Config: ${LIBRETTO_CONFIG_PATH}`);
|
|
12
|
-
} else {
|
|
13
|
-
console.log(` Source: ${status.source}`);
|
|
14
|
-
}
|
|
15
|
-
console.log(
|
|
16
|
-
" To change: npx libretto ai configure openai | anthropic | gemini | vertex | openrouter"
|
|
17
|
-
);
|
|
18
|
-
break;
|
|
19
|
-
case "configured-missing-credentials":
|
|
20
|
-
console.log(
|
|
21
|
-
` \u2717 ${status.provider} is configured (model: ${status.model}), but credentials are missing.`
|
|
22
|
-
);
|
|
23
|
-
console.log(" Run `npx libretto setup` to repair.");
|
|
24
|
-
break;
|
|
25
|
-
case "invalid-config":
|
|
26
|
-
console.log(" \u2717 Config is invalid:");
|
|
27
|
-
for (const line of status.message.split("\n")) {
|
|
28
|
-
console.log(` ${line}`);
|
|
29
|
-
}
|
|
30
|
-
console.log(" Run `npx libretto setup` to reconfigure.");
|
|
31
|
-
break;
|
|
32
|
-
case "unconfigured":
|
|
33
|
-
console.log(" \u2717 No AI model configured.");
|
|
34
|
-
console.log(
|
|
35
|
-
" Run `npx libretto setup` or `npx libretto ai configure` to set up."
|
|
36
|
-
);
|
|
37
|
-
break;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
3
|
function printOpenSessions(sessions) {
|
|
41
4
|
console.log("\nOpen sessions:");
|
|
42
5
|
if (sessions.length === 0) {
|
|
@@ -50,10 +13,8 @@ function printOpenSessions(sessions) {
|
|
|
50
13
|
}
|
|
51
14
|
}
|
|
52
15
|
const statusCommand = SimpleCLI.command({
|
|
53
|
-
description: "Show workspace status
|
|
16
|
+
description: "Show workspace status and open sessions"
|
|
54
17
|
}).input(SimpleCLI.input({ positionals: [], named: {} })).handle(async () => {
|
|
55
|
-
const aiStatus = resolveAiSetupStatus();
|
|
56
|
-
printAiStatus(aiStatus);
|
|
57
18
|
const sessions = listRunningSessions();
|
|
58
19
|
printOpenSessions(sessions);
|
|
59
20
|
});
|