libretto 0.5.4 → 0.5.6
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 +71 -6
- package/dist/cli/commands/execution.js +101 -44
- package/dist/cli/commands/setup.js +376 -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 +81 -42
- package/dist/cli/core/{ai-config.js → config.js} +13 -79
- package/dist/cli/core/context.js +1 -25
- package/dist/cli/core/deploy-artifact.js +121 -61
- 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 +44 -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 +29 -25
- package/dist/cli/workers/run-integration-worker-protocol.js +3 -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 +4 -1
- package/dist/shared/run/browser.js +5 -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 +10 -1
- package/dist/shared/state/session-state.js +3 -0
- package/dist/shared/workflow/workflow.d.ts +2 -3
- package/dist/shared/workflow/workflow.js +16 -9
- package/package.json +3 -4
- 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 +6 -6
- 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 +81 -7
- package/src/cli/commands/execution.ts +128 -61
- package/src/cli/commands/setup.ts +499 -0
- package/src/cli/commands/snapshot.ts +2 -2
- package/src/cli/commands/status.ts +77 -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 +107 -45
- package/src/cli/core/{ai-config.ts → config.ts} +13 -108
- package/src/cli/core/context.ts +1 -45
- package/src/cli/core/deploy-artifact.ts +141 -71
- 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 +62 -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 +36 -31
- package/src/cli/workers/run-integration-worker-protocol.ts +2 -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 +5 -1
- package/src/shared/state/index.ts +2 -0
- package/src/shared/state/session-state.ts +3 -0
- package/src/shared/workflow/workflow.ts +24 -15
- 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/dist/shared/llm/types.js +0 -0
- 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
|
@@ -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,7 +63,8 @@ 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"
|
|
67
68
|
}) {
|
|
68
69
|
const debugPort = await pickFreePort();
|
|
69
70
|
const windowPosition = headless ? void 0 : resolveWindowPosition();
|
|
@@ -96,7 +97,8 @@ async function launchBrowser({
|
|
|
96
97
|
port: debugPort,
|
|
97
98
|
pid: process.pid,
|
|
98
99
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
99
|
-
status: "active"
|
|
100
|
+
status: "active",
|
|
101
|
+
mode: accessMode
|
|
100
102
|
},
|
|
101
103
|
null,
|
|
102
104
|
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,
|
|
@@ -8,6 +8,10 @@ declare const SessionStatusSchema: z.ZodEnum<{
|
|
|
8
8
|
failed: "failed";
|
|
9
9
|
exited: "exited";
|
|
10
10
|
}>;
|
|
11
|
+
declare const SessionAccessModeSchema: z.ZodEnum<{
|
|
12
|
+
"read-only": "read-only";
|
|
13
|
+
"write-access": "write-access";
|
|
14
|
+
}>;
|
|
11
15
|
declare const SessionViewportSchema: z.ZodObject<{
|
|
12
16
|
width: z.ZodNumber;
|
|
13
17
|
height: z.ZodNumber;
|
|
@@ -26,16 +30,21 @@ declare const SessionStateFileSchema: z.ZodObject<{
|
|
|
26
30
|
failed: "failed";
|
|
27
31
|
exited: "exited";
|
|
28
32
|
}>>;
|
|
33
|
+
mode: z.ZodDefault<z.ZodEnum<{
|
|
34
|
+
"read-only": "read-only";
|
|
35
|
+
"write-access": "write-access";
|
|
36
|
+
}>>;
|
|
29
37
|
viewport: z.ZodOptional<z.ZodObject<{
|
|
30
38
|
width: z.ZodNumber;
|
|
31
39
|
height: z.ZodNumber;
|
|
32
40
|
}, z.core.$strip>>;
|
|
33
41
|
}, z.core.$strip>;
|
|
34
42
|
type SessionStatus = z.infer<typeof SessionStatusSchema>;
|
|
43
|
+
type SessionAccessMode = z.infer<typeof SessionAccessModeSchema>;
|
|
35
44
|
type SessionStateFile = z.infer<typeof SessionStateFileSchema>;
|
|
36
45
|
type SessionState = Omit<SessionStateFile, "version">;
|
|
37
46
|
declare function parseSessionStateData(rawState: unknown, source: string): SessionState;
|
|
38
47
|
declare function parseSessionStateContent(content: string, source: string): SessionState;
|
|
39
48
|
declare function serializeSessionState(state: SessionState): SessionStateFile;
|
|
40
49
|
|
|
41
|
-
export { SESSION_STATE_VERSION, type SessionState, type SessionStateFile, SessionStateFileSchema, type SessionStatus, SessionStatusSchema, SessionViewportSchema, parseSessionStateContent, parseSessionStateData, serializeSessionState };
|
|
50
|
+
export { SESSION_STATE_VERSION, type SessionAccessMode, SessionAccessModeSchema, type SessionState, type SessionStateFile, SessionStateFileSchema, type SessionStatus, SessionStatusSchema, SessionViewportSchema, parseSessionStateContent, parseSessionStateData, serializeSessionState };
|
|
@@ -7,6 +7,7 @@ const SessionStatusSchema = z.enum([
|
|
|
7
7
|
"failed",
|
|
8
8
|
"exited"
|
|
9
9
|
]);
|
|
10
|
+
const SessionAccessModeSchema = z.enum(["read-only", "write-access"]);
|
|
10
11
|
const SessionViewportSchema = z.object({
|
|
11
12
|
width: z.number().int().min(1),
|
|
12
13
|
height: z.number().int().min(1)
|
|
@@ -19,6 +20,7 @@ const SessionStateFileSchema = z.object({
|
|
|
19
20
|
session: z.string().min(1),
|
|
20
21
|
startedAt: z.string().datetime({ offset: true }),
|
|
21
22
|
status: SessionStatusSchema.optional(),
|
|
23
|
+
mode: SessionAccessModeSchema.default("write-access"),
|
|
22
24
|
viewport: SessionViewportSchema.optional()
|
|
23
25
|
});
|
|
24
26
|
function formatIssues(error) {
|
|
@@ -56,6 +58,7 @@ function serializeSessionState(state) {
|
|
|
56
58
|
}
|
|
57
59
|
export {
|
|
58
60
|
SESSION_STATE_VERSION,
|
|
61
|
+
SessionAccessModeSchema,
|
|
59
62
|
SessionStateFileSchema,
|
|
60
63
|
SessionStatusSchema,
|
|
61
64
|
SessionViewportSchema,
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Page } from 'playwright';
|
|
2
|
-
import { MinimalLogger } from '../logger/logger.js';
|
|
3
2
|
|
|
4
3
|
declare const LIBRETTO_WORKFLOW_BRAND: unique symbol;
|
|
5
4
|
type LibrettoWorkflowContext = {
|
|
6
5
|
session: string;
|
|
7
6
|
page: Page;
|
|
8
|
-
logger: MinimalLogger;
|
|
9
7
|
};
|
|
10
8
|
type LibrettoWorkflowHandler<Input = unknown, Output = unknown> = (ctx: LibrettoWorkflowContext, input: Input) => Promise<Output>;
|
|
11
9
|
declare class LibrettoWorkflow<Input = unknown, Output = unknown> {
|
|
@@ -23,7 +21,8 @@ type ExportedLibrettoWorkflow = {
|
|
|
23
21
|
type WorkflowModuleExports = Record<string, unknown>;
|
|
24
22
|
declare function isLibrettoWorkflow(value: unknown): value is ExportedLibrettoWorkflow;
|
|
25
23
|
declare function getWorkflowsFromModuleExports(moduleExports: WorkflowModuleExports): ExportedLibrettoWorkflow[];
|
|
24
|
+
declare function getDefaultWorkflowFromModuleExports(moduleExports: WorkflowModuleExports): ExportedLibrettoWorkflow | null;
|
|
26
25
|
declare function getWorkflowFromModuleExports(moduleExports: WorkflowModuleExports, workflowName: string): ExportedLibrettoWorkflow | null;
|
|
27
26
|
declare function workflow<Input = unknown, Output = unknown>(name: string, handler: LibrettoWorkflowHandler<Input, Output>): LibrettoWorkflow<Input, Output>;
|
|
28
27
|
|
|
29
|
-
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.5.
|
|
3
|
+
"version": "0.5.6",
|
|
4
4
|
"description": "AI-powered browser automation library and CLI built on Playwright",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"url": "https://github.com/saffron-health/libretto"
|
|
9
9
|
},
|
|
10
10
|
"type": "module",
|
|
11
|
-
"packageManager": "pnpm@
|
|
11
|
+
"packageManager": "pnpm@10.33.0",
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public"
|
|
14
14
|
},
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"dist",
|
|
17
17
|
"src",
|
|
18
18
|
"scripts",
|
|
19
|
-
"skills
|
|
19
|
+
"skills"
|
|
20
20
|
],
|
|
21
21
|
"types": "./dist/index.d.ts",
|
|
22
22
|
"bin": {
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"postinstall": "node scripts/postinstall.mjs",
|
|
34
33
|
"sync:mirrors": "node ../dev-tools/scripts/sync-mirrors.mjs",
|
|
35
34
|
"check:mirrors": "node ../dev-tools/scripts/check-mirrors-sync.mjs",
|
|
36
35
|
"sync-skills": "pnpm run sync:mirrors",
|
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
|
|
@@ -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 export a `workflow()` instance so they can be run via `npx libretto run <file
|
|
9
|
+
Generated files must default-export a `workflow()` instance so they can be run via `npx libretto run <file>`. Import `workflow` and its types from `"libretto"`:
|
|
10
10
|
|
|
11
11
|
```typescript
|
|
12
12
|
import { workflow, pause, type LibrettoWorkflowContext } from "libretto";
|
|
@@ -22,12 +22,12 @@ type Output = {
|
|
|
22
22
|
results: Array<{ name: string; value: string }>;
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
export
|
|
25
|
+
export default workflow<Input, Output>(
|
|
26
26
|
"myWorkflow",
|
|
27
27
|
async (ctx: LibrettoWorkflowContext, input): Promise<Output> => {
|
|
28
|
-
const { session, page
|
|
28
|
+
const { session, page } = ctx;
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
console.log("workflow-start", { session, query: input.query });
|
|
31
31
|
await page.goto("https://example.com");
|
|
32
32
|
await pause(session);
|
|
33
33
|
|
|
@@ -39,8 +39,8 @@ export const myWorkflow = workflow<Input, Output>(
|
|
|
39
39
|
Key points:
|
|
40
40
|
|
|
41
41
|
- `workflow(name, handler)` takes a unique workflow name and returns the workflow object that Libretto can run.
|
|
42
|
-
- `npx libretto run ./file.ts
|
|
43
|
-
- `ctx` provides `session
|
|
42
|
+
- `npx libretto run ./file.ts` executes the file's default-exported workflow, so always use `export default workflow(...)`.
|
|
43
|
+
- `ctx` provides `session` and `page`. Use `console.log`/`console.warn`/`console.error` for logging — the runtime wraps these with structured metadata automatically.
|
|
44
44
|
- `input` comes from `--params '{"query":"foo"}'` or `--params-file params.json` on the CLI
|
|
45
45
|
- Use `await pause(ctx.session)` (or `await pause(session)`) to pause the workflow for debugging. It is a no-op in production.
|
|
46
46
|
- After validation is complete and the workflow is confirmed working end to end, remove all `pause()` calls and pause-only workflow params unless the user explicitly says to keep them.
|
|
@@ -12,8 +12,9 @@ Use this reference when you need to inspect or change the workspace configuratio
|
|
|
12
12
|
|
|
13
13
|
Libretto reads workspace config from `.libretto/config.json`.
|
|
14
14
|
|
|
15
|
-
- The file is
|
|
15
|
+
- The file is created by `npx libretto setup` during first-time onboarding (auto-pins the default model for the detected provider) or by `npx libretto ai configure ...` for explicit overrides.
|
|
16
16
|
- API credentials still come from your shell environment or `.env`. The config file stores the selected model, not the secret itself.
|
|
17
|
+
- Use `npx libretto status` to inspect the current AI configuration and open sessions without changing anything.
|
|
17
18
|
- For first-time setup instructions, follow the main `SKILL.md` flow instead of expanding this reference.
|
|
18
19
|
|
|
19
20
|
## Supported Settings
|
|
@@ -21,6 +22,7 @@ Libretto reads workspace config from `.libretto/config.json`.
|
|
|
21
22
|
- `ai.model` selects the configured analysis model for `snapshot`.
|
|
22
23
|
- `viewport` is an optional top-level setting used by `open` and `run` when you do not pass `--viewport`.
|
|
23
24
|
- Viewport precedence is: CLI `--viewport`, then `.libretto/config.json`, then the default `1366x768`.
|
|
25
|
+
- `sessionMode` sets the default session access mode for new sessions created by `open`, `connect`, and `run`. Must be `"read-only"` or `"write-access"`. When omitted, defaults to `"write-access"`. Pass `--read-only` or `--write-access` to `open`, `connect`, or `run` to override when creating a session.
|
|
24
26
|
|
|
25
27
|
Example:
|
|
26
28
|
|
|
@@ -34,20 +36,23 @@ Example:
|
|
|
34
36
|
"viewport": {
|
|
35
37
|
"width": 1280,
|
|
36
38
|
"height": 800
|
|
37
|
-
}
|
|
39
|
+
},
|
|
40
|
+
"sessionMode": "write-access"
|
|
38
41
|
}
|
|
39
42
|
```
|
|
40
43
|
|
|
41
44
|
## Common Commands
|
|
42
45
|
|
|
43
46
|
```bash
|
|
44
|
-
npx libretto
|
|
45
|
-
npx libretto
|
|
47
|
+
npx libretto setup # first-time onboarding, auto-pins default model
|
|
48
|
+
npx libretto status # inspect AI config and open sessions
|
|
49
|
+
npx libretto ai configure openai # explicitly change provider/model
|
|
46
50
|
npx libretto open https://app.example.com --viewport 1440x900
|
|
47
|
-
npx libretto run ./integration.ts
|
|
51
|
+
npx libretto run ./integration.ts --viewport 1440x900
|
|
48
52
|
```
|
|
49
53
|
|
|
50
54
|
## Notes
|
|
51
55
|
|
|
52
56
|
- If you want a persistent default viewport for the workspace, add `viewport` to `.libretto/config.json` instead of repeating `--viewport` on every command.
|
|
53
|
-
- If `snapshot` analysis is not configured yet,
|
|
57
|
+
- If `snapshot` analysis is not configured yet, run `npx libretto setup` to auto-configure, or see the main `SKILL.md` flow.
|
|
58
|
+
- Run `npx libretto status` at any time to check which model is active and whether credentials are present.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: libretto-readonly
|
|
3
|
+
description: "Read-only Libretto workflow for diagnosing live browser state without clicks, typing, navigation, or mutation requests."
|
|
4
|
+
license: MIT
|
|
5
|
+
metadata:
|
|
6
|
+
author: saffron-health
|
|
7
|
+
version: "0.5.6"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## How Libretto Read-Only Works
|
|
11
|
+
|
|
12
|
+
- Use this skill when the browser session must stay strictly read-only.
|
|
13
|
+
- Libretto stores read-only vs write-access on the session itself.
|
|
14
|
+
- The primary inspection tools are `snapshot` and `readonly-exec`.
|
|
15
|
+
- `readonly-exec` reuses Libretto's normal execution pipeline, but it only exposes read-only helpers and denies mutating Playwright methods.
|
|
16
|
+
- 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.
|
|
17
|
+
|
|
18
|
+
## Working Rules
|
|
19
|
+
|
|
20
|
+
- Announce which session you are using and what page you are inspecting.
|
|
21
|
+
- Do not use `exec`, `run`, or any direct Playwright action that could change browser or application state.
|
|
22
|
+
- Do not click, type, submit forms, navigate, upload files, dispatch DOM events, or send non-GET requests.
|
|
23
|
+
- Prefer `snapshot` first when the visible page state is unclear.
|
|
24
|
+
- Use `readonly-exec` for focused inspection: titles, HTML, locator text, counts, visibility checks, and GET requests.
|
|
25
|
+
- Keep snippets small and purpose-built. Do not run multiple `readonly-exec` commands at the same time.
|
|
26
|
+
- End with diagnosis and handoff guidance, not an attempted in-browser repair.
|
|
27
|
+
|
|
28
|
+
## Commands
|
|
29
|
+
|
|
30
|
+
### `connect`
|
|
31
|
+
|
|
32
|
+
- Use `connect` to attach to an existing CDP endpoint for a preserved browser session.
|
|
33
|
+
- Use `--read-only` when creating the Libretto session handle for a preserved browser session.
|
|
34
|
+
- Libretto read-only mode is enforced through Libretto commands; direct CDP clients that skip Libretto are outside this boundary.
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx libretto connect http://127.0.0.1:9222 --read-only --session failed-job-debug
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### `pages`
|
|
41
|
+
|
|
42
|
+
- Use `pages` when a popup, new tab, or second page exists.
|
|
43
|
+
- If `readonly-exec` or `snapshot` complains about multiple pages, list ids first and then pass `--page`.
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npx libretto pages --session failed-job-debug
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### `snapshot`
|
|
50
|
+
|
|
51
|
+
- Use `snapshot` as the first high-level observation tool.
|
|
52
|
+
- Always provide both `--objective` and `--context`.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx libretto snapshot \
|
|
56
|
+
--session failed-job-debug \
|
|
57
|
+
--objective "Identify the visible failure state and likely blocking UI condition" \
|
|
58
|
+
--context "The workflow already failed and the preserved browser must remain read-only."
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### `readonly-exec`
|
|
62
|
+
|
|
63
|
+
- Use `readonly-exec` for narrow inspection code only.
|
|
64
|
+
- Denied operations fail with `ReadonlyExecDenied: ...`.
|
|
65
|
+
|
|
66
|
+
#### Helpers
|
|
67
|
+
|
|
68
|
+
- `page` — a read-only Playwright `Page` proxy. Standard Playwright read methods work normally (`url()`, `title()`, `content()`, `getByRole()`, `locator()`, `textContent()`, `isVisible()`, `count()`, `scrollIntoViewIfNeeded()`, etc.). Anything that mutates the page (`click`, `fill`, `goto`, `evaluate`, `keyboard`, `mouse`) is blocked.
|
|
69
|
+
- `state` — the current Libretto session state object.
|
|
70
|
+
- `get(url, options?)` — HTTP client restricted to **GET and HEAD** requests. Replaces `fetch`, which is blocked in readonly mode. Any request with a body or a non-GET/HEAD method throws `ReadonlyExecDenied`.
|
|
71
|
+
- `scrollBy(deltaX, deltaY)` — scroll the viewport by pixel offset. Use this to inspect content below the fold without targeting a specific element.
|
|
72
|
+
|
|
73
|
+
Standard JS globals `console`, `URL`, `Buffer`, `setTimeout`, and `setInterval` are also available.
|
|
74
|
+
|
|
75
|
+
#### Examples
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npx libretto readonly-exec "return page.url()" --session failed-job-debug
|
|
79
|
+
npx libretto readonly-exec "return await page.getByRole('heading').first().textContent()" --session failed-job-debug
|
|
80
|
+
|
|
81
|
+
# HTTP GET inspection
|
|
82
|
+
echo "const r = await get('https://api.example.com/status'); return await r.json()" \
|
|
83
|
+
| npx libretto readonly-exec - --session failed-job-debug
|
|
84
|
+
|
|
85
|
+
# Scroll down to inspect below-the-fold content
|
|
86
|
+
npx libretto readonly-exec "await scrollBy(0, 500)" --session failed-job-debug
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### `close`
|
|
90
|
+
|
|
91
|
+
- Use `close` when the inspection session is no longer needed.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npx libretto close --session failed-job-debug
|
|
95
|
+
```
|
package/src/cli/cli.ts
CHANGED
|
@@ -16,6 +16,10 @@ Examples:
|
|
|
16
16
|
|
|
17
17
|
libretto exec "await page.locator('button:has-text(\\"Sign in\\")').click()"
|
|
18
18
|
libretto exec "await page.fill('input[name=\\"email\\"]', 'test@example.com')"
|
|
19
|
+
libretto readonly-exec "return await page.title()" --session test1
|
|
20
|
+
libretto connect http://127.0.0.1:9222 --read-only --session test1
|
|
21
|
+
libretto run ./integration.ts --read-only --session test1
|
|
22
|
+
libretto status
|
|
19
23
|
libretto ai configure openai
|
|
20
24
|
libretto ai configure anthropic
|
|
21
25
|
libretto ai configure gemini
|
|
@@ -36,6 +40,9 @@ Examples:
|
|
|
36
40
|
Available in exec:
|
|
37
41
|
page, context, state, browser, networkLog, actionLog
|
|
38
42
|
|
|
43
|
+
Available in readonly-exec:
|
|
44
|
+
page, state, snapshot, scrollBy, get
|
|
45
|
+
|
|
39
46
|
Profiles:
|
|
40
47
|
Profiles are saved to .libretto/profiles/<domain>.json (git-ignored)
|
|
41
48
|
They persist cookies, localStorage, and session data across browser launches.
|
|
@@ -46,6 +53,9 @@ Sessions:
|
|
|
46
53
|
Session state is stored in .libretto/sessions/<session>/state.json
|
|
47
54
|
CLI logs are stored in .libretto/sessions/<session>/logs.jsonl
|
|
48
55
|
Each session runs an isolated browser instance on a dynamic port.
|
|
56
|
+
Session mode is stored per session as read-only or write-access.
|
|
57
|
+
Use --read-only on open, connect, or run to create a read-only session.
|
|
58
|
+
Session mode is enforced by Libretto commands, not by raw CDP clients outside Libretto.
|
|
49
59
|
`;
|
|
50
60
|
}
|
|
51
61
|
|