libretto 0.5.5 → 0.6.0
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 +23 -10
- package/README.template.md +23 -10
- package/dist/cli/cli.js +10 -0
- package/dist/cli/commands/ai.js +77 -2
- package/dist/cli/commands/browser.js +98 -8
- package/dist/cli/commands/execution.js +152 -56
- package/dist/cli/commands/setup.js +390 -0
- package/dist/cli/commands/snapshot.js +2 -2
- package/dist/cli/commands/status.js +62 -0
- package/dist/cli/core/{snapshot-api-config.js → ai-model.js} +81 -7
- package/dist/cli/core/api-snapshot-analyzer.js +7 -5
- package/dist/cli/core/browser.js +202 -36
- package/dist/cli/core/{ai-config.js → config.js} +14 -79
- package/dist/cli/core/context.js +1 -25
- package/dist/cli/core/deploy-artifact.js +121 -61
- package/dist/cli/core/providers/browserbase.js +53 -0
- package/dist/cli/core/providers/index.js +48 -0
- package/dist/cli/core/providers/kernel.js +46 -0
- package/dist/cli/core/providers/libretto-cloud.js +58 -0
- package/dist/cli/core/readonly-exec.js +231 -0
- package/dist/{shared/llm/client.js → cli/core/resolve-model.js} +4 -68
- package/dist/cli/core/session.js +53 -0
- package/dist/cli/core/skill-version.js +73 -0
- package/dist/cli/core/telemetry.js +1 -54
- package/dist/cli/index.js +1 -7
- package/dist/cli/router.js +4 -4
- package/dist/cli/workers/run-integration-runtime.js +19 -13
- package/dist/cli/workers/run-integration-worker-protocol.js +5 -2
- package/dist/index.d.ts +2 -4
- package/dist/index.js +2 -2
- package/dist/runtime/extract/extract.d.ts +2 -2
- package/dist/runtime/extract/extract.js +4 -2
- package/dist/runtime/extract/index.d.ts +1 -1
- package/dist/runtime/recovery/agent.d.ts +2 -3
- package/dist/runtime/recovery/agent.js +5 -3
- package/dist/runtime/recovery/errors.d.ts +2 -3
- package/dist/runtime/recovery/errors.js +4 -2
- package/dist/runtime/recovery/index.d.ts +1 -2
- package/dist/runtime/recovery/recovery.d.ts +2 -3
- package/dist/runtime/recovery/recovery.js +3 -3
- package/dist/shared/debug/pause.js +4 -21
- package/dist/shared/run/api.d.ts +2 -0
- package/dist/shared/run/browser.d.ts +9 -1
- package/dist/shared/run/browser.js +43 -3
- package/dist/shared/state/index.d.ts +1 -1
- package/dist/shared/state/index.js +2 -0
- package/dist/shared/state/session-state.d.ts +20 -1
- package/dist/shared/state/session-state.js +12 -2
- package/dist/shared/workflow/workflow.d.ts +2 -1
- package/dist/shared/workflow/workflow.js +16 -9
- package/package.json +17 -16
- package/scripts/postinstall.mjs +13 -11
- package/scripts/skills-libretto.mjs +14 -4
- package/skills/AGENTS.md +11 -0
- package/skills/libretto/SKILL.md +30 -9
- package/skills/libretto/references/auth-profiles.md +1 -1
- package/skills/libretto/references/code-generation-rules.md +3 -3
- package/skills/libretto/references/configuration-file-reference.md +11 -6
- package/skills/libretto-readonly/SKILL.md +95 -0
- package/src/cli/cli.ts +10 -0
- package/src/cli/commands/ai.ts +111 -1
- package/src/cli/commands/browser.ts +111 -9
- package/src/cli/commands/execution.ts +181 -74
- package/src/cli/commands/setup.ts +516 -0
- package/src/cli/commands/snapshot.ts +2 -2
- package/src/cli/commands/status.ts +79 -0
- package/src/cli/core/{snapshot-api-config.ts → ai-model.ts} +154 -14
- package/src/cli/core/api-snapshot-analyzer.ts +7 -5
- package/src/cli/core/browser.ts +242 -35
- package/src/cli/core/{ai-config.ts → config.ts} +14 -108
- package/src/cli/core/context.ts +1 -45
- package/src/cli/core/deploy-artifact.ts +141 -71
- package/src/cli/core/providers/browserbase.ts +57 -0
- package/src/cli/core/providers/index.ts +62 -0
- package/src/cli/core/providers/kernel.ts +49 -0
- package/src/cli/core/providers/libretto-cloud.ts +61 -0
- package/src/cli/core/providers/types.ts +9 -0
- package/src/cli/core/readonly-exec.ts +284 -0
- package/src/{shared/llm/client.ts → cli/core/resolve-model.ts} +3 -85
- package/src/cli/core/session.ts +75 -2
- package/src/cli/core/skill-version.ts +93 -0
- package/src/cli/core/telemetry.ts +0 -52
- package/src/cli/index.ts +0 -6
- package/src/cli/router.ts +4 -4
- package/src/cli/workers/run-integration-runtime.ts +18 -16
- package/src/cli/workers/run-integration-worker-protocol.ts +4 -1
- package/src/index.ts +1 -7
- package/src/runtime/extract/extract.ts +6 -5
- package/src/runtime/recovery/agent.ts +5 -4
- package/src/runtime/recovery/errors.ts +4 -3
- package/src/runtime/recovery/recovery.ts +4 -4
- package/src/shared/debug/pause.ts +4 -23
- package/src/shared/run/browser.ts +50 -1
- package/src/shared/state/index.ts +2 -0
- package/src/shared/state/session-state.ts +10 -0
- package/src/shared/workflow/workflow.ts +24 -13
- package/dist/cli/commands/init.js +0 -286
- package/dist/cli/commands/logs.js +0 -117
- package/dist/shared/llm/ai-sdk-adapter.d.ts +0 -22
- package/dist/shared/llm/ai-sdk-adapter.js +0 -49
- package/dist/shared/llm/client.d.ts +0 -13
- package/dist/shared/llm/index.d.ts +0 -5
- package/dist/shared/llm/index.js +0 -6
- package/dist/shared/llm/types.d.ts +0 -67
- package/src/cli/commands/init.ts +0 -331
- package/src/cli/commands/logs.ts +0 -128
- package/src/shared/llm/ai-sdk-adapter.ts +0 -81
- package/src/shared/llm/index.ts +0 -3
- package/src/shared/llm/types.ts +0 -63
- /package/dist/{shared/llm → cli/core/providers}/types.js +0 -0
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
defaultLogger
|
|
3
3
|
} from "../../shared/logger/logger.js";
|
|
4
|
+
import { generateObject } from "ai";
|
|
4
5
|
import { z } from "zod";
|
|
5
6
|
const detectSubmissionErrorSchema = z.object({
|
|
6
7
|
hasError: z.boolean().describe("Whether an error is visible on the page"),
|
|
7
8
|
matchedKnownErrorId: z.string().nullable().describe("The ID of the matched known error, or null if no match"),
|
|
8
9
|
errorMessage: z.string().nullable().describe("The error message visible on screen, or null if no error")
|
|
9
10
|
});
|
|
10
|
-
async function detectSubmissionError(page, error, logContext,
|
|
11
|
+
async function detectSubmissionError(page, error, logContext, model, knownErrors = [], logger) {
|
|
11
12
|
const log = logger ?? defaultLogger;
|
|
12
13
|
let screenshot;
|
|
13
14
|
let domSnapshot;
|
|
@@ -58,7 +59,8 @@ IMPORTANT:
|
|
|
58
59
|
${domSnapshot ? `<dom_snapshot>
|
|
59
60
|
${domSnapshot}
|
|
60
61
|
</dom_snapshot>` : ""}`;
|
|
61
|
-
const result = await
|
|
62
|
+
const { object: result } = await generateObject({
|
|
63
|
+
model,
|
|
62
64
|
schema: detectSubmissionErrorSchema,
|
|
63
65
|
messages: [
|
|
64
66
|
{
|
|
@@ -3,5 +3,4 @@ export { attemptWithRecovery } from './recovery.js';
|
|
|
3
3
|
export { DetectedSubmissionError, KnownSubmissionError, detectSubmissionError } from './errors.js';
|
|
4
4
|
import 'playwright';
|
|
5
5
|
import '../../shared/logger/logger.js';
|
|
6
|
-
import '
|
|
7
|
-
import 'zod';
|
|
6
|
+
import 'ai';
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Page } from 'playwright';
|
|
2
2
|
import { MinimalLogger } from '../../shared/logger/logger.js';
|
|
3
|
-
import {
|
|
4
|
-
import 'zod';
|
|
3
|
+
import { LanguageModel } from 'ai';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Attempts to execute a function, and if it fails, runs popup recovery
|
|
8
7
|
* (if an LLM client is provided) and retries the function once.
|
|
9
8
|
*/
|
|
10
|
-
declare function attemptWithRecovery<T>(page: Page, fn: () => Promise<T>, logger?: MinimalLogger,
|
|
9
|
+
declare function attemptWithRecovery<T>(page: Page, fn: () => Promise<T>, logger?: MinimalLogger, model?: LanguageModel): Promise<T>;
|
|
11
10
|
|
|
12
11
|
export { attemptWithRecovery };
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
defaultLogger
|
|
3
3
|
} from "../../shared/logger/logger.js";
|
|
4
4
|
import { executeRecoveryAgent } from "./agent.js";
|
|
5
|
-
async function attemptWithRecovery(page, fn, logger,
|
|
5
|
+
async function attemptWithRecovery(page, fn, logger, model) {
|
|
6
6
|
const log = logger ?? defaultLogger;
|
|
7
7
|
try {
|
|
8
8
|
return await fn();
|
|
@@ -13,7 +13,7 @@ async function attemptWithRecovery(page, fn, logger, llmClient) {
|
|
|
13
13
|
});
|
|
14
14
|
throw error;
|
|
15
15
|
}
|
|
16
|
-
if (!
|
|
16
|
+
if (!model) {
|
|
17
17
|
throw error;
|
|
18
18
|
}
|
|
19
19
|
log.info("Action failed, attempting popup recovery", {
|
|
@@ -23,7 +23,7 @@ async function attemptWithRecovery(page, fn, logger, llmClient) {
|
|
|
23
23
|
page,
|
|
24
24
|
"Look at the page to see if there is a popup blocking the screen. If so, close the popup.",
|
|
25
25
|
log,
|
|
26
|
-
|
|
26
|
+
model
|
|
27
27
|
);
|
|
28
28
|
return await fn();
|
|
29
29
|
}
|
|
@@ -5,31 +5,14 @@ import {
|
|
|
5
5
|
getPauseSignalPaths,
|
|
6
6
|
removeSignalIfExists
|
|
7
7
|
} from "../../cli/core/pause-signals.js";
|
|
8
|
-
import {
|
|
9
|
-
listSessionsWithStateFile,
|
|
10
|
-
readSessionState
|
|
11
|
-
} from "../../cli/core/session.js";
|
|
12
|
-
function isPidRunning(pid) {
|
|
13
|
-
try {
|
|
14
|
-
process.kill(pid, 0);
|
|
15
|
-
return true;
|
|
16
|
-
} catch {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function getRunningSessions() {
|
|
21
|
-
return listSessionsWithStateFile().filter((candidate) => {
|
|
22
|
-
const state = readSessionState(candidate);
|
|
23
|
-
return state !== null && state.pid != null && isPidRunning(state.pid);
|
|
24
|
-
});
|
|
25
|
-
}
|
|
8
|
+
import { listRunningSessions } from "../../cli/core/session.js";
|
|
26
9
|
function throwMissingSessionError() {
|
|
27
|
-
const runningSessions =
|
|
10
|
+
const runningSessions = listRunningSessions();
|
|
28
11
|
const lines = ["pause(session) requires a non-empty session ID."];
|
|
29
12
|
if (runningSessions.length > 0) {
|
|
30
13
|
lines.push("", "Running sessions:");
|
|
31
|
-
for (const
|
|
32
|
-
lines.push(` ${
|
|
14
|
+
for (const s of runningSessions) {
|
|
15
|
+
lines.push(` ${s.session}`);
|
|
33
16
|
}
|
|
34
17
|
}
|
|
35
18
|
throw new Error(lines.join("\n"));
|
package/dist/shared/run/api.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Browser, BrowserContext, Page } from 'playwright';
|
|
2
|
+
import { SessionAccessMode } from '../state/session-state.js';
|
|
3
|
+
import 'zod';
|
|
2
4
|
|
|
3
5
|
type LaunchBrowserArgs = {
|
|
4
6
|
sessionName: string;
|
|
@@ -8,6 +10,12 @@ type LaunchBrowserArgs = {
|
|
|
8
10
|
height: number;
|
|
9
11
|
};
|
|
10
12
|
storageStatePath?: string;
|
|
13
|
+
accessMode?: SessionAccessMode;
|
|
14
|
+
cdpEndpoint?: string;
|
|
15
|
+
provider?: {
|
|
16
|
+
name: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
};
|
|
11
19
|
};
|
|
12
20
|
type BrowserSession = {
|
|
13
21
|
browser: Browser;
|
|
@@ -17,6 +25,6 @@ type BrowserSession = {
|
|
|
17
25
|
metadataPath: string;
|
|
18
26
|
close: () => Promise<void>;
|
|
19
27
|
};
|
|
20
|
-
declare function launchBrowser({ sessionName, headless, viewport, storageStatePath, }: LaunchBrowserArgs): Promise<BrowserSession>;
|
|
28
|
+
declare function launchBrowser({ sessionName, headless, viewport, storageStatePath, accessMode, cdpEndpoint, provider, }: LaunchBrowserArgs): Promise<BrowserSession>;
|
|
21
29
|
|
|
22
30
|
export { type BrowserSession, type LaunchBrowserArgs, launchBrowser };
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
SESSION_STATE_VERSION,
|
|
9
9
|
SessionStateFileSchema
|
|
10
10
|
} from "../state/session-state.js";
|
|
11
|
-
import { readLibrettoConfig } from "../../cli/core/
|
|
11
|
+
import { readLibrettoConfig } from "../../cli/core/config.js";
|
|
12
12
|
async function pickFreePort() {
|
|
13
13
|
return await new Promise((resolve, reject) => {
|
|
14
14
|
const server = createServer();
|
|
@@ -63,8 +63,47 @@ async function launchBrowser({
|
|
|
63
63
|
sessionName,
|
|
64
64
|
headless = false,
|
|
65
65
|
viewport = { width: 1366, height: 768 },
|
|
66
|
-
storageStatePath
|
|
66
|
+
storageStatePath,
|
|
67
|
+
accessMode = "write-access",
|
|
68
|
+
cdpEndpoint,
|
|
69
|
+
provider
|
|
67
70
|
}) {
|
|
71
|
+
if (cdpEndpoint) {
|
|
72
|
+
const browser2 = await chromium.connectOverCDP(cdpEndpoint);
|
|
73
|
+
const context2 = browser2.contexts()[0] ?? await browser2.newContext({ viewport });
|
|
74
|
+
const page2 = context2.pages()[0] ?? await context2.newPage();
|
|
75
|
+
page2.setDefaultTimeout(3e4);
|
|
76
|
+
page2.setDefaultNavigationTimeout(45e3);
|
|
77
|
+
const metadataPath2 = ensureLibrettoSessionStatePath(sessionName);
|
|
78
|
+
writeFileSync(
|
|
79
|
+
metadataPath2,
|
|
80
|
+
JSON.stringify(
|
|
81
|
+
{
|
|
82
|
+
version: SESSION_STATE_VERSION,
|
|
83
|
+
session: sessionName,
|
|
84
|
+
port: 0,
|
|
85
|
+
cdpEndpoint,
|
|
86
|
+
pid: process.pid,
|
|
87
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
88
|
+
status: "active",
|
|
89
|
+
mode: accessMode,
|
|
90
|
+
...provider ? { provider } : {}
|
|
91
|
+
},
|
|
92
|
+
null,
|
|
93
|
+
2
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
return {
|
|
97
|
+
browser: browser2,
|
|
98
|
+
context: context2,
|
|
99
|
+
page: page2,
|
|
100
|
+
debugPort: 0,
|
|
101
|
+
metadataPath: metadataPath2,
|
|
102
|
+
close: async () => {
|
|
103
|
+
await browser2.close();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
}
|
|
68
107
|
const debugPort = await pickFreePort();
|
|
69
108
|
const windowPosition = headless ? void 0 : resolveWindowPosition();
|
|
70
109
|
const browser = await chromium.launch({
|
|
@@ -96,7 +135,8 @@ async function launchBrowser({
|
|
|
96
135
|
port: debugPort,
|
|
97
136
|
pid: process.pid,
|
|
98
137
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
99
|
-
status: "active"
|
|
138
|
+
status: "active",
|
|
139
|
+
mode: accessMode
|
|
100
140
|
},
|
|
101
141
|
null,
|
|
102
142
|
2
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { SESSION_STATE_VERSION, SessionState, SessionStateFile, SessionStateFileSchema, SessionStatus, SessionStatusSchema, parseSessionStateContent, parseSessionStateData, serializeSessionState } from './session-state.js';
|
|
1
|
+
export { SESSION_STATE_VERSION, SessionAccessMode, SessionAccessModeSchema, SessionState, SessionStateFile, SessionStateFileSchema, SessionStatus, SessionStatusSchema, parseSessionStateContent, parseSessionStateData, serializeSessionState } from './session-state.js';
|
|
2
2
|
import 'zod';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
SessionAccessModeSchema,
|
|
2
3
|
SESSION_STATE_VERSION,
|
|
3
4
|
SessionStatusSchema,
|
|
4
5
|
SessionStateFileSchema,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
} from "./session-state.js";
|
|
9
10
|
export {
|
|
10
11
|
SESSION_STATE_VERSION,
|
|
12
|
+
SessionAccessModeSchema,
|
|
11
13
|
SessionStateFileSchema,
|
|
12
14
|
SessionStatusSchema,
|
|
13
15
|
parseSessionStateContent,
|
|
@@ -7,11 +7,20 @@ declare const SessionStatusSchema: z.ZodEnum<{
|
|
|
7
7
|
completed: "completed";
|
|
8
8
|
failed: "failed";
|
|
9
9
|
exited: "exited";
|
|
10
|
+
"cleanup-failed": "cleanup-failed";
|
|
11
|
+
}>;
|
|
12
|
+
declare const SessionAccessModeSchema: z.ZodEnum<{
|
|
13
|
+
"read-only": "read-only";
|
|
14
|
+
"write-access": "write-access";
|
|
10
15
|
}>;
|
|
11
16
|
declare const SessionViewportSchema: z.ZodObject<{
|
|
12
17
|
width: z.ZodNumber;
|
|
13
18
|
height: z.ZodNumber;
|
|
14
19
|
}, z.core.$strip>;
|
|
20
|
+
declare const ProviderStateSchema: z.ZodObject<{
|
|
21
|
+
name: z.ZodString;
|
|
22
|
+
sessionId: z.ZodString;
|
|
23
|
+
}, z.core.$strip>;
|
|
15
24
|
declare const SessionStateFileSchema: z.ZodObject<{
|
|
16
25
|
version: z.ZodLiteral<1>;
|
|
17
26
|
port: z.ZodNumber;
|
|
@@ -25,17 +34,27 @@ declare const SessionStateFileSchema: z.ZodObject<{
|
|
|
25
34
|
completed: "completed";
|
|
26
35
|
failed: "failed";
|
|
27
36
|
exited: "exited";
|
|
37
|
+
"cleanup-failed": "cleanup-failed";
|
|
38
|
+
}>>;
|
|
39
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
40
|
+
"read-only": "read-only";
|
|
41
|
+
"write-access": "write-access";
|
|
28
42
|
}>>;
|
|
29
43
|
viewport: z.ZodOptional<z.ZodObject<{
|
|
30
44
|
width: z.ZodNumber;
|
|
31
45
|
height: z.ZodNumber;
|
|
32
46
|
}, z.core.$strip>>;
|
|
47
|
+
provider: z.ZodOptional<z.ZodObject<{
|
|
48
|
+
name: z.ZodString;
|
|
49
|
+
sessionId: z.ZodString;
|
|
50
|
+
}, z.core.$strip>>;
|
|
33
51
|
}, z.core.$strip>;
|
|
34
52
|
type SessionStatus = z.infer<typeof SessionStatusSchema>;
|
|
53
|
+
type SessionAccessMode = z.infer<typeof SessionAccessModeSchema>;
|
|
35
54
|
type SessionStateFile = z.infer<typeof SessionStateFileSchema>;
|
|
36
55
|
type SessionState = Omit<SessionStateFile, "version">;
|
|
37
56
|
declare function parseSessionStateData(rawState: unknown, source: string): SessionState;
|
|
38
57
|
declare function parseSessionStateContent(content: string, source: string): SessionState;
|
|
39
58
|
declare function serializeSessionState(state: SessionState): SessionStateFile;
|
|
40
59
|
|
|
41
|
-
export { SESSION_STATE_VERSION, type SessionState, type SessionStateFile, SessionStateFileSchema, type SessionStatus, SessionStatusSchema, SessionViewportSchema, parseSessionStateContent, parseSessionStateData, serializeSessionState };
|
|
60
|
+
export { ProviderStateSchema, SESSION_STATE_VERSION, type SessionAccessMode, SessionAccessModeSchema, type SessionState, type SessionStateFile, SessionStateFileSchema, type SessionStatus, SessionStatusSchema, SessionViewportSchema, parseSessionStateContent, parseSessionStateData, serializeSessionState };
|
|
@@ -5,12 +5,18 @@ const SessionStatusSchema = z.enum([
|
|
|
5
5
|
"paused",
|
|
6
6
|
"completed",
|
|
7
7
|
"failed",
|
|
8
|
-
"exited"
|
|
8
|
+
"exited",
|
|
9
|
+
"cleanup-failed"
|
|
9
10
|
]);
|
|
11
|
+
const SessionAccessModeSchema = z.enum(["read-only", "write-access"]);
|
|
10
12
|
const SessionViewportSchema = z.object({
|
|
11
13
|
width: z.number().int().min(1),
|
|
12
14
|
height: z.number().int().min(1)
|
|
13
15
|
});
|
|
16
|
+
const ProviderStateSchema = z.object({
|
|
17
|
+
name: z.string(),
|
|
18
|
+
sessionId: z.string()
|
|
19
|
+
});
|
|
14
20
|
const SessionStateFileSchema = z.object({
|
|
15
21
|
version: z.literal(SESSION_STATE_VERSION),
|
|
16
22
|
port: z.number().int().min(0).max(65535),
|
|
@@ -19,7 +25,9 @@ const SessionStateFileSchema = z.object({
|
|
|
19
25
|
session: z.string().min(1),
|
|
20
26
|
startedAt: z.string().datetime({ offset: true }),
|
|
21
27
|
status: SessionStatusSchema.optional(),
|
|
22
|
-
|
|
28
|
+
mode: SessionAccessModeSchema.default("write-access"),
|
|
29
|
+
viewport: SessionViewportSchema.optional(),
|
|
30
|
+
provider: ProviderStateSchema.optional()
|
|
23
31
|
});
|
|
24
32
|
function formatIssues(error) {
|
|
25
33
|
return error.issues.map((issue) => {
|
|
@@ -55,7 +63,9 @@ function serializeSessionState(state) {
|
|
|
55
63
|
});
|
|
56
64
|
}
|
|
57
65
|
export {
|
|
66
|
+
ProviderStateSchema,
|
|
58
67
|
SESSION_STATE_VERSION,
|
|
68
|
+
SessionAccessModeSchema,
|
|
59
69
|
SessionStateFileSchema,
|
|
60
70
|
SessionStatusSchema,
|
|
61
71
|
SessionViewportSchema,
|
|
@@ -21,7 +21,8 @@ type ExportedLibrettoWorkflow = {
|
|
|
21
21
|
type WorkflowModuleExports = Record<string, unknown>;
|
|
22
22
|
declare function isLibrettoWorkflow(value: unknown): value is ExportedLibrettoWorkflow;
|
|
23
23
|
declare function getWorkflowsFromModuleExports(moduleExports: WorkflowModuleExports): ExportedLibrettoWorkflow[];
|
|
24
|
+
declare function getDefaultWorkflowFromModuleExports(moduleExports: WorkflowModuleExports): ExportedLibrettoWorkflow | null;
|
|
24
25
|
declare function getWorkflowFromModuleExports(moduleExports: WorkflowModuleExports, workflowName: string): ExportedLibrettoWorkflow | null;
|
|
25
26
|
declare function workflow<Input = unknown, Output = unknown>(name: string, handler: LibrettoWorkflowHandler<Input, Output>): LibrettoWorkflow<Input, Output>;
|
|
26
27
|
|
|
27
|
-
export { type ExportedLibrettoWorkflow, LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, type LibrettoWorkflowContext, type LibrettoWorkflowHandler, getWorkflowFromModuleExports, getWorkflowsFromModuleExports, isLibrettoWorkflow, workflow };
|
|
28
|
+
export { type ExportedLibrettoWorkflow, LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, type LibrettoWorkflowContext, type LibrettoWorkflowHandler, getDefaultWorkflowFromModuleExports, getWorkflowFromModuleExports, getWorkflowsFromModuleExports, isLibrettoWorkflow, workflow };
|
|
@@ -26,24 +26,30 @@ function addWorkflowOrThrow(workflowsByName, value) {
|
|
|
26
26
|
}
|
|
27
27
|
workflowsByName.set(value.name, value);
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function collectWorkflowsOrThrow(values) {
|
|
30
30
|
const workflowsByName = /* @__PURE__ */ new Map();
|
|
31
|
+
for (const value of values) {
|
|
32
|
+
addWorkflowOrThrow(workflowsByName, value);
|
|
33
|
+
}
|
|
34
|
+
return [...workflowsByName.values()];
|
|
35
|
+
}
|
|
36
|
+
function getWorkflowsFromModuleExports(moduleExports) {
|
|
37
|
+
const discoveredValues = [];
|
|
31
38
|
for (const [exportName, value] of Object.entries(moduleExports)) {
|
|
32
39
|
if (exportName === "workflows" && value && typeof value === "object") {
|
|
33
40
|
if (isLibrettoWorkflow(value)) {
|
|
34
|
-
|
|
41
|
+
discoveredValues.push(value);
|
|
35
42
|
} else {
|
|
36
|
-
|
|
37
|
-
value
|
|
38
|
-
)) {
|
|
39
|
-
addWorkflowOrThrow(workflowsByName, nestedValue);
|
|
40
|
-
}
|
|
43
|
+
discoveredValues.push(...Object.values(value));
|
|
41
44
|
}
|
|
42
45
|
continue;
|
|
43
46
|
}
|
|
44
|
-
|
|
47
|
+
discoveredValues.push(value);
|
|
45
48
|
}
|
|
46
|
-
return
|
|
49
|
+
return collectWorkflowsOrThrow(discoveredValues);
|
|
50
|
+
}
|
|
51
|
+
function getDefaultWorkflowFromModuleExports(moduleExports) {
|
|
52
|
+
return isLibrettoWorkflow(moduleExports.default) ? moduleExports.default : null;
|
|
47
53
|
}
|
|
48
54
|
function getWorkflowFromModuleExports(moduleExports, workflowName) {
|
|
49
55
|
for (const workflow2 of getWorkflowsFromModuleExports(moduleExports)) {
|
|
@@ -59,6 +65,7 @@ function workflow(name, handler) {
|
|
|
59
65
|
export {
|
|
60
66
|
LIBRETTO_WORKFLOW_BRAND,
|
|
61
67
|
LibrettoWorkflow,
|
|
68
|
+
getDefaultWorkflowFromModuleExports,
|
|
62
69
|
getWorkflowFromModuleExports,
|
|
63
70
|
getWorkflowsFromModuleExports,
|
|
64
71
|
isLibrettoWorkflow,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "libretto",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "AI-powered browser automation library and CLI built on Playwright",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"url": "https://github.com/saffron-health/libretto"
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
|
+
"packageManager": "pnpm@10.33.0",
|
|
11
12
|
"publishConfig": {
|
|
12
13
|
"access": "public"
|
|
13
14
|
},
|
|
@@ -15,7 +16,7 @@
|
|
|
15
16
|
"dist",
|
|
16
17
|
"src",
|
|
17
18
|
"scripts",
|
|
18
|
-
"skills
|
|
19
|
+
"skills"
|
|
19
20
|
],
|
|
20
21
|
"types": "./dist/index.d.ts",
|
|
21
22
|
"bin": {
|
|
@@ -28,6 +29,19 @@
|
|
|
28
29
|
"default": "./dist/index.js"
|
|
29
30
|
}
|
|
30
31
|
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"sync:mirrors": "node ../dev-tools/scripts/sync-mirrors.mjs",
|
|
34
|
+
"check:mirrors": "node ../dev-tools/scripts/check-mirrors-sync.mjs",
|
|
35
|
+
"sync-skills": "pnpm run sync:mirrors",
|
|
36
|
+
"check:skills": "pnpm run check:mirrors",
|
|
37
|
+
"build": "tsup --config tsup.config.ts",
|
|
38
|
+
"type-check": "tsc --noEmit",
|
|
39
|
+
"test": "pnpm run build && vitest run",
|
|
40
|
+
"test:watch": "vitest",
|
|
41
|
+
"cli": "node dist/index.js",
|
|
42
|
+
"generate-changelog": "tsx scripts/generate-changelog.ts",
|
|
43
|
+
"prepack": "pnpm run build"
|
|
44
|
+
},
|
|
31
45
|
"peerDependencies": {
|
|
32
46
|
"@ai-sdk/anthropic": "^3.0.58",
|
|
33
47
|
"@ai-sdk/google": "^3.0.51",
|
|
@@ -72,18 +86,5 @@
|
|
|
72
86
|
"playwright": "^1.58.2",
|
|
73
87
|
"tsx": "^4.21.0",
|
|
74
88
|
"zod": "^4.3.6"
|
|
75
|
-
},
|
|
76
|
-
"scripts": {
|
|
77
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
78
|
-
"sync:mirrors": "node ../dev-tools/scripts/sync-mirrors.mjs",
|
|
79
|
-
"check:mirrors": "node ../dev-tools/scripts/check-mirrors-sync.mjs",
|
|
80
|
-
"sync-skills": "pnpm run sync:mirrors",
|
|
81
|
-
"check:skills": "pnpm run check:mirrors",
|
|
82
|
-
"build": "tsup --config tsup.config.ts",
|
|
83
|
-
"type-check": "tsc --noEmit",
|
|
84
|
-
"test": "pnpm run build && vitest run",
|
|
85
|
-
"test:watch": "vitest",
|
|
86
|
-
"cli": "node dist/index.js",
|
|
87
|
-
"generate-changelog": "tsx scripts/generate-changelog.ts"
|
|
88
89
|
}
|
|
89
|
-
}
|
|
90
|
+
}
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { dirname, join } from "node:path";
|
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import { SKILL_MIRRORS, syncSkillDir } from "./skills-libretto.mjs";
|
|
9
9
|
|
|
10
10
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
const packageRoot = join(__dirname, "..");
|
|
@@ -36,15 +36,17 @@ const repoRoot =
|
|
|
36
36
|
? gitResult.stdout.trim()
|
|
37
37
|
: installCwd;
|
|
38
38
|
|
|
39
|
-
const sourceDir = join(packageRoot, "skills", "libretto");
|
|
40
|
-
if (!existsSync(sourceDir)) process.exit(0);
|
|
41
|
-
|
|
42
39
|
const syncMissingDirs = repoRoot === packageRoot;
|
|
43
|
-
for (const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
for (const skillMirror of SKILL_MIRRORS) {
|
|
41
|
+
const sourceDir = join(packageRoot, skillMirror.source.replace(/^packages\/libretto\//, ""));
|
|
42
|
+
if (!existsSync(sourceDir)) continue;
|
|
43
|
+
|
|
44
|
+
for (const dir of skillMirror.targets) {
|
|
45
|
+
const rootName = dir.split("/")[0];
|
|
46
|
+
const rootDir = join(repoRoot, rootName);
|
|
47
|
+
if (!syncMissingDirs && !existsSync(rootDir)) continue;
|
|
48
|
+
const dest = join(repoRoot, dir);
|
|
49
|
+
syncSkillDir(sourceDir, dest);
|
|
50
|
+
console.log(`libretto: synced ${skillMirror.source} -> ${dest}`);
|
|
51
|
+
}
|
|
50
52
|
}
|
|
@@ -2,10 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
import { cpSync, mkdirSync, rmSync } from "node:fs";
|
|
4
4
|
|
|
5
|
-
export const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
export const SKILL_MIRRORS = [
|
|
6
|
+
{
|
|
7
|
+
name: "libretto",
|
|
8
|
+
source: "packages/libretto/skills/libretto",
|
|
9
|
+
targets: [".agents/skills/libretto", ".claude/skills/libretto"],
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: "libretto-readonly",
|
|
13
|
+
source: "packages/libretto/skills/libretto-readonly",
|
|
14
|
+
targets: [
|
|
15
|
+
".agents/skills/libretto-readonly",
|
|
16
|
+
".claude/skills/libretto-readonly",
|
|
17
|
+
],
|
|
18
|
+
},
|
|
9
19
|
];
|
|
10
20
|
|
|
11
21
|
export function syncSkillDir(sourceDir, destDir) {
|
package/skills/AGENTS.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Skills Directory
|
|
2
|
+
|
|
3
|
+
- `skills/libretto` is the source of truth for the interactive Libretto skill.
|
|
4
|
+
- `skills/libretto-readonly` is the source of truth for the read-only diagnosis skill.
|
|
5
|
+
- The mirrored copies in `.agents/skills/*` and `.claude/skills/*` are generated from the matching source directories under `skills/`.
|
|
6
|
+
- Edit files under `skills/` directly. Do not hand-edit the mirrored copies.
|
|
7
|
+
|
|
8
|
+
## Syncing
|
|
9
|
+
|
|
10
|
+
- Run `pnpm sync:mirrors` after changing anything under `skills/`.
|
|
11
|
+
- Run `pnpm check:mirrors` to verify that generated READMEs, skill mirrors, and skill version metadata are in sync.
|
package/skills/libretto/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: "Browser automation CLI for building, maintaining, and running brow
|
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
6
|
author: saffron-health
|
|
7
|
-
version: "0.5.
|
|
7
|
+
version: "0.5.6"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
## How Libretto Works
|
|
@@ -21,8 +21,10 @@ metadata:
|
|
|
21
21
|
|
|
22
22
|
## Setup
|
|
23
23
|
|
|
24
|
-
- Use `npx libretto
|
|
25
|
-
- If credentials are
|
|
24
|
+
- Use `npx libretto setup` for first-time workspace onboarding. It installs Chromium, syncs skills, and pins the default snapshot model to `.libretto/config.json` when provider credentials are available.
|
|
25
|
+
- Re-running `setup` on a healthy workspace shows the current configuration. If credentials are missing for a configured provider, it offers an interactive repair flow.
|
|
26
|
+
- Use `npx libretto status` to inspect AI configuration health and open sessions without triggering setup.
|
|
27
|
+
- Use `npx libretto ai configure openai|anthropic|gemini|vertex` to explicitly change the snapshot model or provider (advanced override).
|
|
26
28
|
|
|
27
29
|
## Working Rules
|
|
28
30
|
|
|
@@ -35,6 +37,7 @@ metadata:
|
|
|
35
37
|
- Treat exploration sessions as disposable unless the user explicitly wants one kept open.
|
|
36
38
|
- Get explicit user confirmation before mutating actions or replaying network requests that may have side effects.
|
|
37
39
|
- Never run multiple `exec` commands at the same time.
|
|
40
|
+
- If the browser must remain read-only, switch to the `libretto-readonly` skill and use `readonly-exec` instead of `exec`.
|
|
38
41
|
|
|
39
42
|
## Commands
|
|
40
43
|
|
|
@@ -43,23 +46,38 @@ metadata:
|
|
|
43
46
|
- Open a page before using `exec` or `snapshot`.
|
|
44
47
|
- Use `open` at the start of script authoring when you need live page state to decide how the workflow should work.
|
|
45
48
|
- Use headed mode when the user needs to log in or watch the workflow.
|
|
49
|
+
- Pass `--read-only` when you want the session locked for inspection from the moment it is created.
|
|
46
50
|
|
|
47
51
|
```bash
|
|
48
52
|
npx libretto open https://example.com --headed
|
|
53
|
+
npx libretto open https://example.com --headless --read-only --session readonly-example
|
|
49
54
|
npx libretto open https://example.com --headless --session debug-example
|
|
50
55
|
```
|
|
51
56
|
|
|
52
57
|
### `connect`
|
|
53
58
|
|
|
54
59
|
- Use `connect` to attach to any existing Chrome DevTools Protocol (CDP) endpoint — a browser started with `--remote-debugging-port`, an Electron app, or any other CDP-compatible target.
|
|
55
|
-
- After connecting, `exec`, `snapshot`, `pages`, and
|
|
60
|
+
- After connecting, `exec`, `snapshot`, `pages`, and the rest of the session commands follow that session's stored mode.
|
|
56
61
|
- Libretto does not manage the connected process's lifecycle. `close` clears the session but does not terminate the remote process.
|
|
62
|
+
- Pass `--read-only` if the connected session must stay inspection-only from the start.
|
|
57
63
|
|
|
58
64
|
```bash
|
|
59
65
|
npx libretto connect http://127.0.0.1:9222 --session my-session
|
|
66
|
+
npx libretto connect http://127.0.0.1:9222 --read-only --session readonly-session
|
|
60
67
|
npx libretto connect http://127.0.0.1:9223 --session another-session
|
|
61
68
|
```
|
|
62
69
|
|
|
70
|
+
### `session-mode`
|
|
71
|
+
|
|
72
|
+
- Use `session-mode` to inspect whether an existing session is `write-access` or `read-only`.
|
|
73
|
+
- Only a user can change the session mode for an existing session. Never change a session's mode on your own — the user must change it themselves manually.
|
|
74
|
+
- `open`, `run`, and `connect` default new sessions to `write-access` unless the config sets `sessionMode` to `read-only`.
|
|
75
|
+
- Pass `--read-only` or `--write-access` to override the config default for a single command.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx libretto session-mode --session my-session
|
|
79
|
+
```
|
|
80
|
+
|
|
63
81
|
### `snapshot`
|
|
64
82
|
|
|
65
83
|
- Use `snapshot` as the primary page observation tool.
|
|
@@ -87,6 +105,7 @@ npx libretto snapshot \
|
|
|
87
105
|
- Available globals: `page`, `context`, `browser`, `state`, `fetch`, `Buffer`.
|
|
88
106
|
- Let failures throw. Do not hide `exec` failures with `try/catch` or `.catch()`.
|
|
89
107
|
- Do not run multiple `exec` commands in parallel.
|
|
108
|
+
- Do not use `exec` in read-only diagnosis flows. Use `readonly-exec` from the `libretto-readonly` skill for those sessions.
|
|
90
109
|
|
|
91
110
|
```bash
|
|
92
111
|
npx libretto exec "return await page.url()"
|
|
@@ -98,7 +117,7 @@ echo "return await page.url()" | npx libretto exec - --session debug-example
|
|
|
98
117
|
### `pages`
|
|
99
118
|
|
|
100
119
|
- Use `pages` when a popup, new tab, or second page appears.
|
|
101
|
-
- If `exec
|
|
120
|
+
- If `exec` or `snapshot` complains about multiple pages, list page ids first and then pass `--page`.
|
|
102
121
|
|
|
103
122
|
```bash
|
|
104
123
|
npx libretto pages --session debug-example
|
|
@@ -109,14 +128,16 @@ npx libretto exec --session debug-example --page <page-id> "return await page.ur
|
|
|
109
128
|
|
|
110
129
|
- Use `run` to verify a workflow file after creating it or editing it, preferring `run --headless` for the normal fix/verify loop.
|
|
111
130
|
- Plain `run` defaults to headed mode.
|
|
131
|
+
- Pass `--read-only` if the preserved session should come back locked for follow-up terminal inspection after the workflow run.
|
|
112
132
|
- If the workflow fails, Libretto keeps the browser open. Inspect the failed state with `snapshot` and `exec` before editing code.
|
|
113
133
|
- Insert `await pause(session)` statements in the workflow file when you need to stop at specific states for interactive debugging, like breakpoints in the browser flow.
|
|
114
134
|
- If the workflow pauses, resume it with `npx libretto resume --session <name>`.
|
|
115
135
|
- Re-run the same workflow after each fix to verify the browser behavior end to end.
|
|
116
136
|
|
|
117
137
|
```bash
|
|
118
|
-
npx libretto run ./integration.ts
|
|
119
|
-
npx libretto run ./integration.ts
|
|
138
|
+
npx libretto run ./integration.ts --headless --params '{"status":"open"}'
|
|
139
|
+
npx libretto run ./integration.ts --headless --read-only
|
|
140
|
+
npx libretto run ./integration.ts --auth-profile app.example.com
|
|
120
141
|
```
|
|
121
142
|
|
|
122
143
|
### `resume`
|
|
@@ -191,7 +212,7 @@ Assistant: I'll inspect the real site first if needed, but before I finish I'll
|
|
|
191
212
|
Assistant: [Runs `npx libretto open https://target.example.com --headed`]
|
|
192
213
|
Assistant: [Reads `references/site-security-review.md` before choosing between passive network inspection, direct browser fetch calls, and Playwright-first automation]
|
|
193
214
|
Assistant: [Runs `npx libretto snapshot --objective "Find the next required action" --context "We are starting the workflow from the landing page and need the first meaningful step."`]
|
|
194
|
-
Assistant: [Uses `
|
|
215
|
+
Assistant: [Uses `snapshot` and `exec` as needed to understand the site and decide the implementation path]
|
|
195
216
|
Assistant: [Reads `references/code-generation-rules.md` before writing production workflow code]
|
|
196
217
|
Assistant: I found the working path. I'll now update the workflow file outside Libretto and verify it with `npx libretto run ...`.
|
|
197
218
|
</example>
|
|
@@ -203,7 +224,7 @@ Assistant: I found the working path. I'll now update the workflow file outside L
|
|
|
203
224
|
<example>
|
|
204
225
|
[Context: The user has an existing Libretto workflow that is failing]
|
|
205
226
|
Assistant: I'll reproduce the failure first so we can inspect the exact browser state it leaves behind.
|
|
206
|
-
Assistant: [Runs `npx libretto run ./integration.ts
|
|
227
|
+
Assistant: [Runs `npx libretto run ./integration.ts --session debug-flow --headed`]
|
|
207
228
|
Assistant: The workflow failed and Libretto kept the browser open. I'll inspect the page state before changing code.
|
|
208
229
|
Assistant: [Runs `npx libretto snapshot --session debug-flow --objective "Find the blocking error or broken selector target" --context "The workflow just failed after trying to continue from the review step, and I need to identify the visible blocker on the current page."`]
|
|
209
230
|
Assistant: [Runs `npx libretto exec --session debug-flow "...focused inspection or prototype..."`]
|
|
@@ -19,7 +19,7 @@ Use this reference only when the user explicitly asks to save or reuse local aut
|
|
|
19
19
|
```bash
|
|
20
20
|
npx libretto open https://app.example.com --headed
|
|
21
21
|
npx libretto save app.example.com
|
|
22
|
-
npx libretto run ./integration.ts
|
|
22
|
+
npx libretto run ./integration.ts --auth-profile app.example.com
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Notes
|