libretto 0.4.2 → 0.4.4
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/dist/cli/commands/execution.js +1 -1
- package/dist/cli/commands/init.js +30 -21
- package/dist/cli/commands/snapshot.js +6 -1
- package/dist/cli/core/ai-config.js +62 -12
- package/dist/cli/core/snapshot-api-config.js +46 -5
- package/dist/cli/workers/run-integration-runtime.js +0 -2
- package/dist/shared/debug/index.cjs +2 -4
- package/dist/shared/debug/index.d.cts +1 -1
- package/dist/shared/debug/index.d.ts +1 -1
- package/dist/shared/debug/index.js +2 -3
- package/dist/shared/debug/pause.cjs +33 -37
- package/dist/shared/debug/pause.d.cts +2 -6
- package/dist/shared/debug/pause.d.ts +2 -6
- package/dist/shared/debug/pause.js +27 -20
- package/dist/shared/llm/client.cjs +5 -5
- package/dist/shared/llm/client.js +5 -5
- package/package.json +3 -2
- package/scripts/postinstall.mjs +41 -0
|
@@ -292,7 +292,7 @@ async function runResume(session, logger, sessionState) {
|
|
|
292
292
|
} = getPauseSignalPaths(session);
|
|
293
293
|
if (!existsSync(pausedSignalPath)) {
|
|
294
294
|
throw new Error(
|
|
295
|
-
`Session "${session}" is not paused. Run "libretto-cli run ... --session ${session}" and call pause() first.`
|
|
295
|
+
`Session "${session}" is not paused. Run "libretto-cli run ... --session ${session}" and call pause("${session}") first.`
|
|
296
296
|
);
|
|
297
297
|
}
|
|
298
298
|
if (!isProcessRunning(sessionState.pid)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
2
|
import { appendFileSync, cpSync, existsSync, readdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
|
-
import { dirname, join } from "node:path";
|
|
4
|
+
import { basename, dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { readAiConfig } from "../core/ai-config.js";
|
|
7
7
|
import { REPO_ROOT } from "../core/context.js";
|
|
@@ -60,6 +60,17 @@ function safeReadAiConfig() {
|
|
|
60
60
|
return null;
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
function printInvalidAiConfigWarning() {
|
|
64
|
+
try {
|
|
65
|
+
readAiConfig();
|
|
66
|
+
} catch (error) {
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
console.log(" ! Existing AI config is invalid:");
|
|
69
|
+
for (const line of message.split("\n")) {
|
|
70
|
+
console.log(` ${line}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
63
74
|
function printSnapshotApiStatus() {
|
|
64
75
|
const config = safeReadAiConfig();
|
|
65
76
|
const selection = resolveSnapshotApiModel(config);
|
|
@@ -69,6 +80,7 @@ function printSnapshotApiStatus() {
|
|
|
69
80
|
" Libretto uses direct API calls for snapshot analysis when supported credentials are available."
|
|
70
81
|
);
|
|
71
82
|
console.log(` Credentials are loaded from process env and ${envPath}.`);
|
|
83
|
+
printInvalidAiConfigWarning();
|
|
72
84
|
if (selection && hasProviderCredentials(selection.provider)) {
|
|
73
85
|
console.log(` \u2713 Ready: ${selection.model} (${selection.source})`);
|
|
74
86
|
console.log(" Snapshot objectives will use the API analyzer by default.");
|
|
@@ -84,7 +96,7 @@ function printSnapshotApiStatus() {
|
|
|
84
96
|
" GOOGLE_CLOUD_PROJECT=... # plus application default credentials for Vertex"
|
|
85
97
|
);
|
|
86
98
|
console.log(
|
|
87
|
-
" Or run `npx libretto ai configure
|
|
99
|
+
" Or run `npx libretto ai configure openai | anthropic | gemini | vertex` to set a specific model."
|
|
88
100
|
);
|
|
89
101
|
console.log(" Run `npx libretto init` interactively to set up credentials.");
|
|
90
102
|
}
|
|
@@ -95,6 +107,7 @@ async function runInteractiveApiSetup() {
|
|
|
95
107
|
console.log("\nSnapshot analysis setup:");
|
|
96
108
|
console.log(" Libretto uses direct API calls for snapshot analysis.");
|
|
97
109
|
console.log(` Credentials are loaded from process env and ${envPath}.`);
|
|
110
|
+
printInvalidAiConfigWarning();
|
|
98
111
|
if (selection && hasProviderCredentials(selection.provider)) {
|
|
99
112
|
console.log(` \u2713 Ready: ${selection.model} (${selection.source})`);
|
|
100
113
|
console.log(" Snapshot objectives will use the API analyzer by default.");
|
|
@@ -119,7 +132,7 @@ async function runInteractiveApiSetup() {
|
|
|
119
132
|
console.log(" ANTHROPIC_API_KEY=...");
|
|
120
133
|
console.log(" GEMINI_API_KEY=...");
|
|
121
134
|
console.log(
|
|
122
|
-
" Or run `npx libretto ai configure
|
|
135
|
+
" Or run `npx libretto ai configure openai | anthropic | gemini | vertex` to set a specific model."
|
|
123
136
|
);
|
|
124
137
|
return;
|
|
125
138
|
}
|
|
@@ -196,27 +209,21 @@ function getPackageSkillsDir() {
|
|
|
196
209
|
}
|
|
197
210
|
throw new Error("Could not locate libretto skill files in package");
|
|
198
211
|
}
|
|
212
|
+
function detectAgentDirs(root) {
|
|
213
|
+
const dirs = [];
|
|
214
|
+
if (existsSync(join(root, ".agents"))) dirs.push(join(root, ".agents"));
|
|
215
|
+
if (existsSync(join(root, ".claude"))) dirs.push(join(root, ".claude"));
|
|
216
|
+
return dirs;
|
|
217
|
+
}
|
|
199
218
|
async function copySkills() {
|
|
200
|
-
const
|
|
201
|
-
const agentDirs = [];
|
|
202
|
-
if (existsSync(join(cwd, ".agents"))) {
|
|
203
|
-
agentDirs.push({
|
|
204
|
-
name: ".agents",
|
|
205
|
-
skillDest: join(cwd, ".agents", "skills", "libretto")
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
if (existsSync(join(cwd, ".claude"))) {
|
|
209
|
-
agentDirs.push({
|
|
210
|
-
name: ".claude",
|
|
211
|
-
skillDest: join(cwd, ".claude", "skills", "libretto")
|
|
212
|
-
});
|
|
213
|
-
}
|
|
219
|
+
const agentDirs = detectAgentDirs(REPO_ROOT);
|
|
214
220
|
if (agentDirs.length === 0) {
|
|
215
|
-
console.log("\nSkills: No .agents/ or .claude/ directory found \u2014 skipping
|
|
221
|
+
console.log("\nSkills: No .agents/ or .claude/ directory found in repo root \u2014 skipping.");
|
|
216
222
|
return;
|
|
217
223
|
}
|
|
218
|
-
const
|
|
219
|
-
const
|
|
224
|
+
const destinations = agentDirs.map((d) => join(d, "skills", "libretto"));
|
|
225
|
+
const dirNames = agentDirs.map((d) => basename(d)).join(" and ");
|
|
226
|
+
const existing = destinations.filter((d) => existsSync(d));
|
|
220
227
|
const verb = existing.length > 0 ? "Overwrite" : "Install";
|
|
221
228
|
const proceed = await askYesNo(`
|
|
222
229
|
${verb} libretto skills in ${dirNames}?`);
|
|
@@ -231,7 +238,9 @@ ${verb} libretto skills in ${dirNames}?`);
|
|
|
231
238
|
console.error(` \u2717 ${e instanceof Error ? e.message : String(e)}`);
|
|
232
239
|
return;
|
|
233
240
|
}
|
|
234
|
-
for (
|
|
241
|
+
for (let i = 0; i < agentDirs.length; i++) {
|
|
242
|
+
const skillDest = destinations[i];
|
|
243
|
+
const name = basename(agentDirs[i]);
|
|
235
244
|
if (existsSync(skillDest)) {
|
|
236
245
|
rmSync(skillDest, { recursive: true });
|
|
237
246
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "./shared.js";
|
|
14
14
|
import { runApiInterpret } from "../core/api-snapshot-analyzer.js";
|
|
15
15
|
import { readAiConfig } from "../core/ai-config.js";
|
|
16
|
+
import { resolveSnapshotApiModelOrThrow } from "../core/snapshot-api-config.js";
|
|
16
17
|
const DEFAULT_SNAPSHOT_CONTEXT = "No additional user context provided.";
|
|
17
18
|
const FALLBACK_SNAPSHOT_VIEWPORT = { width: 1280, height: 800 };
|
|
18
19
|
function generateSnapshotRunId() {
|
|
@@ -191,6 +192,10 @@ async function runSnapshot(session, logger, pageId, objective, context) {
|
|
|
191
192
|
"Couldn't run analysis: --objective is required when providing --context."
|
|
192
193
|
);
|
|
193
194
|
}
|
|
195
|
+
const configuredAi = normalizedObjective ? readAiConfig() : null;
|
|
196
|
+
if (normalizedObjective) {
|
|
197
|
+
resolveSnapshotApiModelOrThrow(configuredAi);
|
|
198
|
+
}
|
|
194
199
|
const { pngPath, htmlPath, condensedHtmlPath } = await captureScreenshot(
|
|
195
200
|
session,
|
|
196
201
|
logger,
|
|
@@ -212,7 +217,7 @@ async function runSnapshot(session, logger, pageId, objective, context) {
|
|
|
212
217
|
htmlPath,
|
|
213
218
|
condensedHtmlPath
|
|
214
219
|
};
|
|
215
|
-
await runApiInterpret(interpretArgs, logger,
|
|
220
|
+
await runApiInterpret(interpretArgs, logger, configuredAi);
|
|
216
221
|
}
|
|
217
222
|
const snapshotInput = SimpleCLI.input({
|
|
218
223
|
positionals: [],
|
|
@@ -6,7 +6,7 @@ const CURRENT_CONFIG_VERSION = 1;
|
|
|
6
6
|
const AiConfigSchema = z.object({
|
|
7
7
|
model: z.string().min(1),
|
|
8
8
|
updatedAt: z.string()
|
|
9
|
-
})
|
|
9
|
+
});
|
|
10
10
|
const ViewportConfigSchema = z.object({
|
|
11
11
|
width: z.number().int().min(1),
|
|
12
12
|
height: z.number().int().min(1)
|
|
@@ -19,22 +19,68 @@ const LibrettoConfigSchema = z.object({
|
|
|
19
19
|
const DEFAULT_MODELS = {
|
|
20
20
|
openai: "openai/gpt-5.4",
|
|
21
21
|
anthropic: "anthropic/claude-sonnet-4-6",
|
|
22
|
-
gemini: "google/gemini-
|
|
23
|
-
google: "google/gemini-2.5-flash",
|
|
22
|
+
gemini: "google/gemini-3-flash-preview",
|
|
24
23
|
vertex: "vertex/gemini-2.5-pro"
|
|
25
24
|
};
|
|
26
|
-
const
|
|
27
|
-
|
|
25
|
+
const PROVIDER_ALIASES = {
|
|
26
|
+
claude: DEFAULT_MODELS.anthropic,
|
|
27
|
+
google: DEFAULT_MODELS.gemini
|
|
28
|
+
};
|
|
29
|
+
const CONFIGURE_PROVIDERS = ["openai", "anthropic", "gemini", "vertex"];
|
|
30
|
+
function formatConfigureProviders(separator = " | ") {
|
|
31
|
+
return CONFIGURE_PROVIDERS.join(separator);
|
|
32
|
+
}
|
|
33
|
+
function formatConfigIssues(error) {
|
|
34
|
+
return error.issues.map((issue) => ` - ${issue.path.join(".") || "root"}: ${issue.message}`).join("\n");
|
|
35
|
+
}
|
|
36
|
+
function formatExpectedConfigExample() {
|
|
37
|
+
return JSON.stringify(
|
|
38
|
+
{
|
|
39
|
+
version: CURRENT_CONFIG_VERSION,
|
|
40
|
+
ai: {
|
|
41
|
+
model: "openai/gpt-5.4",
|
|
42
|
+
updatedAt: "2026-01-01T00:00:00.000Z"
|
|
43
|
+
},
|
|
44
|
+
viewport: {
|
|
45
|
+
width: 1280,
|
|
46
|
+
height: 800
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
null,
|
|
50
|
+
2
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
function invalidConfigError(configPath, detail) {
|
|
28
54
|
return new Error(
|
|
29
|
-
|
|
55
|
+
[
|
|
56
|
+
`AI config is invalid at ${configPath}.`,
|
|
57
|
+
detail ? `Problems:
|
|
58
|
+
${detail}` : null,
|
|
59
|
+
"Expected config example:",
|
|
60
|
+
formatExpectedConfigExample(),
|
|
61
|
+
"Notes:",
|
|
62
|
+
' - "ai" and "viewport" are optional.',
|
|
63
|
+
' - "ai.model" must be a provider/model string like "openai/gpt-5.4" or "anthropic/claude-sonnet-4-6".',
|
|
64
|
+
"Fix the file to match this shape, or delete it and rerun:",
|
|
65
|
+
` npx libretto ai configure ${formatConfigureProviders()}`
|
|
66
|
+
].filter(Boolean).join("\n")
|
|
30
67
|
);
|
|
31
68
|
}
|
|
32
69
|
function parseConfig(raw, configPath) {
|
|
70
|
+
let parsedJson;
|
|
33
71
|
try {
|
|
34
|
-
|
|
35
|
-
} catch {
|
|
36
|
-
throw invalidConfigError(
|
|
72
|
+
parsedJson = JSON.parse(raw);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
throw invalidConfigError(
|
|
75
|
+
configPath,
|
|
76
|
+
` - root: Invalid JSON: ${error instanceof Error ? error.message : String(error)}`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const parsed = LibrettoConfigSchema.safeParse(parsedJson);
|
|
80
|
+
if (!parsed.success) {
|
|
81
|
+
throw invalidConfigError(configPath, formatConfigIssues(parsed.error));
|
|
37
82
|
}
|
|
83
|
+
return parsed.data;
|
|
38
84
|
}
|
|
39
85
|
function readLibrettoConfig(configPath = LIBRETTO_CONFIG_PATH) {
|
|
40
86
|
if (!existsSync(configPath)) {
|
|
@@ -88,7 +134,8 @@ function resolveModelFromInput(input) {
|
|
|
88
134
|
const trimmed = input.trim();
|
|
89
135
|
if (!trimmed) return null;
|
|
90
136
|
if (trimmed.includes("/")) return trimmed;
|
|
91
|
-
|
|
137
|
+
const normalized = trimmed.toLowerCase();
|
|
138
|
+
return DEFAULT_MODELS[normalized] ?? PROVIDER_ALIASES[normalized] ?? null;
|
|
92
139
|
}
|
|
93
140
|
function runAiConfigure(input, options = {}) {
|
|
94
141
|
const configureCommandName = options.configureCommandName ?? "npx libretto ai configure";
|
|
@@ -97,7 +144,10 @@ function runAiConfigure(input, options = {}) {
|
|
|
97
144
|
if (!presetArg && !input.clear) {
|
|
98
145
|
const config2 = readAiConfig(configPath);
|
|
99
146
|
if (!config2) {
|
|
100
|
-
console.log(
|
|
147
|
+
console.log(
|
|
148
|
+
`No AI config set. Choose a default model: ${configureCommandName} ${formatConfigureProviders()}`
|
|
149
|
+
);
|
|
150
|
+
console.log("Provider credentials still come from your shell or .env file.");
|
|
101
151
|
return;
|
|
102
152
|
}
|
|
103
153
|
printAiConfig(config2, configPath);
|
|
@@ -120,7 +170,7 @@ function runAiConfigure(input, options = {}) {
|
|
|
120
170
|
${configureCommandName} --clear`
|
|
121
171
|
);
|
|
122
172
|
throw new Error(
|
|
123
|
-
`Invalid provider or model. Use one of: ${
|
|
173
|
+
`Invalid provider or model. Use one of: ${formatConfigureProviders()}, or a full model string like "openai/gpt-4o".`
|
|
124
174
|
);
|
|
125
175
|
}
|
|
126
176
|
const config = writeAiConfig(model, configPath);
|
|
@@ -3,16 +3,15 @@ import { dirname, join, resolve } from "node:path";
|
|
|
3
3
|
import {
|
|
4
4
|
readAiConfig
|
|
5
5
|
} from "./ai-config.js";
|
|
6
|
-
import { REPO_ROOT } from "./context.js";
|
|
6
|
+
import { LIBRETTO_CONFIG_PATH, REPO_ROOT } from "./context.js";
|
|
7
7
|
import {
|
|
8
8
|
hasProviderCredentials,
|
|
9
|
-
missingProviderCredentialsMessage,
|
|
10
9
|
parseModel
|
|
11
10
|
} from "../../shared/llm/client.js";
|
|
12
11
|
const DEFAULT_SNAPSHOT_MODELS = {
|
|
13
12
|
openai: "openai/gpt-5.4",
|
|
14
13
|
anthropic: "anthropic/claude-sonnet-4-6",
|
|
15
|
-
google: "google/gemini-
|
|
14
|
+
google: "google/gemini-3-flash-preview",
|
|
16
15
|
vertex: "vertex/gemini-2.5-pro"
|
|
17
16
|
};
|
|
18
17
|
class SnapshotApiUnavailableError extends Error {
|
|
@@ -21,6 +20,48 @@ class SnapshotApiUnavailableError extends Error {
|
|
|
21
20
|
this.name = "SnapshotApiUnavailableError";
|
|
22
21
|
}
|
|
23
22
|
}
|
|
23
|
+
function providerSetupSentence(provider) {
|
|
24
|
+
switch (provider) {
|
|
25
|
+
case "openai":
|
|
26
|
+
return "Add OPENAI_API_KEY to .env or as a shell environment variable.";
|
|
27
|
+
case "anthropic":
|
|
28
|
+
return "Add ANTHROPIC_API_KEY to .env or as a shell environment variable.";
|
|
29
|
+
case "google":
|
|
30
|
+
return "Add GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY to .env or as a shell environment variable.";
|
|
31
|
+
case "vertex":
|
|
32
|
+
return "Add GOOGLE_CLOUD_PROJECT or GCLOUD_PROJECT to .env or as a shell environment variable, and make sure application default credentials are configured.";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function defaultModelCommandLine() {
|
|
36
|
+
return "npx libretto ai configure openai | anthropic | gemini | vertex";
|
|
37
|
+
}
|
|
38
|
+
function providerMissingCredentialSummary(provider) {
|
|
39
|
+
switch (provider) {
|
|
40
|
+
case "openai":
|
|
41
|
+
return "OPENAI_API_KEY is missing";
|
|
42
|
+
case "anthropic":
|
|
43
|
+
return "ANTHROPIC_API_KEY is missing";
|
|
44
|
+
case "google":
|
|
45
|
+
return "GEMINI_API_KEY and GOOGLE_GENERATIVE_AI_API_KEY are missing";
|
|
46
|
+
case "vertex":
|
|
47
|
+
return "GOOGLE_CLOUD_PROJECT and GCLOUD_PROJECT are missing";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function noSnapshotApiConfiguredMessage() {
|
|
51
|
+
return [
|
|
52
|
+
"Failed to analyze snapshot because no snapshot analyzer is configured.",
|
|
53
|
+
`Add OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY, or GOOGLE_CLOUD_PROJECT to .env or as a shell environment variable, or choose a default model with \`${defaultModelCommandLine()}\`.`,
|
|
54
|
+
"For more info, run `npx libretto init`."
|
|
55
|
+
].join(" ");
|
|
56
|
+
}
|
|
57
|
+
function missingProviderSnapshotMessage(selection) {
|
|
58
|
+
const configuredSource = selection.source === "config" ? ` in ${LIBRETTO_CONFIG_PATH}` : " from process env or .env";
|
|
59
|
+
return [
|
|
60
|
+
`Failed to analyze snapshot because ${selection.provider} is configured${configuredSource}, but ${providerMissingCredentialSummary(selection.provider)}.`,
|
|
61
|
+
providerSetupSentence(selection.provider),
|
|
62
|
+
"For more info, run `npx libretto init`."
|
|
63
|
+
].join(" ");
|
|
64
|
+
}
|
|
24
65
|
function readWorktreeEnvPath() {
|
|
25
66
|
const gitPath = join(REPO_ROOT, ".git");
|
|
26
67
|
if (!existsSync(gitPath)) return null;
|
|
@@ -114,12 +155,12 @@ function resolveSnapshotApiModelOrThrow(config = readAiConfig()) {
|
|
|
114
155
|
const selection = resolveSnapshotApiModel(config);
|
|
115
156
|
if (!selection) {
|
|
116
157
|
throw new SnapshotApiUnavailableError(
|
|
117
|
-
|
|
158
|
+
noSnapshotApiConfiguredMessage()
|
|
118
159
|
);
|
|
119
160
|
}
|
|
120
161
|
if (!hasProviderCredentials(selection.provider)) {
|
|
121
162
|
throw new SnapshotApiUnavailableError(
|
|
122
|
-
|
|
163
|
+
missingProviderSnapshotMessage(selection)
|
|
123
164
|
);
|
|
124
165
|
}
|
|
125
166
|
return selection;
|
|
@@ -6,7 +6,6 @@ import { pathToFileURL } from "node:url";
|
|
|
6
6
|
import {
|
|
7
7
|
launchBrowser
|
|
8
8
|
} from "../../index.js";
|
|
9
|
-
import { setSessionForPause } from "../../shared/debug/pause.js";
|
|
10
9
|
import { parseSessionStateContent } from "../../shared/state/index.js";
|
|
11
10
|
import { getProfilePath, normalizeDomain } from "../core/browser.js";
|
|
12
11
|
import {
|
|
@@ -181,7 +180,6 @@ async function runIntegrationInternal(args, options) {
|
|
|
181
180
|
appendFileSync(networkLogPath, JSON.stringify(entry) + "\n");
|
|
182
181
|
}
|
|
183
182
|
});
|
|
184
|
-
setSessionForPause(args.session);
|
|
185
183
|
const workflowContext = {
|
|
186
184
|
logger: integrationLogger,
|
|
187
185
|
page: browserSession.page,
|
|
@@ -18,13 +18,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var debug_exports = {};
|
|
20
20
|
__export(debug_exports, {
|
|
21
|
-
pause: () => import_pause.pause
|
|
22
|
-
setSessionForPause: () => import_pause.setSessionForPause
|
|
21
|
+
pause: () => import_pause.pause
|
|
23
22
|
});
|
|
24
23
|
module.exports = __toCommonJS(debug_exports);
|
|
25
24
|
var import_pause = require("./pause.js");
|
|
26
25
|
// Annotate the CommonJS export names for ESM import in node:
|
|
27
26
|
0 && (module.exports = {
|
|
28
|
-
pause
|
|
29
|
-
setSessionForPause
|
|
27
|
+
pause
|
|
30
28
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { pause
|
|
1
|
+
export { pause } from './pause.cjs';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export { pause
|
|
1
|
+
export { pause } from './pause.js';
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,54 +15,53 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
var pause_exports = {};
|
|
30
20
|
__export(pause_exports, {
|
|
31
|
-
pause: () => pause
|
|
32
|
-
setSessionForPause: () => setSessionForPause
|
|
21
|
+
pause: () => pause
|
|
33
22
|
});
|
|
34
23
|
module.exports = __toCommonJS(pause_exports);
|
|
35
24
|
var import_node_fs = require("node:fs");
|
|
36
25
|
var import_promises = require("node:fs/promises");
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
function getSessionFromProcessArgs() {
|
|
42
|
-
const rawPayload = process.argv[2];
|
|
43
|
-
if (!rawPayload) return void 0;
|
|
26
|
+
var import_context = require("../../cli/core/context.js");
|
|
27
|
+
var import_pause_signals = require("../../cli/core/pause-signals.js");
|
|
28
|
+
var import_session = require("../../cli/core/session.js");
|
|
29
|
+
function isPidRunning(pid) {
|
|
44
30
|
try {
|
|
45
|
-
|
|
46
|
-
return
|
|
31
|
+
process.kill(pid, 0);
|
|
32
|
+
return true;
|
|
47
33
|
} catch {
|
|
48
|
-
return
|
|
34
|
+
return false;
|
|
49
35
|
}
|
|
50
36
|
}
|
|
51
|
-
function
|
|
52
|
-
return
|
|
37
|
+
function getRunningSessions() {
|
|
38
|
+
return (0, import_session.listSessionsWithStateFile)().filter((candidate) => {
|
|
39
|
+
const state = (0, import_session.readSessionState)(candidate);
|
|
40
|
+
return state !== null && isPidRunning(state.pid);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function throwMissingSessionError() {
|
|
44
|
+
const runningSessions = getRunningSessions();
|
|
45
|
+
const lines = ["pause(session) requires a non-empty session ID."];
|
|
46
|
+
if (runningSessions.length > 0) {
|
|
47
|
+
lines.push("", "Running sessions:");
|
|
48
|
+
for (const runningSession of runningSessions) {
|
|
49
|
+
lines.push(` ${runningSession}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
throw new Error(lines.join("\n"));
|
|
53
53
|
}
|
|
54
|
-
async function pause() {
|
|
54
|
+
async function pause(session) {
|
|
55
55
|
if (process.env.NODE_ENV === "production") {
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return;
|
|
58
|
+
if (typeof session !== "string" || session.trim().length === 0) {
|
|
59
|
+
throwMissingSessionError();
|
|
61
60
|
}
|
|
62
|
-
const
|
|
63
|
-
const { getSessionDir } = await import("../../cli/core/context.js");
|
|
64
|
-
const signalPaths = getPauseSignalPaths(session);
|
|
61
|
+
const signalPaths = (0, import_pause_signals.getPauseSignalPaths)(session);
|
|
65
62
|
const { pausedSignalPath, resumeSignalPath } = signalPaths;
|
|
66
|
-
await (0, import_promises.mkdir)(getSessionDir(session), { recursive: true });
|
|
67
|
-
await removeSignalIfExists(resumeSignalPath);
|
|
63
|
+
await (0, import_promises.mkdir)((0, import_context.getSessionDir)(session), { recursive: true });
|
|
64
|
+
await (0, import_pause_signals.removeSignalIfExists)(resumeSignalPath);
|
|
68
65
|
const details = {
|
|
69
66
|
sessionName: session,
|
|
70
67
|
pausedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -79,12 +76,11 @@ async function pause() {
|
|
|
79
76
|
(resolve) => setTimeout(resolve, RESUME_POLL_INTERVAL_MS)
|
|
80
77
|
);
|
|
81
78
|
}
|
|
82
|
-
await removeSignalIfExists(resumeSignalPath);
|
|
83
|
-
await removeSignalIfExists(pausedSignalPath);
|
|
79
|
+
await (0, import_pause_signals.removeSignalIfExists)(resumeSignalPath);
|
|
80
|
+
await (0, import_pause_signals.removeSignalIfExists)(pausedSignalPath);
|
|
84
81
|
console.log("[pause] Resume signal received. Continuing workflow...");
|
|
85
82
|
}
|
|
86
83
|
// Annotate the CommonJS export names for ESM import in node:
|
|
87
84
|
0 && (module.exports = {
|
|
88
|
-
pause
|
|
89
|
-
setSessionForPause
|
|
85
|
+
pause
|
|
90
86
|
});
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Called by the CLI runtime to make the session name available to `pause()`.
|
|
3
|
-
*/
|
|
4
|
-
declare function setSessionForPause(session: string): void;
|
|
5
1
|
/**
|
|
6
2
|
* Standalone pause function.
|
|
7
3
|
*
|
|
@@ -11,6 +7,6 @@ declare function setSessionForPause(session: string): void;
|
|
|
11
7
|
*
|
|
12
8
|
* Import directly: `import { pause } from "libretto";`
|
|
13
9
|
*/
|
|
14
|
-
declare function pause(): Promise<void>;
|
|
10
|
+
declare function pause(session: string): Promise<void>;
|
|
15
11
|
|
|
16
|
-
export { pause
|
|
12
|
+
export { pause };
|
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Called by the CLI runtime to make the session name available to `pause()`.
|
|
3
|
-
*/
|
|
4
|
-
declare function setSessionForPause(session: string): void;
|
|
5
1
|
/**
|
|
6
2
|
* Standalone pause function.
|
|
7
3
|
*
|
|
@@ -11,6 +7,6 @@ declare function setSessionForPause(session: string): void;
|
|
|
11
7
|
*
|
|
12
8
|
* Import directly: `import { pause } from "libretto";`
|
|
13
9
|
*/
|
|
14
|
-
declare function pause(): Promise<void>;
|
|
10
|
+
declare function pause(session: string): Promise<void>;
|
|
15
11
|
|
|
16
|
-
export { pause
|
|
12
|
+
export { pause };
|
|
@@ -1,32 +1,40 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
function getSessionFromProcessArgs() {
|
|
8
|
-
const rawPayload = process.argv[2];
|
|
9
|
-
if (!rawPayload) return void 0;
|
|
3
|
+
import { getSessionDir } from "../../cli/core/context.js";
|
|
4
|
+
import { getPauseSignalPaths, removeSignalIfExists } from "../../cli/core/pause-signals.js";
|
|
5
|
+
import { listSessionsWithStateFile, readSessionState } from "../../cli/core/session.js";
|
|
6
|
+
function isPidRunning(pid) {
|
|
10
7
|
try {
|
|
11
|
-
|
|
12
|
-
return
|
|
8
|
+
process.kill(pid, 0);
|
|
9
|
+
return true;
|
|
13
10
|
} catch {
|
|
14
|
-
return
|
|
11
|
+
return false;
|
|
15
12
|
}
|
|
16
13
|
}
|
|
17
|
-
function
|
|
18
|
-
return
|
|
14
|
+
function getRunningSessions() {
|
|
15
|
+
return listSessionsWithStateFile().filter((candidate) => {
|
|
16
|
+
const state = readSessionState(candidate);
|
|
17
|
+
return state !== null && isPidRunning(state.pid);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function throwMissingSessionError() {
|
|
21
|
+
const runningSessions = getRunningSessions();
|
|
22
|
+
const lines = ["pause(session) requires a non-empty session ID."];
|
|
23
|
+
if (runningSessions.length > 0) {
|
|
24
|
+
lines.push("", "Running sessions:");
|
|
25
|
+
for (const runningSession of runningSessions) {
|
|
26
|
+
lines.push(` ${runningSession}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
throw new Error(lines.join("\n"));
|
|
19
30
|
}
|
|
20
|
-
async function pause() {
|
|
31
|
+
async function pause(session) {
|
|
21
32
|
if (process.env.NODE_ENV === "production") {
|
|
22
33
|
return;
|
|
23
34
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return;
|
|
35
|
+
if (typeof session !== "string" || session.trim().length === 0) {
|
|
36
|
+
throwMissingSessionError();
|
|
27
37
|
}
|
|
28
|
-
const { getPauseSignalPaths, removeSignalIfExists } = await import("../../cli/core/pause-signals.js");
|
|
29
|
-
const { getSessionDir } = await import("../../cli/core/context.js");
|
|
30
38
|
const signalPaths = getPauseSignalPaths(session);
|
|
31
39
|
const { pausedSignalPath, resumeSignalPath } = signalPaths;
|
|
32
40
|
await mkdir(getSessionDir(session), { recursive: true });
|
|
@@ -50,6 +58,5 @@ async function pause() {
|
|
|
50
58
|
console.log("[pause] Resume signal received. Continuing workflow...");
|
|
51
59
|
}
|
|
52
60
|
export {
|
|
53
|
-
pause
|
|
54
|
-
setSessionForPause
|
|
61
|
+
pause
|
|
55
62
|
};
|
|
@@ -62,7 +62,7 @@ function parseModel(model) {
|
|
|
62
62
|
const slashIndex = model.indexOf("/");
|
|
63
63
|
if (slashIndex === -1) {
|
|
64
64
|
throw new Error(
|
|
65
|
-
`Invalid model string "${model}". Expected format: "provider/model-id" (for example "openai/gpt-5.4", "anthropic/claude-sonnet-4-6", "google/gemini-
|
|
65
|
+
`Invalid model string "${model}". Expected format: "provider/model-id" (for example "openai/gpt-5.4", "anthropic/claude-sonnet-4-6", "google/gemini-3-flash-preview", or "vertex/gemini-2.5-pro").`
|
|
66
66
|
);
|
|
67
67
|
}
|
|
68
68
|
const providerInput = model.slice(0, slashIndex).toLowerCase();
|
|
@@ -90,14 +90,14 @@ function hasProviderCredentials(provider, env = process.env) {
|
|
|
90
90
|
function missingProviderCredentialsMessage(provider) {
|
|
91
91
|
switch (provider) {
|
|
92
92
|
case "google":
|
|
93
|
-
return "
|
|
93
|
+
return "Gemini API key is missing. Set GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY.";
|
|
94
94
|
case "vertex":
|
|
95
|
-
return "
|
|
95
|
+
return "Vertex AI project is missing. Set GOOGLE_CLOUD_PROJECT (or GCLOUD_PROJECT) and ensure application default credentials are configured.";
|
|
96
96
|
case "anthropic": {
|
|
97
|
-
return "
|
|
97
|
+
return "Anthropic API key is missing. Set ANTHROPIC_API_KEY.";
|
|
98
98
|
}
|
|
99
99
|
case "openai": {
|
|
100
|
-
return "
|
|
100
|
+
return "OpenAI API key is missing. Set OPENAI_API_KEY.";
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
}
|
|
@@ -26,7 +26,7 @@ function parseModel(model) {
|
|
|
26
26
|
const slashIndex = model.indexOf("/");
|
|
27
27
|
if (slashIndex === -1) {
|
|
28
28
|
throw new Error(
|
|
29
|
-
`Invalid model string "${model}". Expected format: "provider/model-id" (for example "openai/gpt-5.4", "anthropic/claude-sonnet-4-6", "google/gemini-
|
|
29
|
+
`Invalid model string "${model}". Expected format: "provider/model-id" (for example "openai/gpt-5.4", "anthropic/claude-sonnet-4-6", "google/gemini-3-flash-preview", or "vertex/gemini-2.5-pro").`
|
|
30
30
|
);
|
|
31
31
|
}
|
|
32
32
|
const providerInput = model.slice(0, slashIndex).toLowerCase();
|
|
@@ -54,14 +54,14 @@ function hasProviderCredentials(provider, env = process.env) {
|
|
|
54
54
|
function missingProviderCredentialsMessage(provider) {
|
|
55
55
|
switch (provider) {
|
|
56
56
|
case "google":
|
|
57
|
-
return "
|
|
57
|
+
return "Gemini API key is missing. Set GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY.";
|
|
58
58
|
case "vertex":
|
|
59
|
-
return "
|
|
59
|
+
return "Vertex AI project is missing. Set GOOGLE_CLOUD_PROJECT (or GCLOUD_PROJECT) and ensure application default credentials are configured.";
|
|
60
60
|
case "anthropic": {
|
|
61
|
-
return "
|
|
61
|
+
return "Anthropic API key is missing. Set ANTHROPIC_API_KEY.";
|
|
62
62
|
}
|
|
63
63
|
case "openai": {
|
|
64
|
-
return "
|
|
64
|
+
return "OpenAI API key is missing. Set OPENAI_API_KEY.";
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libretto",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "AI-powered browser automation library and CLI built on Playwright",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
17
|
+
"scripts",
|
|
17
18
|
"skills/libretto"
|
|
18
19
|
],
|
|
19
20
|
"bin": {
|
|
@@ -28,7 +29,7 @@
|
|
|
28
29
|
}
|
|
29
30
|
},
|
|
30
31
|
"scripts": {
|
|
31
|
-
"postinstall": "
|
|
32
|
+
"postinstall": "node scripts/postinstall.mjs",
|
|
32
33
|
"build": "pnpm run build:runtime && pnpm run build:cli",
|
|
33
34
|
"build:runtime": "tsup --config tsup.config.ts",
|
|
34
35
|
"build:cli": "tsup --config tsup.cli.config.ts",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const packageRoot = join(__dirname, "..");
|
|
10
|
+
|
|
11
|
+
// Install Playwright Chromium
|
|
12
|
+
spawnSync("npx", ["playwright", "install", "chromium"], {
|
|
13
|
+
stdio: "inherit",
|
|
14
|
+
shell: true,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Find git repo root
|
|
18
|
+
const gitResult = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
19
|
+
encoding: "utf-8",
|
|
20
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
21
|
+
});
|
|
22
|
+
const repoRoot = gitResult.status === 0 && gitResult.stdout
|
|
23
|
+
? gitResult.stdout.trim()
|
|
24
|
+
: null;
|
|
25
|
+
if (!repoRoot) process.exit(0);
|
|
26
|
+
|
|
27
|
+
// Sync skills to any agent dirs at repo root
|
|
28
|
+
const sourceDir = join(packageRoot, "skills", "libretto");
|
|
29
|
+
if (!existsSync(sourceDir)) process.exit(0);
|
|
30
|
+
|
|
31
|
+
const agentDirNames = [".agents", ".claude"];
|
|
32
|
+
for (const name of agentDirNames) {
|
|
33
|
+
const agentDir = join(repoRoot, name);
|
|
34
|
+
if (!existsSync(agentDir)) continue;
|
|
35
|
+
const dest = join(agentDir, "skills", "libretto");
|
|
36
|
+
if (existsSync(dest)) rmSync(dest, { recursive: true });
|
|
37
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
38
|
+
cpSync(sourceDir, dest, { recursive: true });
|
|
39
|
+
const count = readdirSync(dest).length;
|
|
40
|
+
console.log(`libretto: synced ${count} skill files to ${dest}`);
|
|
41
|
+
}
|