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
|
@@ -105,6 +105,7 @@ async function getProviderModel(
|
|
|
105
105
|
if (!apiKey) {
|
|
106
106
|
throw new Error(missingProviderCredentialsMessage(provider));
|
|
107
107
|
}
|
|
108
|
+
// @lintc-ignore Human-approved: we don't want to import unless the user is using that subagent.
|
|
108
109
|
const { createGoogleGenerativeAI } = await import("@ai-sdk/google");
|
|
109
110
|
const google = createGoogleGenerativeAI({ apiKey });
|
|
110
111
|
return google(modelId);
|
|
@@ -114,6 +115,7 @@ async function getProviderModel(
|
|
|
114
115
|
if (!project) {
|
|
115
116
|
throw new Error(missingProviderCredentialsMessage(provider));
|
|
116
117
|
}
|
|
118
|
+
// @lintc-ignore Human-approved: we don't want to import unless the user is using that subagent.
|
|
117
119
|
const { createVertex } = await import("@ai-sdk/google-vertex");
|
|
118
120
|
const vertex = createVertex({
|
|
119
121
|
project,
|
|
@@ -126,6 +128,7 @@ async function getProviderModel(
|
|
|
126
128
|
if (!apiKey) {
|
|
127
129
|
throw new Error(missingProviderCredentialsMessage(provider));
|
|
128
130
|
}
|
|
131
|
+
// @lintc-ignore Human-approved: we don't want to import unless the user is using that subagent.
|
|
129
132
|
const { createAnthropic } = await import("@ai-sdk/anthropic");
|
|
130
133
|
const anthropic = createAnthropic({ apiKey });
|
|
131
134
|
return anthropic(modelId);
|
|
@@ -135,6 +138,7 @@ async function getProviderModel(
|
|
|
135
138
|
if (!apiKey) {
|
|
136
139
|
throw new Error(missingProviderCredentialsMessage(provider));
|
|
137
140
|
}
|
|
141
|
+
// @lintc-ignore Human-approved: we don't want to import unless the user is using that subagent.
|
|
138
142
|
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
139
143
|
const openai = createOpenAI({ apiKey });
|
|
140
144
|
return openai(modelId);
|
|
@@ -144,6 +148,7 @@ async function getProviderModel(
|
|
|
144
148
|
if (!apiKey) {
|
|
145
149
|
throw new Error(missingProviderCredentialsMessage(provider));
|
|
146
150
|
}
|
|
151
|
+
// @lintc-ignore Human-approved: we don't want to import unless the user is using that subagent.
|
|
147
152
|
const { createOpenAI } = await import("@ai-sdk/openai");
|
|
148
153
|
const openrouter = createOpenAI({
|
|
149
154
|
apiKey,
|
|
@@ -37,6 +37,7 @@ export async function loadDefaultWorkflow(
|
|
|
37
37
|
): Promise<ExportedLibrettoWorkflow> {
|
|
38
38
|
let loadedModule: Record<string, unknown>;
|
|
39
39
|
try {
|
|
40
|
+
// @lintc-ignore Human-approved: user workflow files must be loaded dynamically from the CLI argument.
|
|
40
41
|
loadedModule = (await import(pathToFileURL(absolutePath).href)) as Record<
|
|
41
42
|
string,
|
|
42
43
|
unknown
|
package/src/cli/index.ts
CHANGED
package/src/cli/router.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { aiCommands } from "./commands/ai.js";
|
|
2
1
|
import { authCommands } from "./commands/auth.js";
|
|
3
2
|
import { billingCommands } from "./commands/billing.js";
|
|
4
3
|
import { browserCommands } from "./commands/browser.js";
|
|
@@ -9,16 +8,20 @@ import { setupCommand } from "./commands/setup.js";
|
|
|
9
8
|
import { statusCommand } from "./commands/status.js";
|
|
10
9
|
import { snapshotCommand } from "./commands/snapshot.js";
|
|
11
10
|
import { librettoCommand } from "../shared/package-manager.js";
|
|
12
|
-
import { SimpleCLI } from "
|
|
11
|
+
import { SimpleCLI } from "affordance";
|
|
13
12
|
|
|
14
13
|
export const cliRoutes = {
|
|
15
14
|
...browserCommands,
|
|
16
|
-
|
|
15
|
+
cloud: SimpleCLI.group({
|
|
16
|
+
description: "Libretto Cloud commands",
|
|
17
|
+
routes: {
|
|
18
|
+
deploy: deployCommand,
|
|
19
|
+
auth: authCommands,
|
|
20
|
+
billing: billingCommands,
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
17
23
|
experiments: experimentsCommand,
|
|
18
24
|
...executionCommands,
|
|
19
|
-
ai: aiCommands,
|
|
20
|
-
auth: authCommands,
|
|
21
|
-
billing: billingCommands,
|
|
22
25
|
setup: setupCommand,
|
|
23
26
|
status: statusCommand,
|
|
24
27
|
snapshot: snapshotCommand,
|
|
@@ -131,12 +131,12 @@ function wrapLocatorActions(
|
|
|
131
131
|
try {
|
|
132
132
|
const result = await orig(...args);
|
|
133
133
|
if (opts.visualize) {
|
|
134
|
-
enqueue(page, () => visualizeAfterAction(page));
|
|
134
|
+
void enqueue(page, () => visualizeAfterAction(page));
|
|
135
135
|
}
|
|
136
136
|
return result;
|
|
137
137
|
} catch (err: any) {
|
|
138
138
|
if (opts.visualize) {
|
|
139
|
-
enqueue(page, () => visualizeAfterAction(page));
|
|
139
|
+
void enqueue(page, () => visualizeAfterAction(page));
|
|
140
140
|
}
|
|
141
141
|
// Enrich timeout errors for pointer actions
|
|
142
142
|
if (POINTER_ACTIONS.has(method) && isTimeoutError(err)) {
|
|
@@ -323,12 +323,12 @@ export async function installInstrumentation(
|
|
|
323
323
|
try {
|
|
324
324
|
const result = await orig(...args);
|
|
325
325
|
if (visualize) {
|
|
326
|
-
enqueue(page, () => visualizeAfterAction(page));
|
|
326
|
+
void enqueue(page, () => visualizeAfterAction(page));
|
|
327
327
|
}
|
|
328
328
|
return result;
|
|
329
329
|
} catch (err: any) {
|
|
330
330
|
if (visualize) {
|
|
331
|
-
enqueue(page, () => visualizeAfterAction(page));
|
|
331
|
+
void enqueue(page, () => visualizeAfterAction(page));
|
|
332
332
|
}
|
|
333
333
|
if (
|
|
334
334
|
POINTER_ACTIONS.has(method) &&
|
|
@@ -6,6 +6,7 @@ import { expect, test as base } from "vitest";
|
|
|
6
6
|
import { createIpcPeer, type IpcPeer } from "./ipc.js";
|
|
7
7
|
import {
|
|
8
8
|
connectToIpcSocket,
|
|
9
|
+
isWindowsNamedPipePath,
|
|
9
10
|
listenForIpcConnections,
|
|
10
11
|
} from "./socket-transport.js";
|
|
11
12
|
|
|
@@ -64,6 +65,11 @@ test("sends concurrent calls over one socket", async ({ socketPath }) => {
|
|
|
64
65
|
await expect(stat(socketPath)).rejects.toThrow();
|
|
65
66
|
});
|
|
66
67
|
|
|
68
|
+
test("recognizes Windows named pipe paths", () => {
|
|
69
|
+
expect(isWindowsNamedPipePath("\\\\.\\pipe\\libretto-abc123")).toBe(true);
|
|
70
|
+
expect(isWindowsNamedPipePath("/tmp/libretto-501-abc123.sock")).toBe(false);
|
|
71
|
+
});
|
|
72
|
+
|
|
67
73
|
test("rejects pending calls when the socket closes", async ({ socketPath }) => {
|
|
68
74
|
const serverPeers: Array<IpcPeer<ClientApi>> = [];
|
|
69
75
|
const server = await listenForIpcConnections(socketPath, (transport) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { rm } from "node:fs/promises";
|
|
1
|
+
import { mkdir, rm } from "node:fs/promises";
|
|
2
2
|
import {
|
|
3
3
|
createServer,
|
|
4
4
|
createConnection,
|
|
@@ -6,7 +6,6 @@ import {
|
|
|
6
6
|
type Socket,
|
|
7
7
|
} from "node:net";
|
|
8
8
|
import { dirname } from "node:path";
|
|
9
|
-
import { mkdir } from "node:fs/promises";
|
|
10
9
|
import type { IpcProtocolMessage, IpcTransport } from "./ipc.js";
|
|
11
10
|
|
|
12
11
|
function createJsonSocketTransport(
|
|
@@ -109,13 +108,12 @@ export async function listenOnIpcSocket(
|
|
|
109
108
|
server: Server,
|
|
110
109
|
socketPath: string,
|
|
111
110
|
): Promise<void> {
|
|
112
|
-
await
|
|
113
|
-
await rm(socketPath, { force: true });
|
|
111
|
+
await prepareIpcSocketPath(socketPath);
|
|
114
112
|
|
|
115
113
|
const originalClose = server.close.bind(server);
|
|
116
114
|
server.close = ((callback?: (error?: Error) => void) => {
|
|
117
115
|
return originalClose((error?: Error) => {
|
|
118
|
-
void
|
|
116
|
+
void removeStaleSocketFile(socketPath).finally(() => callback?.(error));
|
|
119
117
|
});
|
|
120
118
|
}) as Server["close"];
|
|
121
119
|
|
|
@@ -135,6 +133,23 @@ export async function listenOnIpcSocket(
|
|
|
135
133
|
});
|
|
136
134
|
}
|
|
137
135
|
|
|
136
|
+
export function isWindowsNamedPipePath(socketPath: string): boolean {
|
|
137
|
+
return socketPath.startsWith("\\\\.\\pipe\\");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function prepareIpcSocketPath(socketPath: string): Promise<void> {
|
|
141
|
+
if (isWindowsNamedPipePath(socketPath)) return;
|
|
142
|
+
|
|
143
|
+
await mkdir(dirname(socketPath), { recursive: true });
|
|
144
|
+
await removeStaleSocketFile(socketPath);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function removeStaleSocketFile(socketPath: string): Promise<void> {
|
|
148
|
+
if (isWindowsNamedPipePath(socketPath)) return;
|
|
149
|
+
|
|
150
|
+
await rm(socketPath, { force: true });
|
|
151
|
+
}
|
|
152
|
+
|
|
138
153
|
async function connectSocket(socketPath: string): Promise<Socket> {
|
|
139
154
|
const socket = createConnection(socketPath);
|
|
140
155
|
|
package/dist/cli/commands/ai.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import {
|
|
3
|
-
readSnapshotModel,
|
|
4
|
-
writeSnapshotModel,
|
|
5
|
-
clearSnapshotModel
|
|
6
|
-
} from "../core/config.js";
|
|
7
|
-
import { LIBRETTO_CONFIG_PATH } from "../core/context.js";
|
|
8
|
-
import { DEFAULT_SNAPSHOT_MODELS } from "../core/ai-model.js";
|
|
9
|
-
import { librettoCommand } from "../../shared/package-manager.js";
|
|
10
|
-
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
11
|
-
const PROVIDER_ALIASES = {
|
|
12
|
-
claude: DEFAULT_SNAPSHOT_MODELS.anthropic,
|
|
13
|
-
gemini: DEFAULT_SNAPSHOT_MODELS.google,
|
|
14
|
-
google: DEFAULT_SNAPSHOT_MODELS.google
|
|
15
|
-
};
|
|
16
|
-
const CONFIGURE_PROVIDERS = [
|
|
17
|
-
"openai",
|
|
18
|
-
"anthropic",
|
|
19
|
-
"gemini",
|
|
20
|
-
"vertex"
|
|
21
|
-
];
|
|
22
|
-
function formatConfigureProviders(separator = " | ") {
|
|
23
|
-
return CONFIGURE_PROVIDERS.join(separator);
|
|
24
|
-
}
|
|
25
|
-
function printSnapshotModelConfig(model, configPath) {
|
|
26
|
-
console.log(`Snapshot model: ${model}`);
|
|
27
|
-
console.log(`Config file: ${configPath}`);
|
|
28
|
-
}
|
|
29
|
-
function resolveModelFromInput(input) {
|
|
30
|
-
const trimmed = input.trim();
|
|
31
|
-
if (!trimmed) return null;
|
|
32
|
-
if (trimmed.includes("/")) return trimmed;
|
|
33
|
-
const normalized = trimmed.toLowerCase();
|
|
34
|
-
return DEFAULT_SNAPSHOT_MODELS[normalized] ?? PROVIDER_ALIASES[normalized] ?? null;
|
|
35
|
-
}
|
|
36
|
-
function runAiConfigure(input, options = {}) {
|
|
37
|
-
const configureCommandName = options.configureCommandName ?? librettoCommand("ai configure");
|
|
38
|
-
const configPath = options.configPath ?? LIBRETTO_CONFIG_PATH;
|
|
39
|
-
const presetArg = input.preset?.trim();
|
|
40
|
-
if (!presetArg && !input.clear) {
|
|
41
|
-
const model2 = readSnapshotModel(configPath);
|
|
42
|
-
if (!model2) {
|
|
43
|
-
console.log(
|
|
44
|
-
`No snapshot model set. Choose a default model: ${configureCommandName} ${formatConfigureProviders()}`
|
|
45
|
-
);
|
|
46
|
-
console.log(
|
|
47
|
-
"Provider credentials still come from your shell or .env file."
|
|
48
|
-
);
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
printSnapshotModelConfig(model2, configPath);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (input.clear) {
|
|
55
|
-
const removed = clearSnapshotModel(configPath);
|
|
56
|
-
if (removed) {
|
|
57
|
-
console.log(`Cleared snapshot model config: ${configPath}`);
|
|
58
|
-
} else {
|
|
59
|
-
console.log("No snapshot model was set.");
|
|
60
|
-
}
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
const model = resolveModelFromInput(presetArg);
|
|
64
|
-
if (!model) {
|
|
65
|
-
console.log(
|
|
66
|
-
`Usage: ${configureCommandName} <${CONFIGURE_PROVIDERS.join("|")}|provider/model-id>
|
|
67
|
-
${configureCommandName}
|
|
68
|
-
${configureCommandName} --clear`
|
|
69
|
-
);
|
|
70
|
-
throw new Error(
|
|
71
|
-
`Invalid provider or model. Use one of: ${formatConfigureProviders()}, or a full model string like "openai/gpt-4o".`
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
writeSnapshotModel(model, configPath);
|
|
75
|
-
console.log("Snapshot model saved.");
|
|
76
|
-
printSnapshotModelConfig(model, configPath);
|
|
77
|
-
}
|
|
78
|
-
const aiConfigureInput = SimpleCLI.input({
|
|
79
|
-
positionals: [
|
|
80
|
-
SimpleCLI.positional("preset", z.string().optional(), {
|
|
81
|
-
help: "Provider shorthand or provider/model-id"
|
|
82
|
-
})
|
|
83
|
-
],
|
|
84
|
-
named: {
|
|
85
|
-
clear: SimpleCLI.flag({ help: "Clear existing AI config" })
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
const aiCommands = SimpleCLI.group({
|
|
89
|
-
description: "AI commands",
|
|
90
|
-
routes: {
|
|
91
|
-
configure: SimpleCLI.command({
|
|
92
|
-
description: "Configure AI runtime"
|
|
93
|
-
}).input(aiConfigureInput).handle(async ({ input }) => {
|
|
94
|
-
runAiConfigure(
|
|
95
|
-
{
|
|
96
|
-
clear: input.clear,
|
|
97
|
-
preset: input.preset
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
configureCommandName: librettoCommand("ai configure")
|
|
101
|
-
}
|
|
102
|
-
);
|
|
103
|
-
})
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
export {
|
|
107
|
-
aiCommands,
|
|
108
|
-
aiConfigureInput,
|
|
109
|
-
runAiConfigure
|
|
110
|
-
};
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
import { readSnapshotModel } from "./config.js";
|
|
2
|
-
import { LIBRETTO_CONFIG_PATH } from "./context.js";
|
|
3
|
-
import { librettoCommand } from "../../shared/package-manager.js";
|
|
4
|
-
import {
|
|
5
|
-
hasProviderCredentials,
|
|
6
|
-
parseModel
|
|
7
|
-
} from "./resolve-model.js";
|
|
8
|
-
import { parseDotEnvAssignment } from "../../shared/env/load-env.js";
|
|
9
|
-
const DEFAULT_SNAPSHOT_MODELS = {
|
|
10
|
-
openai: "openai/gpt-5.4",
|
|
11
|
-
anthropic: "anthropic/claude-sonnet-4-6",
|
|
12
|
-
google: "google/gemini-3-flash-preview",
|
|
13
|
-
vertex: "vertex/gemini-2.5-flash",
|
|
14
|
-
openrouter: "openrouter/free"
|
|
15
|
-
};
|
|
16
|
-
function detectProviderEnvVar(provider, env = process.env) {
|
|
17
|
-
switch (provider) {
|
|
18
|
-
case "openai":
|
|
19
|
-
return env.OPENAI_API_KEY?.trim() ? "OPENAI_API_KEY" : null;
|
|
20
|
-
case "anthropic":
|
|
21
|
-
return env.ANTHROPIC_API_KEY?.trim() ? "ANTHROPIC_API_KEY" : null;
|
|
22
|
-
case "google":
|
|
23
|
-
if (env.GEMINI_API_KEY?.trim()) return "GEMINI_API_KEY";
|
|
24
|
-
if (env.GOOGLE_GENERATIVE_AI_API_KEY?.trim())
|
|
25
|
-
return "GOOGLE_GENERATIVE_AI_API_KEY";
|
|
26
|
-
return null;
|
|
27
|
-
case "vertex":
|
|
28
|
-
if (env.GOOGLE_CLOUD_PROJECT?.trim()) return "GOOGLE_CLOUD_PROJECT";
|
|
29
|
-
if (env.GCLOUD_PROJECT?.trim()) return "GCLOUD_PROJECT";
|
|
30
|
-
return null;
|
|
31
|
-
case "openrouter":
|
|
32
|
-
return env.OPENROUTER_API_KEY?.trim() ? "OPENROUTER_API_KEY" : null;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
class SnapshotApiUnavailableError extends Error {
|
|
36
|
-
constructor(message) {
|
|
37
|
-
super(message);
|
|
38
|
-
this.name = "SnapshotApiUnavailableError";
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
function providerSetupSentence(provider) {
|
|
42
|
-
switch (provider) {
|
|
43
|
-
case "openai":
|
|
44
|
-
return "Add OPENAI_API_KEY to .env or as a shell environment variable.";
|
|
45
|
-
case "anthropic":
|
|
46
|
-
return "Add ANTHROPIC_API_KEY to .env or as a shell environment variable.";
|
|
47
|
-
case "google":
|
|
48
|
-
return "Add GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY to .env or as a shell environment variable.";
|
|
49
|
-
case "vertex":
|
|
50
|
-
return "Add GOOGLE_CLOUD_PROJECT or GCLOUD_PROJECT to .env or as a shell environment variable, and make sure application default credentials are configured.";
|
|
51
|
-
case "openrouter":
|
|
52
|
-
return "Add OPENROUTER_API_KEY to .env or as a shell environment variable.";
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
function defaultModelCommandLine() {
|
|
56
|
-
return librettoCommand(
|
|
57
|
-
"ai configure openai | anthropic | gemini | vertex | openrouter"
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
function providerMissingCredentialSummary(provider) {
|
|
61
|
-
switch (provider) {
|
|
62
|
-
case "openai":
|
|
63
|
-
return "OPENAI_API_KEY is missing";
|
|
64
|
-
case "anthropic":
|
|
65
|
-
return "ANTHROPIC_API_KEY is missing";
|
|
66
|
-
case "google":
|
|
67
|
-
return "GEMINI_API_KEY and GOOGLE_GENERATIVE_AI_API_KEY are missing";
|
|
68
|
-
case "vertex":
|
|
69
|
-
return "GOOGLE_CLOUD_PROJECT and GCLOUD_PROJECT are missing";
|
|
70
|
-
case "openrouter":
|
|
71
|
-
return "OPENROUTER_API_KEY is missing";
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function noSnapshotApiConfiguredMessage() {
|
|
75
|
-
return [
|
|
76
|
-
"Failed to analyze snapshot because no snapshot analyzer is configured.",
|
|
77
|
-
`Add OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY, GOOGLE_CLOUD_PROJECT, or OPENROUTER_API_KEY to .env or as a shell environment variable, or choose a default model with \`${defaultModelCommandLine()}\`.`,
|
|
78
|
-
`For more info, run \`${librettoCommand("setup")}\`.`
|
|
79
|
-
].join(" ");
|
|
80
|
-
}
|
|
81
|
-
function missingProviderSnapshotMessage(selection) {
|
|
82
|
-
const configuredSource = selection.source === "config" ? ` in ${LIBRETTO_CONFIG_PATH}` : " from process env or .env";
|
|
83
|
-
return [
|
|
84
|
-
`Failed to analyze snapshot because ${selection.provider} is configured${configuredSource}, but ${providerMissingCredentialSummary(selection.provider)}.`,
|
|
85
|
-
providerSetupSentence(selection.provider),
|
|
86
|
-
`For more info, run \`${librettoCommand("setup")}\`.`
|
|
87
|
-
].join(" ");
|
|
88
|
-
}
|
|
89
|
-
function inferAutoSnapshotModel() {
|
|
90
|
-
const providersInPriorityOrder = [
|
|
91
|
-
"openai",
|
|
92
|
-
"anthropic",
|
|
93
|
-
"google",
|
|
94
|
-
"vertex",
|
|
95
|
-
"openrouter"
|
|
96
|
-
];
|
|
97
|
-
for (const provider of providersInPriorityOrder) {
|
|
98
|
-
const envVar = detectProviderEnvVar(provider);
|
|
99
|
-
if (!envVar) continue;
|
|
100
|
-
return {
|
|
101
|
-
model: DEFAULT_SNAPSHOT_MODELS[provider],
|
|
102
|
-
provider,
|
|
103
|
-
source: `env:${envVar}`
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
function resolveSnapshotApiModel(snapshotModel = readSnapshotModel()) {
|
|
109
|
-
if (snapshotModel) {
|
|
110
|
-
const { provider } = parseModel(snapshotModel);
|
|
111
|
-
return {
|
|
112
|
-
model: snapshotModel,
|
|
113
|
-
provider,
|
|
114
|
-
source: "config"
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
return inferAutoSnapshotModel();
|
|
118
|
-
}
|
|
119
|
-
function resolveSnapshotApiModelOrThrow(snapshotModel = readSnapshotModel()) {
|
|
120
|
-
const selection = resolveSnapshotApiModel(snapshotModel);
|
|
121
|
-
if (!selection) {
|
|
122
|
-
throw new SnapshotApiUnavailableError(noSnapshotApiConfiguredMessage());
|
|
123
|
-
}
|
|
124
|
-
if (!hasProviderCredentials(selection.provider)) {
|
|
125
|
-
throw new SnapshotApiUnavailableError(
|
|
126
|
-
missingProviderSnapshotMessage(selection)
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
return selection;
|
|
130
|
-
}
|
|
131
|
-
function isSnapshotApiUnavailableError(error) {
|
|
132
|
-
return error instanceof SnapshotApiUnavailableError;
|
|
133
|
-
}
|
|
134
|
-
function readSnapshotModelSafely(configPath) {
|
|
135
|
-
try {
|
|
136
|
-
return { ok: true, model: readSnapshotModel(configPath) };
|
|
137
|
-
} catch (err) {
|
|
138
|
-
return {
|
|
139
|
-
ok: false,
|
|
140
|
-
message: err instanceof Error ? err.message : String(err)
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
function resolveAiSetupStatus(configPath = LIBRETTO_CONFIG_PATH) {
|
|
145
|
-
const result = readSnapshotModelSafely(configPath);
|
|
146
|
-
if (!result.ok) {
|
|
147
|
-
return { kind: "invalid-config", message: result.message };
|
|
148
|
-
}
|
|
149
|
-
if (result.model) {
|
|
150
|
-
let selection;
|
|
151
|
-
try {
|
|
152
|
-
selection = resolveSnapshotApiModel(result.model);
|
|
153
|
-
} catch (err) {
|
|
154
|
-
return {
|
|
155
|
-
kind: "invalid-config",
|
|
156
|
-
message: err instanceof Error ? err.message : String(err)
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
if (!selection) {
|
|
160
|
-
return { kind: "unconfigured" };
|
|
161
|
-
}
|
|
162
|
-
if (hasProviderCredentials(selection.provider)) {
|
|
163
|
-
return {
|
|
164
|
-
kind: "ready",
|
|
165
|
-
model: selection.model,
|
|
166
|
-
provider: selection.provider,
|
|
167
|
-
source: selection.source
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
return {
|
|
171
|
-
kind: "configured-missing-credentials",
|
|
172
|
-
model: selection.model,
|
|
173
|
-
provider: selection.provider
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
const envSelection = resolveSnapshotApiModel(null);
|
|
177
|
-
if (envSelection && hasProviderCredentials(envSelection.provider)) {
|
|
178
|
-
return {
|
|
179
|
-
kind: "ready",
|
|
180
|
-
model: envSelection.model,
|
|
181
|
-
provider: envSelection.provider,
|
|
182
|
-
source: envSelection.source
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
return { kind: "unconfigured" };
|
|
186
|
-
}
|
|
187
|
-
export {
|
|
188
|
-
DEFAULT_SNAPSHOT_MODELS,
|
|
189
|
-
SnapshotApiUnavailableError,
|
|
190
|
-
isSnapshotApiUnavailableError,
|
|
191
|
-
parseDotEnvAssignment,
|
|
192
|
-
resolveAiSetupStatus,
|
|
193
|
-
resolveSnapshotApiModel,
|
|
194
|
-
resolveSnapshotApiModelOrThrow
|
|
195
|
-
};
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from "node:fs";
|
|
2
|
-
import { generateObject } from "ai";
|
|
3
|
-
import { resolveModel } from "./resolve-model.js";
|
|
4
|
-
import {
|
|
5
|
-
InterpretResultSchema,
|
|
6
|
-
buildInlinePromptSelection,
|
|
7
|
-
getMimeType,
|
|
8
|
-
readFileAsBase64
|
|
9
|
-
} from "./snapshot-analyzer.js";
|
|
10
|
-
import { readSnapshotModel } from "./config.js";
|
|
11
|
-
import { resolveSnapshotApiModelOrThrow } from "./ai-model.js";
|
|
12
|
-
async function runApiInterpret(args, logger, snapshotModel = readSnapshotModel()) {
|
|
13
|
-
const selection = resolveSnapshotApiModelOrThrow(snapshotModel);
|
|
14
|
-
logger.info("api-interpret-start", {
|
|
15
|
-
objective: args.objective,
|
|
16
|
-
pngPath: args.pngPath,
|
|
17
|
-
htmlPath: args.htmlPath,
|
|
18
|
-
condensedHtmlPath: args.condensedHtmlPath,
|
|
19
|
-
model: selection.model,
|
|
20
|
-
modelSource: selection.source
|
|
21
|
-
});
|
|
22
|
-
const fullHtmlContent = readFileSync(args.htmlPath, "utf-8");
|
|
23
|
-
const condensedHtmlContent = readFileSync(args.condensedHtmlPath, "utf-8");
|
|
24
|
-
const promptSelection = buildInlinePromptSelection(
|
|
25
|
-
args,
|
|
26
|
-
fullHtmlContent,
|
|
27
|
-
condensedHtmlContent,
|
|
28
|
-
selection.model
|
|
29
|
-
);
|
|
30
|
-
logger.info("api-interpret-dom-selection", {
|
|
31
|
-
configuredModel: promptSelection.stats.configuredModel,
|
|
32
|
-
fullDomEstimatedTokens: promptSelection.stats.fullDomEstimatedTokens,
|
|
33
|
-
condensedDomEstimatedTokens: promptSelection.stats.condensedDomEstimatedTokens,
|
|
34
|
-
contextWindowTokens: promptSelection.budget.contextWindowTokens,
|
|
35
|
-
promptBudgetTokens: promptSelection.budget.promptBudgetTokens,
|
|
36
|
-
selectedDom: promptSelection.domSource,
|
|
37
|
-
selectedHtmlEstimatedTokens: promptSelection.htmlEstimatedTokens,
|
|
38
|
-
selectedPromptEstimatedTokens: promptSelection.promptEstimatedTokens,
|
|
39
|
-
selectionReason: promptSelection.selectionReason,
|
|
40
|
-
truncated: promptSelection.truncated
|
|
41
|
-
});
|
|
42
|
-
const imageBase64 = readFileAsBase64(args.pngPath);
|
|
43
|
-
const imageMimeType = getMimeType(args.pngPath);
|
|
44
|
-
const imageBytes = Buffer.from(imageBase64, "base64");
|
|
45
|
-
const model = await resolveModel(selection.model);
|
|
46
|
-
const { object: result } = await generateObject({
|
|
47
|
-
model,
|
|
48
|
-
schema: InterpretResultSchema,
|
|
49
|
-
messages: [
|
|
50
|
-
{
|
|
51
|
-
role: "user",
|
|
52
|
-
content: [
|
|
53
|
-
{ type: "text", text: promptSelection.prompt },
|
|
54
|
-
{
|
|
55
|
-
type: "image",
|
|
56
|
-
image: imageBytes,
|
|
57
|
-
mediaType: imageMimeType
|
|
58
|
-
}
|
|
59
|
-
]
|
|
60
|
-
}
|
|
61
|
-
],
|
|
62
|
-
temperature: 0.1
|
|
63
|
-
});
|
|
64
|
-
const parsed = InterpretResultSchema.parse(result);
|
|
65
|
-
logger.info("api-interpret-success", {
|
|
66
|
-
selectorCount: parsed.selectors.length,
|
|
67
|
-
answer: parsed.answer.slice(0, 200)
|
|
68
|
-
});
|
|
69
|
-
console.log("");
|
|
70
|
-
console.log("Analysis:");
|
|
71
|
-
console.log(parsed.answer);
|
|
72
|
-
if (parsed.selectors.length > 0) {
|
|
73
|
-
console.log("");
|
|
74
|
-
console.log("Selectors:");
|
|
75
|
-
parsed.selectors.forEach((selector, index) => {
|
|
76
|
-
console.log(` ${index + 1}. ${selector.label}: ${selector.selector}`);
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
if (parsed.notes?.trim()) {
|
|
80
|
-
console.log("");
|
|
81
|
-
console.log(`Notes: ${parsed.notes.trim()}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
export {
|
|
85
|
-
runApiInterpret
|
|
86
|
-
};
|