libretto 0.6.19 → 0.6.20
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 +10 -13
- package/README.template.md +10 -13
- package/dist/cli/cli.js +0 -14
- package/dist/cli/commands/deploy.js +5 -0
- package/dist/cli/commands/update.js +47 -18
- package/dist/cli/core/daemon/daemon.js +2 -0
- package/dist/cli/core/skill-version.js +8 -95
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -0
- package/dist/shared/workflow/workflow.d.ts +6 -1
- package/dist/shared/workflow/workflow.js +13 -10
- package/package.json +1 -1
- package/skills/libretto/SKILL.md +36 -35
- package/skills/libretto/references/auth-profiles.md +3 -3
- package/skills/libretto/references/code-generation-rules.md +2 -2
- package/skills/libretto/references/configuration-file-reference.md +10 -10
- package/skills/libretto/references/pages-and-page-targeting.md +3 -3
- package/skills/libretto-readonly/SKILL.md +1 -1
- package/src/cli/cli.ts +0 -17
- package/src/cli/commands/deploy.ts +5 -0
- package/src/cli/commands/update.ts +57 -19
- package/src/cli/core/daemon/daemon.ts +2 -0
- package/src/cli/core/skill-version.ts +10 -131
- package/src/index.ts +2 -0
- package/src/shared/workflow/workflow.ts +27 -10
package/README.md
CHANGED
|
@@ -28,17 +28,14 @@ https://github.com/user-attachments/assets/9b9a0ab3-5133-4b20-b3be-459943349d18
|
|
|
28
28
|
## Installation
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
#
|
|
32
|
-
curl -fsSL https://libretto.sh/install.sh | bash
|
|
33
|
-
|
|
34
|
-
# Add Libretto to your project
|
|
31
|
+
# Add Libretto to your project. Requires Node.js and npm.
|
|
35
32
|
npm install libretto
|
|
36
33
|
|
|
37
34
|
# First-time onboarding: install skills and download Chromium
|
|
38
|
-
libretto setup
|
|
35
|
+
npx libretto setup
|
|
39
36
|
|
|
40
37
|
# Check workspace readiness at any time
|
|
41
|
-
libretto status
|
|
38
|
+
npx libretto status
|
|
42
39
|
```
|
|
43
40
|
|
|
44
41
|
`setup` creates the `.libretto/` directory, installs agent skills, and downloads Chromium unless you pass `--skip-browsers`.
|
|
@@ -76,17 +73,17 @@ Agents can use Libretto to reproduce the failure, pause the workflow at any poin
|
|
|
76
73
|
You can also use Libretto directly from the command line. All commands accept `--session <name>` to target a specific session.
|
|
77
74
|
|
|
78
75
|
```bash
|
|
79
|
-
libretto open <url> # launch browser and open a URL
|
|
80
|
-
libretto run ./integration.ts --headless # run a workflow and close on success
|
|
81
|
-
libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
|
|
82
|
-
libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
|
|
83
|
-
libretto exec "<code>" # execute Playwright TypeScript against the open page
|
|
84
|
-
libretto close # close the browser
|
|
76
|
+
npx libretto open <url> # launch browser and open a URL
|
|
77
|
+
npx libretto run ./integration.ts --headless # run a workflow and close on success
|
|
78
|
+
npx libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
|
|
79
|
+
npx libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
|
|
80
|
+
npx libretto exec "<code>" # execute Playwright TypeScript against the open page
|
|
81
|
+
npx libretto close # close the browser
|
|
85
82
|
```
|
|
86
83
|
|
|
87
84
|
`run` sessions are inspectable through the same daemon-backed commands as `open` sessions. Successful runs close the browser by default; pass `--stay-open-on-success` to keep the browser open for `pages`, `snapshot`, and `exec`. Failed or paused workflows keep the browser open so you can inspect the exact page state before fixing or resuming the workflow.
|
|
88
85
|
|
|
89
|
-
Run `libretto help` for the full list of commands.
|
|
86
|
+
Run `npx libretto help` for the full list of commands.
|
|
90
87
|
|
|
91
88
|
## Configuration
|
|
92
89
|
|
package/README.template.md
CHANGED
|
@@ -26,17 +26,14 @@ https://github.com/user-attachments/assets/9b9a0ab3-5133-4b20-b3be-459943349d18
|
|
|
26
26
|
## Installation
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
#
|
|
30
|
-
curl -fsSL https://libretto.sh/install.sh | bash
|
|
31
|
-
|
|
32
|
-
# Add Libretto to your project
|
|
29
|
+
# Add Libretto to your project. Requires Node.js and npm.
|
|
33
30
|
npm install libretto
|
|
34
31
|
|
|
35
32
|
# First-time onboarding: install skills and download Chromium
|
|
36
|
-
libretto setup
|
|
33
|
+
npx libretto setup
|
|
37
34
|
|
|
38
35
|
# Check workspace readiness at any time
|
|
39
|
-
libretto status
|
|
36
|
+
npx libretto status
|
|
40
37
|
```
|
|
41
38
|
|
|
42
39
|
`setup` creates the `.libretto/` directory, installs agent skills, and downloads Chromium unless you pass `--skip-browsers`.
|
|
@@ -74,17 +71,17 @@ Agents can use Libretto to reproduce the failure, pause the workflow at any poin
|
|
|
74
71
|
You can also use Libretto directly from the command line. All commands accept `--session <name>` to target a specific session.
|
|
75
72
|
|
|
76
73
|
```bash
|
|
77
|
-
libretto open <url> # launch browser and open a URL
|
|
78
|
-
libretto run ./integration.ts --headless # run a workflow and close on success
|
|
79
|
-
libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
|
|
80
|
-
libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
|
|
81
|
-
libretto exec "<code>" # execute Playwright TypeScript against the open page
|
|
82
|
-
libretto close # close the browser
|
|
74
|
+
npx libretto open <url> # launch browser and open a URL
|
|
75
|
+
npx libretto run ./integration.ts --headless # run a workflow and close on success
|
|
76
|
+
npx libretto run ./integration.ts --headless --stay-open-on-success # keep a successful run inspectable
|
|
77
|
+
npx libretto snapshot --session <name> # capture a screenshot and compact accessibility tree
|
|
78
|
+
npx libretto exec "<code>" # execute Playwright TypeScript against the open page
|
|
79
|
+
npx libretto close # close the browser
|
|
83
80
|
```
|
|
84
81
|
|
|
85
82
|
`run` sessions are inspectable through the same daemon-backed commands as `open` sessions. Successful runs close the browser by default; pass `--stay-open-on-success` to keep the browser open for `pages`, `snapshot`, and `exec`. Failed or paused workflows keep the browser open so you can inspect the exact page state before fixing or resuming the workflow.
|
|
86
83
|
|
|
87
|
-
Run `libretto help` for the full list of commands.
|
|
84
|
+
Run `npx libretto help` for the full list of commands.
|
|
88
85
|
|
|
89
86
|
## Configuration
|
|
90
87
|
|
package/dist/cli/cli.js
CHANGED
|
@@ -11,19 +11,6 @@ function renderVersion() {
|
|
|
11
11
|
function printSetupAudit() {
|
|
12
12
|
warnIfLibrettoVersionsDiffer();
|
|
13
13
|
}
|
|
14
|
-
function isPackageManagerExec(env = process.env) {
|
|
15
|
-
return env.npm_command === "exec";
|
|
16
|
-
}
|
|
17
|
-
function warnIfPackageManagerExec() {
|
|
18
|
-
if (!isPackageManagerExec()) return;
|
|
19
|
-
console.error(
|
|
20
|
-
[
|
|
21
|
-
"Warning: running Libretto through a package manager is deprecated and will be removed in a future release.",
|
|
22
|
-
"Install the native command instead:",
|
|
23
|
-
" curl -fsSL https://libretto.sh/install.sh | bash"
|
|
24
|
-
].join("\n")
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
14
|
function isRootHelpRequest(rawArgs) {
|
|
28
15
|
if (rawArgs.length === 0) return true;
|
|
29
16
|
return rawArgs[0] === "help" && rawArgs.length === 1;
|
|
@@ -42,7 +29,6 @@ async function runLibrettoCLI() {
|
|
|
42
29
|
const rawArgs = process.argv.slice(2);
|
|
43
30
|
let exitCode = 0;
|
|
44
31
|
loadEnv();
|
|
45
|
-
warnIfPackageManagerExec();
|
|
46
32
|
ensureLibrettoSetup();
|
|
47
33
|
const app = createCLIApp();
|
|
48
34
|
try {
|
|
@@ -93,6 +93,10 @@ const deployInput = SimpleCLI.input({
|
|
|
93
93
|
name: "entry-point",
|
|
94
94
|
help: "Entry point file (default: index.ts)"
|
|
95
95
|
}),
|
|
96
|
+
autoRepair: SimpleCLI.flag({
|
|
97
|
+
name: "auto-repair",
|
|
98
|
+
help: "Route failed jobs for this deployment to autofix"
|
|
99
|
+
}),
|
|
96
100
|
external: SimpleCLI.option(
|
|
97
101
|
z.string().optional().transform(
|
|
98
102
|
(value) => value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? []
|
|
@@ -120,6 +124,7 @@ const deployCommand = SimpleCLI.command({
|
|
|
120
124
|
entry_point: entryPoint
|
|
121
125
|
};
|
|
122
126
|
if (input.description) createPayload.description = input.description;
|
|
127
|
+
if (input.autoRepair) createPayload.auto_repair = true;
|
|
123
128
|
console.log("Uploading deployment...");
|
|
124
129
|
const body = await orpcCall({
|
|
125
130
|
apiUrl,
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { SimpleCLI } from "affordance";
|
|
5
|
-
|
|
6
|
+
import { REPO_ROOT } from "../core/context.js";
|
|
7
|
+
import {
|
|
8
|
+
detectProjectPackageManager,
|
|
9
|
+
installCommand
|
|
10
|
+
} from "../../shared/package-manager.js";
|
|
11
|
+
function packageInstallCommand(packageManager, packageSpec) {
|
|
12
|
+
return `${installCommand(packageManager)} ${packageSpec}`;
|
|
13
|
+
}
|
|
6
14
|
function readCurrentCliVersion() {
|
|
7
15
|
const packageJsonPath = fileURLToPath(
|
|
8
16
|
new URL("../../../package.json", import.meta.url)
|
|
@@ -17,6 +25,21 @@ function readCurrentCliVersion() {
|
|
|
17
25
|
}
|
|
18
26
|
return manifest.version;
|
|
19
27
|
}
|
|
28
|
+
function readPackageVersion(packageJsonPath) {
|
|
29
|
+
try {
|
|
30
|
+
const manifest = JSON.parse(
|
|
31
|
+
readFileSync(packageJsonPath, "utf8")
|
|
32
|
+
);
|
|
33
|
+
return manifest.version?.trim() || null;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function readLocalPackageVersion() {
|
|
39
|
+
return readPackageVersion(
|
|
40
|
+
join(REPO_ROOT, "node_modules", "libretto", "package.json")
|
|
41
|
+
);
|
|
42
|
+
}
|
|
20
43
|
function readLatestNpmVersion() {
|
|
21
44
|
const result = spawnSync("npm", ["view", "libretto@latest", "version"], {
|
|
22
45
|
encoding: "utf8"
|
|
@@ -65,55 +88,61 @@ const updateInput = SimpleCLI.input({
|
|
|
65
88
|
})
|
|
66
89
|
}
|
|
67
90
|
});
|
|
68
|
-
function formatUpdateFailure(status, signal) {
|
|
69
|
-
const knownState = status === null ? `
|
|
91
|
+
function formatUpdateFailure(status, signal, updateCommand2) {
|
|
92
|
+
const knownState = status === null ? `package update was interrupted${signal ? ` by ${signal}` : ""}.` : `package update exited with status ${status}.`;
|
|
70
93
|
return [
|
|
71
94
|
"Error: failed to update Libretto to the latest version.",
|
|
72
95
|
`Known state: ${knownState}`,
|
|
73
|
-
`Try: ${
|
|
96
|
+
`Try: ${updateCommand2}`,
|
|
74
97
|
"Help: libretto help update"
|
|
75
98
|
].join("\n");
|
|
76
99
|
}
|
|
77
100
|
const updateCommand = SimpleCLI.command({
|
|
78
101
|
description: "Update Libretto to the latest version"
|
|
79
102
|
}).input(updateInput).handle(async ({ input }) => {
|
|
103
|
+
const packageManager = detectProjectPackageManager();
|
|
104
|
+
const updateCommand2 = packageInstallCommand(packageManager, "libretto@latest");
|
|
80
105
|
if (input.dryRun) {
|
|
81
106
|
console.log("Update command:");
|
|
82
|
-
console.log(` ${
|
|
107
|
+
console.log(` ${updateCommand2}`);
|
|
83
108
|
console.log("No changes made.");
|
|
84
109
|
return;
|
|
85
110
|
}
|
|
86
111
|
const currentVersion = readCurrentCliVersion();
|
|
112
|
+
const localPackageVersion = readLocalPackageVersion();
|
|
113
|
+
const installedVersion = localPackageVersion ?? currentVersion;
|
|
87
114
|
const latestVersion = readLatestNpmVersion();
|
|
88
|
-
console.log(`Current version: ${
|
|
115
|
+
console.log(`Current version: ${installedVersion}`);
|
|
89
116
|
console.log(`Latest version: ${latestVersion}`);
|
|
90
|
-
if (
|
|
91
|
-
console.log(`Libretto is already up to date (${
|
|
117
|
+
if (localPackageVersion && installedVersion === latestVersion) {
|
|
118
|
+
console.log(`Libretto is already up to date (${installedVersion}).`);
|
|
92
119
|
console.log("No further action required.");
|
|
93
120
|
return;
|
|
94
121
|
}
|
|
95
|
-
|
|
96
|
-
|
|
122
|
+
if (!localPackageVersion) {
|
|
123
|
+
console.log("Local package: not installed");
|
|
124
|
+
}
|
|
125
|
+
console.log("Updating local Libretto package to latest...");
|
|
126
|
+
const result = spawnSync(updateCommand2, {
|
|
97
127
|
stdio: "inherit",
|
|
98
|
-
|
|
99
|
-
...process.env,
|
|
100
|
-
LIBRETTO_VERSION: "latest"
|
|
101
|
-
}
|
|
128
|
+
shell: true
|
|
102
129
|
});
|
|
103
130
|
if (result.error) {
|
|
104
131
|
throw new Error(
|
|
105
132
|
[
|
|
106
|
-
"Error: failed to start the Libretto
|
|
133
|
+
"Error: failed to start the Libretto package update.",
|
|
107
134
|
`Known state: ${result.error.message}`,
|
|
108
|
-
`Try: ${
|
|
135
|
+
`Try: ${updateCommand2}`,
|
|
109
136
|
"Help: libretto help update"
|
|
110
137
|
].join("\n")
|
|
111
138
|
);
|
|
112
139
|
}
|
|
113
140
|
if (result.status !== 0) {
|
|
114
|
-
throw new Error(
|
|
141
|
+
throw new Error(
|
|
142
|
+
formatUpdateFailure(result.status, result.signal, updateCommand2)
|
|
143
|
+
);
|
|
115
144
|
}
|
|
116
|
-
console.log("Libretto updated to latest.");
|
|
145
|
+
console.log("Local Libretto package updated to latest.");
|
|
117
146
|
console.log("No further action required.");
|
|
118
147
|
});
|
|
119
148
|
export {
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
loadDefaultWorkflow
|
|
48
48
|
} from "../workflow-runtime.js";
|
|
49
49
|
import { WorkflowController } from "../workflow-runner/runner.js";
|
|
50
|
+
import { validateWorkflowInput } from "../../../shared/workflow/workflow.js";
|
|
50
51
|
function isOperationalPage(page) {
|
|
51
52
|
const url = page.url();
|
|
52
53
|
return !url.startsWith("devtools://") && !url.startsWith("chrome-error://");
|
|
@@ -682,6 +683,7 @@ async function main() {
|
|
|
682
683
|
loadedWorkflow = await loadDefaultWorkflow(
|
|
683
684
|
getAbsoluteIntegrationPath(config.workflow.integrationPath)
|
|
684
685
|
);
|
|
686
|
+
validateWorkflowInput(loadedWorkflow, config.workflow.params ?? {});
|
|
685
687
|
} catch (error) {
|
|
686
688
|
throw new UserFacingStartupError(
|
|
687
689
|
error instanceof Error ? error.message : String(error)
|
|
@@ -68,115 +68,28 @@ function readInstalledSkillVersions() {
|
|
|
68
68
|
}
|
|
69
69
|
return [...versions];
|
|
70
70
|
}
|
|
71
|
-
function
|
|
72
|
-
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/);
|
|
73
|
-
if (!match) {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
return {
|
|
77
|
-
major: Number(match[1]),
|
|
78
|
-
minor: Number(match[2]),
|
|
79
|
-
patch: Number(match[3]),
|
|
80
|
-
prerelease: match[4] ?? null
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
function compareVersions(left, right) {
|
|
84
|
-
const parsedLeft = parseVersion(left);
|
|
85
|
-
const parsedRight = parseVersion(right);
|
|
86
|
-
if (!parsedLeft || !parsedRight) {
|
|
87
|
-
return left.localeCompare(right);
|
|
88
|
-
}
|
|
89
|
-
for (const key of ["major", "minor", "patch"]) {
|
|
90
|
-
const diff = parsedLeft[key] - parsedRight[key];
|
|
91
|
-
if (diff !== 0) {
|
|
92
|
-
return diff;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (parsedLeft.prerelease === parsedRight.prerelease) {
|
|
96
|
-
return 0;
|
|
97
|
-
}
|
|
98
|
-
if (parsedLeft.prerelease === null) {
|
|
99
|
-
return 1;
|
|
100
|
-
}
|
|
101
|
-
if (parsedRight.prerelease === null) {
|
|
102
|
-
return -1;
|
|
103
|
-
}
|
|
104
|
-
return parsedLeft.prerelease.localeCompare(parsedRight.prerelease);
|
|
105
|
-
}
|
|
106
|
-
function selectTargetVersion(versions) {
|
|
107
|
-
const counts = /* @__PURE__ */ new Map();
|
|
108
|
-
for (const version of versions) {
|
|
109
|
-
counts.set(version, (counts.get(version) ?? 0) + 1);
|
|
110
|
-
}
|
|
111
|
-
const byCountThenVersion = [...counts.entries()].sort(
|
|
112
|
-
([leftVersion, leftCount], [rightVersion, rightCount]) => rightCount - leftCount || compareVersions(rightVersion, leftVersion)
|
|
113
|
-
);
|
|
114
|
-
return byCountThenVersion[0]?.[0] ?? versions[0] ?? "latest";
|
|
115
|
-
}
|
|
116
|
-
function formatVersion(version, targetVersion) {
|
|
117
|
-
return version === targetVersion ? version : `${version} (out of date)`;
|
|
118
|
-
}
|
|
119
|
-
function formatSkillVersions(versions, targetVersion) {
|
|
71
|
+
function formatSkillVersions(versions) {
|
|
120
72
|
if (versions.length === 0) {
|
|
121
73
|
return "not installed";
|
|
122
74
|
}
|
|
123
|
-
return versions.
|
|
124
|
-
}
|
|
125
|
-
function formatUpdateInstructions(components) {
|
|
126
|
-
const instructions = [];
|
|
127
|
-
if (components.cliVersion !== components.targetVersion) {
|
|
128
|
-
instructions.push(
|
|
129
|
-
` global CLI: curl -fsSL https://libretto.sh/install.sh | LIBRETTO_VERSION=${components.targetVersion} bash`
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
if (components.localPackageVersion && components.localPackageVersion !== components.targetVersion) {
|
|
133
|
-
instructions.push(
|
|
134
|
-
` local package: npm install libretto@${components.targetVersion}`
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
if (components.skillVersions.length > 0 && components.skillVersions.some(
|
|
138
|
-
(skillVersion) => skillVersion !== components.targetVersion
|
|
139
|
-
)) {
|
|
140
|
-
instructions.push(" agent skill: libretto setup");
|
|
141
|
-
}
|
|
142
|
-
return instructions;
|
|
75
|
+
return versions.join(", ");
|
|
143
76
|
}
|
|
144
77
|
function formatVersionWarning(components) {
|
|
145
|
-
const targetVersion = selectTargetVersion([
|
|
146
|
-
components.cliVersion,
|
|
147
|
-
...components.localPackageVersion ? [components.localPackageVersion] : [],
|
|
148
|
-
...components.skillVersions
|
|
149
|
-
]);
|
|
150
78
|
const skillLabel = components.skillVersions.length > 1 ? "agent skills" : "agent skill";
|
|
151
|
-
const updateInstructions = formatUpdateInstructions({
|
|
152
|
-
...components,
|
|
153
|
-
targetVersion
|
|
154
|
-
});
|
|
155
79
|
return [
|
|
156
|
-
"WARNING: Libretto version
|
|
157
|
-
|
|
158
|
-
`
|
|
159
|
-
|
|
160
|
-
` ${skillLabel}: ${formatSkillVersions(
|
|
161
|
-
components.skillVersions,
|
|
162
|
-
targetVersion
|
|
163
|
-
)}`,
|
|
164
|
-
"",
|
|
165
|
-
"How to update:",
|
|
166
|
-
...updateInstructions
|
|
80
|
+
"WARNING: Libretto skill version does not match the local package.",
|
|
81
|
+
` local package: ${components.localPackageVersion ?? `${components.cliVersion} (current command)`}`,
|
|
82
|
+
` ${skillLabel}: ${formatSkillVersions(components.skillVersions)}`,
|
|
83
|
+
"Fix: run libretto setup"
|
|
167
84
|
].join("\n");
|
|
168
85
|
}
|
|
169
86
|
function warnIfLibrettoVersionsDiffer() {
|
|
170
87
|
try {
|
|
171
88
|
const cliVersion = readCurrentCliVersion();
|
|
172
89
|
const localPackageVersion = readLocalPackageVersion();
|
|
90
|
+
const packageVersion = localPackageVersion ?? cliVersion;
|
|
173
91
|
const skillVersions = readInstalledSkillVersions();
|
|
174
|
-
|
|
175
|
-
cliVersion,
|
|
176
|
-
...localPackageVersion ? [localPackageVersion] : [],
|
|
177
|
-
...skillVersions
|
|
178
|
-
]);
|
|
179
|
-
if (observedVersions.size <= 1) {
|
|
92
|
+
if (skillVersions.length === 0 || skillVersions.every((skillVersion) => skillVersion === packageVersion)) {
|
|
180
93
|
return;
|
|
181
94
|
}
|
|
182
95
|
console.error(
|
package/dist/index.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export { InstrumentationOptions, InstrumentedPage, installInstrumentation, instr
|
|
|
12
12
|
export { GhostCursorOptions, ensureGhostCursor, ghostClick, hideGhostCursor, moveGhostCursor } from './shared/visualization/ghost-cursor.js';
|
|
13
13
|
export { HighlightOptions, clearHighlights, ensureHighlightLayer, showHighlight } from './shared/visualization/highlight.js';
|
|
14
14
|
export { BrowserSession, LaunchBrowserArgs, launchBrowser } from './shared/run/browser.js';
|
|
15
|
-
export { ExportedLibrettoWorkflow, LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, LibrettoWorkflowContext, LibrettoWorkflowHandler, LibrettoWorkflowInputError, LibrettoWorkflowSchemas, getDefaultWorkflowFromModuleExports, getWorkflowFromModuleExports, getWorkflowsFromModuleExports, isLibrettoWorkflow, workflow } from './shared/workflow/workflow.js';
|
|
15
|
+
export { ExportedLibrettoWorkflow, LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, LibrettoWorkflowContext, LibrettoWorkflowHandler, LibrettoWorkflowInputError, LibrettoWorkflowSchemas, WorkflowInputValidator, getDefaultWorkflowFromModuleExports, getWorkflowFromModuleExports, getWorkflowsFromModuleExports, isLibrettoWorkflow, validateWorkflowInput, workflow } from './shared/workflow/workflow.js';
|
|
16
16
|
import 'zod';
|
|
17
17
|
import 'playwright';
|
|
18
18
|
import 'ai';
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
LibrettoWorkflow,
|
|
60
60
|
LibrettoWorkflowInputError,
|
|
61
61
|
LIBRETTO_WORKFLOW_BRAND,
|
|
62
|
+
validateWorkflowInput,
|
|
62
63
|
workflow
|
|
63
64
|
} from "./shared/workflow/workflow.js";
|
|
64
65
|
const isDirectExecution = () => {
|
|
@@ -113,5 +114,6 @@ export {
|
|
|
113
114
|
prettyConsoleSink,
|
|
114
115
|
serializeSessionState,
|
|
115
116
|
showHighlight,
|
|
117
|
+
validateWorkflowInput,
|
|
116
118
|
workflow
|
|
117
119
|
};
|
|
@@ -16,6 +16,11 @@ declare class LibrettoWorkflowInputError extends Error {
|
|
|
16
16
|
readonly zodError: z.ZodError;
|
|
17
17
|
constructor(workflowName: string, zodError: z.ZodError);
|
|
18
18
|
}
|
|
19
|
+
type WorkflowInputValidator = {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly inputSchema?: z.ZodType;
|
|
22
|
+
};
|
|
23
|
+
declare function validateWorkflowInput(workflow: WorkflowInputValidator, input: unknown): void;
|
|
19
24
|
declare class LibrettoWorkflow<InputSchema extends z.ZodType = z.ZodType<unknown>, OutputSchema extends z.ZodType = z.ZodType<unknown>> {
|
|
20
25
|
readonly [LIBRETTO_WORKFLOW_BRAND] = true;
|
|
21
26
|
readonly name: string;
|
|
@@ -40,4 +45,4 @@ declare function getWorkflowFromModuleExports(moduleExports: WorkflowModuleExpor
|
|
|
40
45
|
declare function workflow<InputSchema extends z.ZodType, OutputSchema extends z.ZodType>(name: string, schemas: LibrettoWorkflowSchemas<InputSchema, OutputSchema>, handler: LibrettoWorkflowHandler<z.infer<InputSchema>, z.infer<OutputSchema>>): LibrettoWorkflow<InputSchema, OutputSchema>;
|
|
41
46
|
declare function workflow<Input = unknown, Output = unknown>(name: string, handler: LibrettoWorkflowHandler<Input, Output>): LibrettoWorkflow<z.ZodType<Input>, z.ZodType<Output>>;
|
|
42
47
|
|
|
43
|
-
export { type ExportedLibrettoWorkflow, LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, type LibrettoWorkflowContext, type LibrettoWorkflowHandler, LibrettoWorkflowInputError, type LibrettoWorkflowSchemas, getDefaultWorkflowFromModuleExports, getWorkflowFromModuleExports, getWorkflowsFromModuleExports, isLibrettoWorkflow, workflow };
|
|
48
|
+
export { type ExportedLibrettoWorkflow, LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, type LibrettoWorkflowContext, type LibrettoWorkflowHandler, LibrettoWorkflowInputError, type LibrettoWorkflowSchemas, type WorkflowInputValidator, getDefaultWorkflowFromModuleExports, getWorkflowFromModuleExports, getWorkflowsFromModuleExports, isLibrettoWorkflow, validateWorkflowInput, workflow };
|
|
@@ -19,6 +19,17 @@ function formatZodErrorMessage(workflowName, zodError) {
|
|
|
19
19
|
...lines
|
|
20
20
|
].join("\n");
|
|
21
21
|
}
|
|
22
|
+
function parseWorkflowInput(workflowName, inputSchema, input) {
|
|
23
|
+
if (!inputSchema) return input;
|
|
24
|
+
const result = inputSchema.safeParse(input);
|
|
25
|
+
if (!result.success) {
|
|
26
|
+
throw new LibrettoWorkflowInputError(workflowName, result.error);
|
|
27
|
+
}
|
|
28
|
+
return result.data;
|
|
29
|
+
}
|
|
30
|
+
function validateWorkflowInput(workflow2, input) {
|
|
31
|
+
parseWorkflowInput(workflow2.name, workflow2.inputSchema, input);
|
|
32
|
+
}
|
|
22
33
|
class LibrettoWorkflow {
|
|
23
34
|
[LIBRETTO_WORKFLOW_BRAND] = true;
|
|
24
35
|
name;
|
|
@@ -38,16 +49,7 @@ class LibrettoWorkflow {
|
|
|
38
49
|
this.handler = handler;
|
|
39
50
|
}
|
|
40
51
|
async run(ctx, input) {
|
|
41
|
-
|
|
42
|
-
if (this.inputSchema) {
|
|
43
|
-
const result = this.inputSchema.safeParse(input);
|
|
44
|
-
if (!result.success) {
|
|
45
|
-
throw new LibrettoWorkflowInputError(this.name, result.error);
|
|
46
|
-
}
|
|
47
|
-
parsed = result.data;
|
|
48
|
-
} else {
|
|
49
|
-
parsed = input;
|
|
50
|
-
}
|
|
52
|
+
const parsed = parseWorkflowInput(this.name, this.inputSchema, input);
|
|
51
53
|
return this.handler(ctx, parsed);
|
|
52
54
|
}
|
|
53
55
|
}
|
|
@@ -118,5 +120,6 @@ export {
|
|
|
118
120
|
getWorkflowFromModuleExports,
|
|
119
121
|
getWorkflowsFromModuleExports,
|
|
120
122
|
isLibrettoWorkflow,
|
|
123
|
+
validateWorkflowInput,
|
|
121
124
|
workflow
|
|
122
125
|
};
|
package/package.json
CHANGED
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.6.
|
|
7
|
+
version: "0.6.20"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
## How Libretto Works
|
|
@@ -39,12 +39,13 @@ Prefer to enter sites at a user-facing URL (homepage, login, etc.) on the first
|
|
|
39
39
|
|
|
40
40
|
## Setup
|
|
41
41
|
|
|
42
|
-
- Use
|
|
43
|
-
- Use `libretto
|
|
42
|
+
- Use the package manager convention for the target project. The examples use `npx libretto`; pnpm, yarn, and bun projects should use their equivalent package-manager execution form.
|
|
43
|
+
- Use `npx libretto setup` for first-time workspace onboarding. It installs Chromium and syncs skills.
|
|
44
|
+
- Use `npx libretto status` to inspect open sessions without triggering setup.
|
|
44
45
|
|
|
45
46
|
## Experiments
|
|
46
47
|
|
|
47
|
-
- Use `libretto experiments` to list internal feature flags and `libretto experiments describe <name>` for usage notes when an experiment is enabled.
|
|
48
|
+
- Use `npx libretto experiments` to list internal feature flags and `npx libretto experiments describe <name>` for usage notes when an experiment is enabled.
|
|
48
49
|
|
|
49
50
|
## Working Rules
|
|
50
51
|
|
|
@@ -83,9 +84,9 @@ npx libretto open https://example.com --session debug-example
|
|
|
83
84
|
- Pass `--read-only` if the connected session must stay inspection-only from the start.
|
|
84
85
|
|
|
85
86
|
```bash
|
|
86
|
-
libretto connect http://127.0.0.1:9222 --session my-session
|
|
87
|
-
libretto connect http://127.0.0.1:9222 --read-only --session readonly-session
|
|
88
|
-
libretto connect http://127.0.0.1:9223 --session another-session
|
|
87
|
+
npx libretto connect http://127.0.0.1:9222 --session my-session
|
|
88
|
+
npx libretto connect http://127.0.0.1:9222 --read-only --session readonly-session
|
|
89
|
+
npx libretto connect http://127.0.0.1:9223 --session another-session
|
|
89
90
|
```
|
|
90
91
|
|
|
91
92
|
### `session-mode`
|
|
@@ -96,7 +97,7 @@ libretto connect http://127.0.0.1:9223 --session another-session
|
|
|
96
97
|
- Pass `--read-only` or `--write-access` to override the config default for a single command.
|
|
97
98
|
|
|
98
99
|
```bash
|
|
99
|
-
libretto session-mode --session my-session
|
|
100
|
+
npx libretto session-mode --session my-session
|
|
100
101
|
```
|
|
101
102
|
|
|
102
103
|
### `snapshot`
|
|
@@ -108,9 +109,9 @@ libretto session-mode --session my-session
|
|
|
108
109
|
- Use it before guessing at selectors, after workflow failures, and whenever the visible page state is unclear.
|
|
109
110
|
|
|
110
111
|
```bash
|
|
111
|
-
libretto snapshot --session debug-example
|
|
112
|
-
libretto snapshot <ref> --session debug-example
|
|
113
|
-
libretto snapshot --session debug-example --page <page-id>
|
|
112
|
+
npx libretto snapshot --session debug-example
|
|
113
|
+
npx libretto snapshot <ref> --session debug-example
|
|
114
|
+
npx libretto snapshot --session debug-example --page <page-id>
|
|
114
115
|
```
|
|
115
116
|
|
|
116
117
|
### `exec`
|
|
@@ -126,10 +127,10 @@ libretto snapshot --session debug-example --page <page-id>
|
|
|
126
127
|
- After successful mutations, `exec` prints page-change diffs from compact snapshots.
|
|
127
128
|
|
|
128
129
|
```bash
|
|
129
|
-
libretto exec "await page.url()"
|
|
130
|
-
libretto exec "await page.locator('button:has-text(\"Continue\")').click()"
|
|
131
|
-
echo "async function textOf(selector) { return await page.locator(selector).textContent(); }" | libretto exec - --session debug-example
|
|
132
|
-
libretto exec --session debug-example "await textOf('h1')"
|
|
130
|
+
npx libretto exec "await page.url()"
|
|
131
|
+
npx libretto exec "await page.locator('button:has-text(\"Continue\")').click()"
|
|
132
|
+
echo "async function textOf(selector) { return await page.locator(selector).textContent(); }" | npx libretto exec - --session debug-example
|
|
133
|
+
npx libretto exec --session debug-example "await textOf('h1')"
|
|
133
134
|
```
|
|
134
135
|
|
|
135
136
|
### `pages`
|
|
@@ -138,8 +139,8 @@ libretto exec --session debug-example "await textOf('h1')"
|
|
|
138
139
|
- If `exec` or `snapshot` complains about multiple pages, list page ids first and then pass `--page`.
|
|
139
140
|
|
|
140
141
|
```bash
|
|
141
|
-
libretto pages --session debug-example
|
|
142
|
-
libretto exec --session debug-example --page <page-id> "await page.url()"
|
|
142
|
+
npx libretto pages --session debug-example
|
|
143
|
+
npx libretto exec --session debug-example --page <page-id> "await page.url()"
|
|
143
144
|
```
|
|
144
145
|
|
|
145
146
|
### `run`
|
|
@@ -150,14 +151,14 @@ libretto exec --session debug-example --page <page-id> "await page.url()"
|
|
|
150
151
|
- Pass `--read-only` if the preserved session should come back locked for follow-up terminal inspection after the workflow run.
|
|
151
152
|
- If the workflow fails, Libretto keeps the browser open. Inspect the failed state with `snapshot` and `exec` before editing code.
|
|
152
153
|
- 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.
|
|
153
|
-
- If the workflow pauses, resume it with `libretto resume --session <name>`.
|
|
154
|
+
- If the workflow pauses, resume it with `npx libretto resume --session <name>`.
|
|
154
155
|
- Re-run the same workflow after each fix to verify the browser behavior end to end.
|
|
155
156
|
|
|
156
157
|
```bash
|
|
157
|
-
libretto run ./integration.ts --params '{"status":"open"}'
|
|
158
|
-
libretto run ./integration.ts --read-only
|
|
159
|
-
libretto run ./integration.ts --stay-open-on-success
|
|
160
|
-
libretto run ./integration.ts --auth-profile app.example.com
|
|
158
|
+
npx libretto run ./integration.ts --params '{"status":"open"}'
|
|
159
|
+
npx libretto run ./integration.ts --read-only
|
|
160
|
+
npx libretto run ./integration.ts --stay-open-on-success
|
|
161
|
+
npx libretto run ./integration.ts --auth-profile app.example.com
|
|
161
162
|
```
|
|
162
163
|
|
|
163
164
|
### `resume`
|
|
@@ -168,7 +169,7 @@ libretto run ./integration.ts --auth-profile app.example.com
|
|
|
168
169
|
- Keep resuming the same session until the workflow completes or pauses again.
|
|
169
170
|
|
|
170
171
|
```bash
|
|
171
|
-
libretto resume --session debug-example
|
|
172
|
+
npx libretto resume --session debug-example
|
|
172
173
|
```
|
|
173
174
|
|
|
174
175
|
### `save`
|
|
@@ -176,7 +177,7 @@ libretto resume --session debug-example
|
|
|
176
177
|
- Use `save` only when the user explicitly asks to save or reuse authenticated browser state.
|
|
177
178
|
|
|
178
179
|
```bash
|
|
179
|
-
libretto save app.example.com
|
|
180
|
+
npx libretto save app.example.com
|
|
180
181
|
```
|
|
181
182
|
|
|
182
183
|
### `close`
|
|
@@ -185,8 +186,8 @@ libretto save app.example.com
|
|
|
185
186
|
- `close --all` is available for workspace cleanup.
|
|
186
187
|
|
|
187
188
|
```bash
|
|
188
|
-
libretto close --session debug-example
|
|
189
|
-
libretto close --all
|
|
189
|
+
npx libretto close --session debug-example
|
|
190
|
+
npx libretto close --all
|
|
190
191
|
```
|
|
191
192
|
|
|
192
193
|
## Session Logs
|
|
@@ -234,17 +235,17 @@ Key fields: `id` (incrementing request id), `ts` (ISO timestamp), `pageId` (page
|
|
|
234
235
|
<example>
|
|
235
236
|
[Context: The user wants to build a new browser workflow and does not yet know the page structure]
|
|
236
237
|
Assistant: I'll inspect the real site first if needed, but before I finish I'll create `target-workflow.ts` so the task produces reusable automation code.
|
|
237
|
-
Assistant: [Runs `libretto open https://target.example.com --headed`]
|
|
238
|
+
Assistant: [Runs `npx libretto open https://target.example.com --headed`]
|
|
238
239
|
Assistant: [Reads `references/site-security-review.md` before choosing between passive network inspection, direct browser fetch calls, and Playwright-first automation]
|
|
239
|
-
Assistant: [Runs `libretto snapshot --session <session>`]
|
|
240
|
+
Assistant: [Runs `npx libretto snapshot --session <session>`]
|
|
240
241
|
Assistant: [Uses `snapshot` and `exec` as needed to understand the site and decide the implementation path]
|
|
241
242
|
Assistant: [Reads `references/code-generation-rules.md` before writing production workflow code]
|
|
242
243
|
Assistant: I found the working path. I'll now create the workflow file and verify it.
|
|
243
244
|
Assistant: [Creates or edits `target-workflow.ts` following `references/code-generation-rules.md`]
|
|
244
|
-
Assistant: [Runs `libretto run ./target-workflow.ts --params '{"status":"open"}'` to validate]
|
|
245
|
+
Assistant: [Runs `npx libretto run ./target-workflow.ts --params '{"status":"open"}'` to validate]
|
|
245
246
|
Assistant: Validation passed. Here are the results:
|
|
246
247
|
[Shows the output/results from the validation run]
|
|
247
|
-
To run it again, use: libretto run ./target-workflow.ts --params '{"status":"open"}'
|
|
248
|
+
To run it again, use: npx libretto run ./target-workflow.ts --params '{"status":"open"}'
|
|
248
249
|
</example>
|
|
249
250
|
```
|
|
250
251
|
|
|
@@ -254,17 +255,17 @@ To run it again, use: libretto run ./target-workflow.ts --params '{"status":"ope
|
|
|
254
255
|
<example>
|
|
255
256
|
[Context: The user has an existing Libretto workflow that is failing]
|
|
256
257
|
Assistant: I'll reproduce the failure first so we can inspect the exact browser state it leaves behind.
|
|
257
|
-
Assistant: [Runs `libretto run ./integration.ts --session debug-flow`]
|
|
258
|
+
Assistant: [Runs `npx libretto run ./integration.ts --session debug-flow`]
|
|
258
259
|
Assistant: The workflow failed and Libretto kept the browser open. I'll inspect the page state before changing code.
|
|
259
|
-
Assistant: [Runs `libretto snapshot --session debug-flow`]
|
|
260
|
-
Assistant: [Runs `libretto exec --session debug-flow "...focused inspection or prototype..."`]
|
|
260
|
+
Assistant: [Runs `npx libretto snapshot --session debug-flow`]
|
|
261
|
+
Assistant: [Runs `npx libretto exec --session debug-flow "...focused inspection or prototype..."`]
|
|
261
262
|
Assistant: [Reads `references/code-generation-rules.md` before patching the workflow file]
|
|
262
263
|
Assistant: I found the issue. I'll patch the workflow code and verify.
|
|
263
264
|
Assistant: [Edits `integration.ts` following `references/code-generation-rules.md`]
|
|
264
|
-
Assistant: [Runs `libretto run ./integration.ts` to validate the fix]
|
|
265
|
+
Assistant: [Runs `npx libretto run ./integration.ts` to validate the fix]
|
|
265
266
|
Assistant: Fix verified. Here are the results:
|
|
266
267
|
[Shows the output/results from the validation run]
|
|
267
|
-
To run it again, use: libretto run ./integration.ts
|
|
268
|
+
To run it again, use: npx libretto run ./integration.ts
|
|
268
269
|
</example>
|
|
269
270
|
```
|
|
270
271
|
|
|
@@ -17,9 +17,9 @@ Use this reference only when the user explicitly asks to save or reuse local aut
|
|
|
17
17
|
## Commands
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
libretto open https://app.example.com --headed
|
|
21
|
-
libretto save app.example.com
|
|
22
|
-
libretto run ./integration.ts --auth-profile app.example.com
|
|
20
|
+
npx libretto open https://app.example.com --headed
|
|
21
|
+
npx libretto save app.example.com
|
|
22
|
+
npx libretto run ./integration.ts --auth-profile app.example.com
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Notes
|
|
@@ -6,7 +6,7 @@ Follow the user's existing codebase conventions, abstractions, and patterns when
|
|
|
6
6
|
|
|
7
7
|
## Workflow File Structure
|
|
8
8
|
|
|
9
|
-
Generated files must default-export a `workflow()` instance so they can be run via `libretto run <file>`. Workflows declare their input and output shapes as Zod schemas, which both type the handler and validate runtime input.
|
|
9
|
+
Generated files must default-export a `workflow()` instance so they can be run via `npx libretto run <file>`. Workflows declare their input and output shapes as Zod schemas, which both type the handler and validate runtime input.
|
|
10
10
|
|
|
11
11
|
Add `zod` (`^4.0.0`) to the workflow's `package.json` dependencies. Then import `workflow` from `"libretto"` and `z` from `"zod"`:
|
|
12
12
|
|
|
@@ -42,7 +42,7 @@ Key points:
|
|
|
42
42
|
|
|
43
43
|
- `workflow(name, { input, output }, handler)` takes a unique workflow name, a pair of Zod schemas describing input and output, and the async handler. The handler's `input` parameter is inferred from the input schema — do not redeclare it with a separate `type Input = ...`.
|
|
44
44
|
- At run time, Libretto validates `input` against `inputSchema` before calling the handler. Invalid input throws a clear error listing each failing field; the workflow handler never sees malformed input.
|
|
45
|
-
- `libretto run ./file.ts` executes the file's default-exported workflow, so always use `export default workflow(...)`.
|
|
45
|
+
- `npx libretto run ./file.ts` executes the file's default-exported workflow, so always use `export default workflow(...)`.
|
|
46
46
|
- `ctx` provides `session` and `page`. Use `console.log`/`console.warn`/`console.error` for logging — the runtime wraps these with structured metadata automatically.
|
|
47
47
|
- `input` comes from `--params '{"query":"foo"}'` or `--params-file params.json` on the CLI, then gets parsed through `inputSchema`.
|
|
48
48
|
- Use `await pause(ctx.session)` (or `await pause(session)`) to pause the workflow for debugging. It is a no-op in production.
|
|
@@ -12,8 +12,8 @@ Use this reference when you need to inspect or change workspace configuration fo
|
|
|
12
12
|
|
|
13
13
|
Libretto reads workspace config from `.libretto/config.json`.
|
|
14
14
|
|
|
15
|
-
- The file is created by `libretto setup` during first-time onboarding.
|
|
16
|
-
- Use `libretto status` to inspect open sessions without changing anything.
|
|
15
|
+
- The file is created by `npx libretto setup` during first-time onboarding.
|
|
16
|
+
- Use `npx libretto status` to inspect open sessions without changing anything.
|
|
17
17
|
- For first-time setup instructions, follow the main `SKILL.md` flow instead of expanding this reference.
|
|
18
18
|
|
|
19
19
|
## Supported Settings
|
|
@@ -42,17 +42,17 @@ Example:
|
|
|
42
42
|
## Common Commands
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
libretto setup # first-time onboarding
|
|
46
|
-
libretto status # inspect open sessions
|
|
47
|
-
libretto open https://example.com --provider kernel
|
|
48
|
-
libretto run ./integration.ts --provider browserbase
|
|
49
|
-
libretto open https://example.com --provider steel
|
|
50
|
-
libretto open https://example.com --viewport 1440x900
|
|
51
|
-
libretto run ./integration.ts --viewport 1440x900
|
|
45
|
+
npx libretto setup # first-time onboarding
|
|
46
|
+
npx libretto status # inspect open sessions
|
|
47
|
+
npx libretto open https://example.com --provider kernel
|
|
48
|
+
npx libretto run ./integration.ts --provider browserbase
|
|
49
|
+
npx libretto open https://example.com --provider steel
|
|
50
|
+
npx libretto open https://example.com --viewport 1440x900
|
|
51
|
+
npx libretto run ./integration.ts --viewport 1440x900
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
## Notes
|
|
55
55
|
|
|
56
56
|
- If you want a persistent default provider for the workspace, add `provider` to `.libretto/config.json` instead of repeating `--provider` on every command.
|
|
57
57
|
- If you want a persistent default viewport for the workspace, add `viewport` to `.libretto/config.json` instead of repeating `--viewport` on every command.
|
|
58
|
-
- Run `libretto status` at any time to check open sessions.
|
|
58
|
+
- Run `npx libretto status` at any time to check open sessions.
|
|
@@ -17,9 +17,9 @@ Use this reference when a Libretto session has multiple open pages and you need
|
|
|
17
17
|
## Commands
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
libretto pages --session debug-flow
|
|
21
|
-
libretto exec --session debug-flow --page <page-id> "return await page.url()"
|
|
22
|
-
libretto snapshot --session debug-flow --page <page-id>
|
|
20
|
+
npx libretto pages --session debug-flow
|
|
21
|
+
npx libretto exec --session debug-flow --page <page-id> "return await page.url()"
|
|
22
|
+
npx libretto snapshot --session debug-flow --page <page-id>
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Notes
|
package/src/cli/cli.ts
CHANGED
|
@@ -14,22 +14,6 @@ function printSetupAudit(): void {
|
|
|
14
14
|
warnIfLibrettoVersionsDiffer();
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function isPackageManagerExec(env: NodeJS.ProcessEnv = process.env): boolean {
|
|
18
|
-
return env.npm_command === "exec";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function warnIfPackageManagerExec(): void {
|
|
22
|
-
if (!isPackageManagerExec()) return;
|
|
23
|
-
|
|
24
|
-
console.error(
|
|
25
|
-
[
|
|
26
|
-
"Warning: running Libretto through a package manager is deprecated and will be removed in a future release.",
|
|
27
|
-
"Install the native command instead:",
|
|
28
|
-
" curl -fsSL https://libretto.sh/install.sh | bash",
|
|
29
|
-
].join("\n"),
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
17
|
function isRootHelpRequest(rawArgs: readonly string[]): boolean {
|
|
34
18
|
if (rawArgs.length === 0) return true;
|
|
35
19
|
return rawArgs[0] === "help" && rawArgs.length === 1;
|
|
@@ -55,7 +39,6 @@ export async function runLibrettoCLI(): Promise<void> {
|
|
|
55
39
|
const rawArgs = process.argv.slice(2);
|
|
56
40
|
let exitCode = 0;
|
|
57
41
|
loadEnv();
|
|
58
|
-
warnIfPackageManagerExec();
|
|
59
42
|
ensureLibrettoSetup();
|
|
60
43
|
const app = createCLIApp();
|
|
61
44
|
|
|
@@ -125,6 +125,10 @@ export const deployInput = SimpleCLI.input({
|
|
|
125
125
|
name: "entry-point",
|
|
126
126
|
help: "Entry point file (default: index.ts)",
|
|
127
127
|
}),
|
|
128
|
+
autoRepair: SimpleCLI.flag({
|
|
129
|
+
name: "auto-repair",
|
|
130
|
+
help: "Route failed jobs for this deployment to autofix",
|
|
131
|
+
}),
|
|
128
132
|
external: SimpleCLI.option(
|
|
129
133
|
z
|
|
130
134
|
.string()
|
|
@@ -167,6 +171,7 @@ export const deployCommand = SimpleCLI.command({
|
|
|
167
171
|
entry_point: entryPoint,
|
|
168
172
|
};
|
|
169
173
|
if (input.description) createPayload.description = input.description;
|
|
174
|
+
if (input.autoRepair) createPayload.auto_repair = true;
|
|
170
175
|
|
|
171
176
|
console.log("Uploading deployment...");
|
|
172
177
|
const body = await orpcCall<DeploymentResponse["json"]>({
|
|
@@ -1,14 +1,26 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { SimpleCLI } from "affordance";
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
import { REPO_ROOT } from "../core/context.js";
|
|
7
|
+
import {
|
|
8
|
+
detectProjectPackageManager,
|
|
9
|
+
installCommand,
|
|
10
|
+
type PackageManager,
|
|
11
|
+
} from "../../shared/package-manager.js";
|
|
7
12
|
|
|
8
13
|
type PackageManifest = {
|
|
9
14
|
version?: string;
|
|
10
15
|
};
|
|
11
16
|
|
|
17
|
+
function packageInstallCommand(
|
|
18
|
+
packageManager: PackageManager,
|
|
19
|
+
packageSpec: string,
|
|
20
|
+
): string {
|
|
21
|
+
return `${installCommand(packageManager)} ${packageSpec}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
12
24
|
function readCurrentCliVersion(): string {
|
|
13
25
|
const packageJsonPath = fileURLToPath(
|
|
14
26
|
new URL("../../../package.json", import.meta.url),
|
|
@@ -26,6 +38,23 @@ function readCurrentCliVersion(): string {
|
|
|
26
38
|
return manifest.version;
|
|
27
39
|
}
|
|
28
40
|
|
|
41
|
+
function readPackageVersion(packageJsonPath: string): string | null {
|
|
42
|
+
try {
|
|
43
|
+
const manifest = JSON.parse(
|
|
44
|
+
readFileSync(packageJsonPath, "utf8"),
|
|
45
|
+
) as PackageManifest;
|
|
46
|
+
return manifest.version?.trim() || null;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readLocalPackageVersion(): string | null {
|
|
53
|
+
return readPackageVersion(
|
|
54
|
+
join(REPO_ROOT, "node_modules", "libretto", "package.json"),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
29
58
|
function readLatestNpmVersion(): string {
|
|
30
59
|
const result = spawnSync("npm", ["view", "libretto@latest", "version"], {
|
|
31
60
|
encoding: "utf8",
|
|
@@ -83,16 +112,17 @@ export const updateInput = SimpleCLI.input({
|
|
|
83
112
|
function formatUpdateFailure(
|
|
84
113
|
status: number | null,
|
|
85
114
|
signal: string | null,
|
|
115
|
+
updateCommand: string,
|
|
86
116
|
): string {
|
|
87
117
|
const knownState =
|
|
88
118
|
status === null
|
|
89
|
-
? `
|
|
90
|
-
: `
|
|
119
|
+
? `package update was interrupted${signal ? ` by ${signal}` : ""}.`
|
|
120
|
+
: `package update exited with status ${status}.`;
|
|
91
121
|
|
|
92
122
|
return [
|
|
93
123
|
"Error: failed to update Libretto to the latest version.",
|
|
94
124
|
`Known state: ${knownState}`,
|
|
95
|
-
`Try: ${
|
|
125
|
+
`Try: ${updateCommand}`,
|
|
96
126
|
"Help: libretto help update",
|
|
97
127
|
].join("\n");
|
|
98
128
|
}
|
|
@@ -102,48 +132,56 @@ export const updateCommand = SimpleCLI.command({
|
|
|
102
132
|
})
|
|
103
133
|
.input(updateInput)
|
|
104
134
|
.handle(async ({ input }) => {
|
|
135
|
+
const packageManager = detectProjectPackageManager();
|
|
136
|
+
const updateCommand = packageInstallCommand(packageManager, "libretto@latest");
|
|
137
|
+
|
|
105
138
|
if (input.dryRun) {
|
|
106
139
|
console.log("Update command:");
|
|
107
|
-
console.log(` ${
|
|
140
|
+
console.log(` ${updateCommand}`);
|
|
108
141
|
console.log("No changes made.");
|
|
109
142
|
return;
|
|
110
143
|
}
|
|
111
144
|
|
|
112
145
|
const currentVersion = readCurrentCliVersion();
|
|
146
|
+
const localPackageVersion = readLocalPackageVersion();
|
|
147
|
+
const installedVersion = localPackageVersion ?? currentVersion;
|
|
113
148
|
const latestVersion = readLatestNpmVersion();
|
|
114
|
-
console.log(`Current version: ${
|
|
149
|
+
console.log(`Current version: ${installedVersion}`);
|
|
115
150
|
console.log(`Latest version: ${latestVersion}`);
|
|
116
151
|
|
|
117
|
-
if (
|
|
118
|
-
console.log(`Libretto is already up to date (${
|
|
152
|
+
if (localPackageVersion && installedVersion === latestVersion) {
|
|
153
|
+
console.log(`Libretto is already up to date (${installedVersion}).`);
|
|
119
154
|
console.log("No further action required.");
|
|
120
155
|
return;
|
|
121
156
|
}
|
|
122
157
|
|
|
123
|
-
|
|
124
|
-
|
|
158
|
+
if (!localPackageVersion) {
|
|
159
|
+
console.log("Local package: not installed");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log("Updating local Libretto package to latest...");
|
|
163
|
+
const result = spawnSync(updateCommand, {
|
|
125
164
|
stdio: "inherit",
|
|
126
|
-
|
|
127
|
-
...process.env,
|
|
128
|
-
LIBRETTO_VERSION: "latest",
|
|
129
|
-
},
|
|
165
|
+
shell: true,
|
|
130
166
|
});
|
|
131
167
|
|
|
132
168
|
if (result.error) {
|
|
133
169
|
throw new Error(
|
|
134
170
|
[
|
|
135
|
-
"Error: failed to start the Libretto
|
|
171
|
+
"Error: failed to start the Libretto package update.",
|
|
136
172
|
`Known state: ${result.error.message}`,
|
|
137
|
-
`Try: ${
|
|
173
|
+
`Try: ${updateCommand}`,
|
|
138
174
|
"Help: libretto help update",
|
|
139
175
|
].join("\n"),
|
|
140
176
|
);
|
|
141
177
|
}
|
|
142
178
|
|
|
143
179
|
if (result.status !== 0) {
|
|
144
|
-
throw new Error(
|
|
180
|
+
throw new Error(
|
|
181
|
+
formatUpdateFailure(result.status, result.signal, updateCommand),
|
|
182
|
+
);
|
|
145
183
|
}
|
|
146
184
|
|
|
147
|
-
console.log("Libretto updated to latest.");
|
|
185
|
+
console.log("Local Libretto package updated to latest.");
|
|
148
186
|
console.log("No further action required.");
|
|
149
187
|
});
|
|
@@ -89,6 +89,7 @@ import {
|
|
|
89
89
|
loadDefaultWorkflow,
|
|
90
90
|
} from "../workflow-runtime.js";
|
|
91
91
|
import { WorkflowController } from "../workflow-runner/runner.js";
|
|
92
|
+
import { validateWorkflowInput } from "../../../shared/workflow/workflow.js";
|
|
92
93
|
|
|
93
94
|
function isOperationalPage(page: Page): boolean {
|
|
94
95
|
const url = page.url();
|
|
@@ -941,6 +942,7 @@ async function main(): Promise<void> {
|
|
|
941
942
|
loadedWorkflow = await loadDefaultWorkflow(
|
|
942
943
|
getAbsoluteIntegrationPath(config.workflow.integrationPath),
|
|
943
944
|
);
|
|
945
|
+
validateWorkflowInput(loadedWorkflow, config.workflow.params ?? {});
|
|
944
946
|
} catch (error) {
|
|
945
947
|
throw new UserFacingStartupError(
|
|
946
948
|
error instanceof Error ? error.message : String(error),
|
|
@@ -89,115 +89,14 @@ function readInstalledSkillVersions(): string[] {
|
|
|
89
89
|
return [...versions];
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
function parseVersion(version: string): {
|
|
93
|
-
major: number;
|
|
94
|
-
minor: number;
|
|
95
|
-
patch: number;
|
|
96
|
-
prerelease: string | null;
|
|
97
|
-
} | null {
|
|
98
|
-
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/);
|
|
99
|
-
if (!match) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return {
|
|
104
|
-
major: Number(match[1]),
|
|
105
|
-
minor: Number(match[2]),
|
|
106
|
-
patch: Number(match[3]),
|
|
107
|
-
prerelease: match[4] ?? null,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function compareVersions(left: string, right: string): number {
|
|
112
|
-
const parsedLeft = parseVersion(left);
|
|
113
|
-
const parsedRight = parseVersion(right);
|
|
114
|
-
if (!parsedLeft || !parsedRight) {
|
|
115
|
-
return left.localeCompare(right);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
for (const key of ["major", "minor", "patch"] as const) {
|
|
119
|
-
const diff = parsedLeft[key] - parsedRight[key];
|
|
120
|
-
if (diff !== 0) {
|
|
121
|
-
return diff;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (parsedLeft.prerelease === parsedRight.prerelease) {
|
|
126
|
-
return 0;
|
|
127
|
-
}
|
|
128
|
-
if (parsedLeft.prerelease === null) {
|
|
129
|
-
return 1;
|
|
130
|
-
}
|
|
131
|
-
if (parsedRight.prerelease === null) {
|
|
132
|
-
return -1;
|
|
133
|
-
}
|
|
134
|
-
return parsedLeft.prerelease.localeCompare(parsedRight.prerelease);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function selectTargetVersion(versions: string[]): string {
|
|
138
|
-
const counts = new Map<string, number>();
|
|
139
|
-
for (const version of versions) {
|
|
140
|
-
counts.set(version, (counts.get(version) ?? 0) + 1);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const byCountThenVersion = [...counts.entries()].sort(
|
|
144
|
-
([leftVersion, leftCount], [rightVersion, rightCount]) =>
|
|
145
|
-
rightCount - leftCount || compareVersions(rightVersion, leftVersion),
|
|
146
|
-
);
|
|
147
|
-
|
|
148
|
-
return byCountThenVersion[0]?.[0] ?? versions[0] ?? "latest";
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function formatVersion(version: string, targetVersion: string): string {
|
|
152
|
-
return version === targetVersion ? version : `${version} (out of date)`;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
92
|
function formatSkillVersions(
|
|
156
93
|
versions: string[],
|
|
157
|
-
targetVersion: string,
|
|
158
94
|
): string {
|
|
159
95
|
if (versions.length === 0) {
|
|
160
96
|
return "not installed";
|
|
161
97
|
}
|
|
162
98
|
|
|
163
|
-
return versions
|
|
164
|
-
.map((version) => formatVersion(version, targetVersion))
|
|
165
|
-
.join(", ");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function formatUpdateInstructions(components: {
|
|
169
|
-
cliVersion: string;
|
|
170
|
-
localPackageVersion: string | null;
|
|
171
|
-
skillVersions: string[];
|
|
172
|
-
targetVersion: string;
|
|
173
|
-
}): string[] {
|
|
174
|
-
const instructions: string[] = [];
|
|
175
|
-
|
|
176
|
-
if (components.cliVersion !== components.targetVersion) {
|
|
177
|
-
instructions.push(
|
|
178
|
-
` global CLI: curl -fsSL https://libretto.sh/install.sh | LIBRETTO_VERSION=${components.targetVersion} bash`,
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (
|
|
183
|
-
components.localPackageVersion &&
|
|
184
|
-
components.localPackageVersion !== components.targetVersion
|
|
185
|
-
) {
|
|
186
|
-
instructions.push(
|
|
187
|
-
` local package: npm install libretto@${components.targetVersion}`,
|
|
188
|
-
);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (
|
|
192
|
-
components.skillVersions.length > 0 &&
|
|
193
|
-
components.skillVersions.some(
|
|
194
|
-
(skillVersion) => skillVersion !== components.targetVersion,
|
|
195
|
-
)
|
|
196
|
-
) {
|
|
197
|
-
instructions.push(" agent skill: libretto setup");
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return instructions;
|
|
99
|
+
return versions.join(", ");
|
|
201
100
|
}
|
|
202
101
|
|
|
203
102
|
function formatVersionWarning(components: {
|
|
@@ -205,34 +104,16 @@ function formatVersionWarning(components: {
|
|
|
205
104
|
localPackageVersion: string | null;
|
|
206
105
|
skillVersions: string[];
|
|
207
106
|
}): string {
|
|
208
|
-
const targetVersion = selectTargetVersion([
|
|
209
|
-
components.cliVersion,
|
|
210
|
-
...(components.localPackageVersion ? [components.localPackageVersion] : []),
|
|
211
|
-
...components.skillVersions,
|
|
212
|
-
]);
|
|
213
107
|
const skillLabel =
|
|
214
108
|
components.skillVersions.length > 1 ? "agent skills" : "agent skill";
|
|
215
|
-
const updateInstructions = formatUpdateInstructions({
|
|
216
|
-
...components,
|
|
217
|
-
targetVersion,
|
|
218
|
-
});
|
|
219
109
|
|
|
220
110
|
return [
|
|
221
|
-
"WARNING: Libretto version
|
|
222
|
-
"",
|
|
223
|
-
` global CLI: ${formatVersion(components.cliVersion, targetVersion)}`,
|
|
111
|
+
"WARNING: Libretto skill version does not match the local package.",
|
|
224
112
|
` local package: ${
|
|
225
|
-
components.localPackageVersion
|
|
226
|
-
? formatVersion(components.localPackageVersion, targetVersion)
|
|
227
|
-
: "not installed"
|
|
113
|
+
components.localPackageVersion ?? `${components.cliVersion} (current command)`
|
|
228
114
|
}`,
|
|
229
|
-
` ${skillLabel}: ${formatSkillVersions(
|
|
230
|
-
|
|
231
|
-
targetVersion,
|
|
232
|
-
)}`,
|
|
233
|
-
"",
|
|
234
|
-
"How to update:",
|
|
235
|
-
...updateInstructions,
|
|
115
|
+
` ${skillLabel}: ${formatSkillVersions(components.skillVersions)}`,
|
|
116
|
+
"Fix: run libretto setup",
|
|
236
117
|
].join("\n");
|
|
237
118
|
}
|
|
238
119
|
|
|
@@ -240,14 +121,12 @@ export function warnIfLibrettoVersionsDiffer(): void {
|
|
|
240
121
|
try {
|
|
241
122
|
const cliVersion = readCurrentCliVersion();
|
|
242
123
|
const localPackageVersion = readLocalPackageVersion();
|
|
124
|
+
const packageVersion = localPackageVersion ?? cliVersion;
|
|
243
125
|
const skillVersions = readInstalledSkillVersions();
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
]);
|
|
249
|
-
|
|
250
|
-
if (observedVersions.size <= 1) {
|
|
126
|
+
if (
|
|
127
|
+
skillVersions.length === 0 ||
|
|
128
|
+
skillVersions.every((skillVersion) => skillVersion === packageVersion)
|
|
129
|
+
) {
|
|
251
130
|
return;
|
|
252
131
|
}
|
|
253
132
|
|
package/src/index.ts
CHANGED
|
@@ -100,11 +100,13 @@ export {
|
|
|
100
100
|
LibrettoWorkflow,
|
|
101
101
|
LibrettoWorkflowInputError,
|
|
102
102
|
LIBRETTO_WORKFLOW_BRAND,
|
|
103
|
+
validateWorkflowInput,
|
|
103
104
|
workflow,
|
|
104
105
|
type ExportedLibrettoWorkflow,
|
|
105
106
|
type LibrettoWorkflowContext,
|
|
106
107
|
type LibrettoWorkflowHandler,
|
|
107
108
|
type LibrettoWorkflowSchemas,
|
|
109
|
+
type WorkflowInputValidator,
|
|
108
110
|
} from "./shared/workflow/workflow.js";
|
|
109
111
|
const isDirectExecution = (): boolean => {
|
|
110
112
|
const entryArg = process.argv[1];
|
|
@@ -49,6 +49,32 @@ function formatZodErrorMessage(
|
|
|
49
49
|
].join("\n");
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
function parseWorkflowInput<InputSchema extends z.ZodType>(
|
|
53
|
+
workflowName: string,
|
|
54
|
+
inputSchema: InputSchema | undefined,
|
|
55
|
+
input: unknown,
|
|
56
|
+
): z.infer<InputSchema> {
|
|
57
|
+
if (!inputSchema) return input as z.infer<InputSchema>;
|
|
58
|
+
|
|
59
|
+
const result = inputSchema.safeParse(input);
|
|
60
|
+
if (!result.success) {
|
|
61
|
+
throw new LibrettoWorkflowInputError(workflowName, result.error);
|
|
62
|
+
}
|
|
63
|
+
return result.data;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export type WorkflowInputValidator = {
|
|
67
|
+
readonly name: string;
|
|
68
|
+
readonly inputSchema?: z.ZodType;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export function validateWorkflowInput(
|
|
72
|
+
workflow: WorkflowInputValidator,
|
|
73
|
+
input: unknown,
|
|
74
|
+
): void {
|
|
75
|
+
parseWorkflowInput(workflow.name, workflow.inputSchema, input);
|
|
76
|
+
}
|
|
77
|
+
|
|
52
78
|
export class LibrettoWorkflow<
|
|
53
79
|
InputSchema extends z.ZodType = z.ZodType<unknown>,
|
|
54
80
|
OutputSchema extends z.ZodType = z.ZodType<unknown>,
|
|
@@ -86,16 +112,7 @@ export class LibrettoWorkflow<
|
|
|
86
112
|
ctx: LibrettoWorkflowContext,
|
|
87
113
|
input: unknown,
|
|
88
114
|
): Promise<z.infer<OutputSchema>> {
|
|
89
|
-
|
|
90
|
-
if (this.inputSchema) {
|
|
91
|
-
const result = this.inputSchema.safeParse(input);
|
|
92
|
-
if (!result.success) {
|
|
93
|
-
throw new LibrettoWorkflowInputError(this.name, result.error);
|
|
94
|
-
}
|
|
95
|
-
parsed = result.data;
|
|
96
|
-
} else {
|
|
97
|
-
parsed = input as z.infer<InputSchema>;
|
|
98
|
-
}
|
|
115
|
+
const parsed = parseWorkflowInput(this.name, this.inputSchema, input);
|
|
99
116
|
return this.handler(ctx, parsed);
|
|
100
117
|
}
|
|
101
118
|
}
|