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
package/src/cli/commands/ai.ts
CHANGED
|
@@ -1,7 +1,117 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
CURRENT_CONFIG_VERSION,
|
|
4
|
+
readAiConfig,
|
|
5
|
+
writeAiConfig,
|
|
6
|
+
clearAiConfig,
|
|
7
|
+
type AiConfig,
|
|
8
|
+
} from "../core/config.js";
|
|
9
|
+
import { LIBRETTO_CONFIG_PATH } from "../core/context.js";
|
|
10
|
+
import { DEFAULT_SNAPSHOT_MODELS } from "../core/ai-model.js";
|
|
3
11
|
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
4
12
|
|
|
13
|
+
const PROVIDER_ALIASES: Record<string, string> = {
|
|
14
|
+
claude: DEFAULT_SNAPSHOT_MODELS.anthropic,
|
|
15
|
+
gemini: DEFAULT_SNAPSHOT_MODELS.google,
|
|
16
|
+
google: DEFAULT_SNAPSHOT_MODELS.google,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const CONFIGURE_PROVIDERS = [
|
|
20
|
+
"openai",
|
|
21
|
+
"anthropic",
|
|
22
|
+
"gemini",
|
|
23
|
+
"vertex",
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
function formatConfigureProviders(separator = " | "): string {
|
|
27
|
+
return CONFIGURE_PROVIDERS.join(separator);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function printAiConfig(config: AiConfig, configPath: string): void {
|
|
31
|
+
console.log(`Model: ${config.model}`);
|
|
32
|
+
console.log(`Config file: ${configPath}`);
|
|
33
|
+
console.log(`Updated at: ${config.updatedAt}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Resolve the model string from a `ai configure` argument.
|
|
38
|
+
* Accepts a provider shorthand ("openai", "anthropic", "gemini", "vertex")
|
|
39
|
+
* or a full provider/model-id string ("openai/gpt-4o", "anthropic/claude-sonnet-4-6").
|
|
40
|
+
*/
|
|
41
|
+
function resolveModelFromInput(input: string): string | null {
|
|
42
|
+
const trimmed = input.trim();
|
|
43
|
+
if (!trimmed) return null;
|
|
44
|
+
|
|
45
|
+
// Full model string (contains a slash)
|
|
46
|
+
if (trimmed.includes("/")) return trimmed;
|
|
47
|
+
|
|
48
|
+
// Provider shorthand
|
|
49
|
+
const normalized = trimmed.toLowerCase();
|
|
50
|
+
return (
|
|
51
|
+
(DEFAULT_SNAPSHOT_MODELS as Record<string, string>)[normalized] ??
|
|
52
|
+
PROVIDER_ALIASES[normalized] ??
|
|
53
|
+
null
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function runAiConfigure(
|
|
58
|
+
input: {
|
|
59
|
+
preset?: string;
|
|
60
|
+
clear?: boolean;
|
|
61
|
+
},
|
|
62
|
+
options: {
|
|
63
|
+
configureCommandName?: string;
|
|
64
|
+
configPath?: string;
|
|
65
|
+
} = {},
|
|
66
|
+
): void {
|
|
67
|
+
const configureCommandName =
|
|
68
|
+
options.configureCommandName ?? "npx libretto ai configure";
|
|
69
|
+
const configPath = options.configPath ?? LIBRETTO_CONFIG_PATH;
|
|
70
|
+
|
|
71
|
+
const presetArg = input.preset?.trim();
|
|
72
|
+
|
|
73
|
+
if (!presetArg && !input.clear) {
|
|
74
|
+
const config = readAiConfig(configPath);
|
|
75
|
+
if (!config) {
|
|
76
|
+
console.log(
|
|
77
|
+
`No AI config set. Choose a default model: ${configureCommandName} ${formatConfigureProviders()}`,
|
|
78
|
+
);
|
|
79
|
+
console.log(
|
|
80
|
+
"Provider credentials still come from your shell or .env file.",
|
|
81
|
+
);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
printAiConfig(config, configPath);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (input.clear) {
|
|
89
|
+
const removed = clearAiConfig(configPath);
|
|
90
|
+
if (removed) {
|
|
91
|
+
console.log(`Cleared AI config: ${configPath}`);
|
|
92
|
+
} else {
|
|
93
|
+
console.log("No AI config was set.");
|
|
94
|
+
}
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const model = resolveModelFromInput(presetArg!);
|
|
99
|
+
if (!model) {
|
|
100
|
+
console.log(
|
|
101
|
+
`Usage: ${configureCommandName} <${CONFIGURE_PROVIDERS.join("|")}|provider/model-id>\n` +
|
|
102
|
+
` ${configureCommandName}\n` +
|
|
103
|
+
` ${configureCommandName} --clear`,
|
|
104
|
+
);
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Invalid provider or model. Use one of: ${formatConfigureProviders()}, or a full model string like "openai/gpt-4o".`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const config = writeAiConfig(model, configPath);
|
|
111
|
+
console.log("AI config saved.");
|
|
112
|
+
printAiConfig(config, configPath);
|
|
113
|
+
}
|
|
114
|
+
|
|
5
115
|
export const aiConfigureInput = SimpleCLI.input({
|
|
6
116
|
positionals: [
|
|
7
117
|
SimpleCLI.positional("preset", z.string().optional(), {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { SessionAccessModeSchema } from "../../shared/state/index.js";
|
|
2
3
|
import {
|
|
3
4
|
runClose as runCloseWithLogger,
|
|
4
5
|
runCloseAll as runCloseAllWithLogger,
|
|
@@ -7,11 +8,15 @@ import {
|
|
|
7
8
|
runPages,
|
|
8
9
|
runSave,
|
|
9
10
|
} from "../core/browser.js";
|
|
11
|
+
import { readLibrettoConfig } from "../core/config.js";
|
|
10
12
|
import { createLoggerForSession, withSessionLogger } from "../core/context.js";
|
|
11
13
|
import {
|
|
14
|
+
type SessionAccessMode,
|
|
12
15
|
assertSessionAvailableForStart,
|
|
16
|
+
setSessionMode,
|
|
13
17
|
validateSessionName,
|
|
14
18
|
} from "../core/session.js";
|
|
19
|
+
import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
|
|
15
20
|
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
16
21
|
import {
|
|
17
22
|
sessionOption,
|
|
@@ -42,6 +47,13 @@ export function parseViewportArg(
|
|
|
42
47
|
return { width, height };
|
|
43
48
|
}
|
|
44
49
|
|
|
50
|
+
function resolveRequestedSessionMode(readOnly: boolean | undefined, writeAccess: boolean | undefined): SessionAccessMode {
|
|
51
|
+
if (readOnly) return "read-only";
|
|
52
|
+
if (writeAccess) return "write-access";
|
|
53
|
+
const config = readLibrettoConfig();
|
|
54
|
+
return config.sessionMode ?? "write-access";
|
|
55
|
+
}
|
|
56
|
+
|
|
45
57
|
export const openInput = SimpleCLI.input({
|
|
46
58
|
positionals: [
|
|
47
59
|
SimpleCLI.positional("url", z.string().optional(), {
|
|
@@ -52,6 +64,14 @@ export const openInput = SimpleCLI.input({
|
|
|
52
64
|
session: sessionOption(),
|
|
53
65
|
headed: SimpleCLI.flag({ help: "Run browser in headed mode" }),
|
|
54
66
|
headless: SimpleCLI.flag({ help: "Run browser in headless mode" }),
|
|
67
|
+
readOnly: SimpleCLI.flag({
|
|
68
|
+
name: "read-only",
|
|
69
|
+
help: "Create the session in read-only mode",
|
|
70
|
+
}),
|
|
71
|
+
writeAccess: SimpleCLI.flag({
|
|
72
|
+
name: "write-access",
|
|
73
|
+
help: "Create the session in write-access mode (overrides config default)",
|
|
74
|
+
}),
|
|
55
75
|
viewport: SimpleCLI.option(z.string().optional(), {
|
|
56
76
|
help: "Viewport size as WIDTHxHEIGHT (e.g. 1920x1080)",
|
|
57
77
|
}),
|
|
@@ -59,11 +79,15 @@ export const openInput = SimpleCLI.input({
|
|
|
59
79
|
})
|
|
60
80
|
.refine(
|
|
61
81
|
(input) => Boolean(input.url),
|
|
62
|
-
`Usage: libretto open <url> [--headless] [--viewport WxH] [--session <name>]`,
|
|
82
|
+
`Usage: libretto open <url> [--headless] [--read-only|--write-access] [--viewport WxH] [--session <name>]`,
|
|
63
83
|
)
|
|
64
84
|
.refine(
|
|
65
85
|
(input) => !(input.headed && input.headless),
|
|
66
86
|
"Cannot pass both --headed and --headless.",
|
|
87
|
+
)
|
|
88
|
+
.refine(
|
|
89
|
+
(input) => !(input.readOnly && input.writeAccess),
|
|
90
|
+
"Cannot pass both --read-only and --write-access.",
|
|
67
91
|
);
|
|
68
92
|
|
|
69
93
|
export const openCommand = SimpleCLI.command({
|
|
@@ -72,10 +96,14 @@ export const openCommand = SimpleCLI.command({
|
|
|
72
96
|
.input(openInput)
|
|
73
97
|
.use(withAutoSession())
|
|
74
98
|
.handle(async ({ input, ctx }) => {
|
|
99
|
+
warnIfInstalledSkillOutOfDate();
|
|
75
100
|
assertSessionAvailableForStart(ctx.session, ctx.logger);
|
|
76
101
|
const headed = input.headed || !input.headless;
|
|
77
102
|
const viewport = parseViewportArg(input.viewport);
|
|
78
|
-
await runOpen(input.url!, headed, ctx.session, ctx.logger, {
|
|
103
|
+
await runOpen(input.url!, headed, ctx.session, ctx.logger, {
|
|
104
|
+
viewport,
|
|
105
|
+
accessMode: resolveRequestedSessionMode(input.readOnly, input.writeAccess),
|
|
106
|
+
});
|
|
79
107
|
});
|
|
80
108
|
|
|
81
109
|
export const connectInput = SimpleCLI.input({
|
|
@@ -86,11 +114,24 @@ export const connectInput = SimpleCLI.input({
|
|
|
86
114
|
],
|
|
87
115
|
named: {
|
|
88
116
|
session: sessionOption(),
|
|
117
|
+
readOnly: SimpleCLI.flag({
|
|
118
|
+
name: "read-only",
|
|
119
|
+
help: "Create the session in read-only mode",
|
|
120
|
+
}),
|
|
121
|
+
writeAccess: SimpleCLI.flag({
|
|
122
|
+
name: "write-access",
|
|
123
|
+
help: "Create the session in write-access mode (overrides config default)",
|
|
124
|
+
}),
|
|
89
125
|
},
|
|
90
|
-
})
|
|
91
|
-
(
|
|
92
|
-
|
|
93
|
-
|
|
126
|
+
})
|
|
127
|
+
.refine(
|
|
128
|
+
(input) => Boolean(input.cdpUrl),
|
|
129
|
+
`Usage: libretto connect <cdp-url> [--read-only|--write-access] --session <name>`,
|
|
130
|
+
)
|
|
131
|
+
.refine(
|
|
132
|
+
(input) => !(input.readOnly && input.writeAccess),
|
|
133
|
+
"Cannot pass both --read-only and --write-access.",
|
|
134
|
+
);
|
|
94
135
|
|
|
95
136
|
export const connectCommand = SimpleCLI.command({
|
|
96
137
|
description: "Connect to an existing Chrome DevTools Protocol (CDP) endpoint",
|
|
@@ -98,7 +139,13 @@ export const connectCommand = SimpleCLI.command({
|
|
|
98
139
|
.input(connectInput)
|
|
99
140
|
.use(withAutoSession())
|
|
100
141
|
.handle(async ({ input, ctx }) => {
|
|
101
|
-
|
|
142
|
+
warnIfInstalledSkillOutOfDate();
|
|
143
|
+
await runConnectWithLogger(
|
|
144
|
+
input.cdpUrl!,
|
|
145
|
+
ctx.session,
|
|
146
|
+
ctx.logger,
|
|
147
|
+
resolveRequestedSessionMode(input.readOnly, input.writeAccess),
|
|
148
|
+
);
|
|
102
149
|
});
|
|
103
150
|
|
|
104
151
|
export const saveInput = SimpleCLI.input({
|
|
@@ -140,6 +187,32 @@ export const pagesCommand = SimpleCLI.command({
|
|
|
140
187
|
await runPages(ctx.session, ctx.logger);
|
|
141
188
|
});
|
|
142
189
|
|
|
190
|
+
export const sessionModeInput = SimpleCLI.input({
|
|
191
|
+
positionals: [
|
|
192
|
+
SimpleCLI.positional("mode", SessionAccessModeSchema.optional(), {
|
|
193
|
+
help: "Session mode to set",
|
|
194
|
+
}),
|
|
195
|
+
],
|
|
196
|
+
named: {
|
|
197
|
+
session: sessionOption(),
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
export const sessionModeCommand = SimpleCLI.command({
|
|
202
|
+
description: "View or set the session access mode",
|
|
203
|
+
})
|
|
204
|
+
.input(sessionModeInput)
|
|
205
|
+
.use(withRequiredSession())
|
|
206
|
+
.handle(async ({ input, ctx }) => {
|
|
207
|
+
if (!input.mode) {
|
|
208
|
+
console.log(`Session "${ctx.session}" mode: ${ctx.sessionState.mode}`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const nextState = setSessionMode(ctx.session, input.mode, ctx.logger);
|
|
213
|
+
console.log(`Session "${ctx.session}" mode set to ${nextState.mode}.`);
|
|
214
|
+
});
|
|
215
|
+
|
|
143
216
|
export const closeInput = SimpleCLI.input({
|
|
144
217
|
positionals: [],
|
|
145
218
|
named: {
|
|
@@ -179,6 +252,7 @@ export const browserCommands = {
|
|
|
179
252
|
connect: connectCommand,
|
|
180
253
|
save: saveCommand,
|
|
181
254
|
pages: pagesCommand,
|
|
255
|
+
"session-mode": sessionModeCommand,
|
|
182
256
|
close: closeCommand,
|
|
183
257
|
};
|
|
184
258
|
|
|
@@ -14,16 +14,20 @@ import { parseViewportArg } from "./browser.js";
|
|
|
14
14
|
import { getPauseSignalPaths } from "../core/pause-signals.js";
|
|
15
15
|
import {
|
|
16
16
|
assertSessionAvailableForStart,
|
|
17
|
+
assertSessionAllowsCommand,
|
|
17
18
|
clearSessionState,
|
|
18
19
|
readSessionState,
|
|
19
20
|
setSessionStatus,
|
|
20
21
|
type SessionState,
|
|
21
22
|
} from "../core/session.js";
|
|
23
|
+
import { warnIfInstalledSkillOutOfDate } from "../core/skill-version.js";
|
|
22
24
|
import {
|
|
23
25
|
readActionLog,
|
|
24
26
|
readNetworkLog,
|
|
25
27
|
wrapPageForActionLogging,
|
|
26
28
|
} from "../core/telemetry.js";
|
|
29
|
+
import { readLibrettoConfig } from "../core/config.js";
|
|
30
|
+
import { createReadonlyExecHelpers } from "../core/readonly-exec.js";
|
|
27
31
|
import type { RunIntegrationWorkerRequest } from "../workers/run-integration-worker-protocol.js";
|
|
28
32
|
import { SimpleCLI } from "../framework/simple-cli.js";
|
|
29
33
|
import {
|
|
@@ -37,6 +41,7 @@ type ExecFunction = (...args: unknown[]) => Promise<unknown>;
|
|
|
37
41
|
type RunIntegrationCommandRequest = RunIntegrationWorkerRequest & {
|
|
38
42
|
tsconfigPath?: string;
|
|
39
43
|
};
|
|
44
|
+
type ExecMode = "exec" | "readonly-exec";
|
|
40
45
|
|
|
41
46
|
type StripTypeScriptTypesFn = (
|
|
42
47
|
code: string,
|
|
@@ -202,14 +207,20 @@ async function runExec(
|
|
|
202
207
|
code: string,
|
|
203
208
|
session: string,
|
|
204
209
|
logger: LoggerApi,
|
|
205
|
-
|
|
206
|
-
|
|
210
|
+
options: {
|
|
211
|
+
visualize?: boolean;
|
|
212
|
+
pageId?: string;
|
|
213
|
+
mode?: ExecMode;
|
|
214
|
+
} = {},
|
|
207
215
|
): Promise<void> {
|
|
216
|
+
const visualize = options.visualize ?? false;
|
|
217
|
+
const pageId = options.pageId;
|
|
218
|
+
const mode = options.mode ?? "exec";
|
|
208
219
|
const { cleaned: cleanedCode, strippedCount } = stripEmptyCatchHandlers(code);
|
|
209
220
|
if (strippedCount > 0) {
|
|
210
221
|
console.log("(Stripped `.catch(() => {})` — letting errors bubble up)");
|
|
211
222
|
}
|
|
212
|
-
logger.info(
|
|
223
|
+
logger.info(`${mode}-start`, {
|
|
213
224
|
session,
|
|
214
225
|
codeLength: cleanedCode.length,
|
|
215
226
|
codePreview: cleanedCode.slice(0, 200),
|
|
@@ -235,20 +246,20 @@ async function runExec(
|
|
|
235
246
|
const stallInterval = setInterval(() => {
|
|
236
247
|
const silenceMs = Date.now() - lastActivityTs;
|
|
237
248
|
if (silenceMs >= STALL_THRESHOLD_MS) {
|
|
238
|
-
logger.warn(
|
|
249
|
+
logger.warn(`${mode}-stall-warning`, {
|
|
239
250
|
session,
|
|
240
251
|
silenceMs,
|
|
241
252
|
codePreview: cleanedCode.slice(0, 200),
|
|
242
253
|
});
|
|
243
254
|
console.warn(
|
|
244
|
-
`[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1000)}s —
|
|
255
|
+
`[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1000)}s — ${mode} may be hung (code: ${cleanedCode.slice(0, 100)}...)`,
|
|
245
256
|
);
|
|
246
257
|
}
|
|
247
258
|
}, STALL_THRESHOLD_MS);
|
|
248
259
|
|
|
249
260
|
const execStartTs = Date.now();
|
|
250
261
|
const sigintHandler = () => {
|
|
251
|
-
logger.info(
|
|
262
|
+
logger.info(`${mode}-interrupted`, {
|
|
252
263
|
session,
|
|
253
264
|
duration: Date.now() - execStartTs,
|
|
254
265
|
codePreview: cleanedCode.slice(0, 200),
|
|
@@ -256,60 +267,67 @@ async function runExec(
|
|
|
256
267
|
};
|
|
257
268
|
process.on("SIGINT", sigintHandler);
|
|
258
269
|
|
|
259
|
-
|
|
270
|
+
if (mode === "exec") {
|
|
271
|
+
wrapPageForActionLogging(page, session, resolvedPageId, onActivity);
|
|
272
|
+
}
|
|
260
273
|
|
|
261
|
-
if (visualize) {
|
|
274
|
+
if (visualize && mode === "exec") {
|
|
262
275
|
await installInstrumentation(page, { visualize: true, logger });
|
|
263
276
|
}
|
|
264
277
|
|
|
265
278
|
try {
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
279
|
+
const helpers =
|
|
280
|
+
mode === "readonly-exec"
|
|
281
|
+
? createReadonlyExecHelpers(page, { onActivity })
|
|
282
|
+
: (() => {
|
|
283
|
+
const execState: Record<string, unknown> = {};
|
|
284
|
+
|
|
285
|
+
const networkLog = (
|
|
286
|
+
opts: {
|
|
287
|
+
last?: number;
|
|
288
|
+
filter?: string;
|
|
289
|
+
method?: string;
|
|
290
|
+
pageId?: string;
|
|
291
|
+
} = {},
|
|
292
|
+
) => {
|
|
293
|
+
return readNetworkLog(session, opts);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const actionLog = (
|
|
297
|
+
opts: {
|
|
298
|
+
last?: number;
|
|
299
|
+
filter?: string;
|
|
300
|
+
action?: string;
|
|
301
|
+
source?: string;
|
|
302
|
+
pageId?: string;
|
|
303
|
+
} = {},
|
|
304
|
+
) => {
|
|
305
|
+
return readActionLog(session, opts);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
return {
|
|
309
|
+
page,
|
|
310
|
+
context,
|
|
311
|
+
state: execState,
|
|
312
|
+
browser,
|
|
313
|
+
networkLog,
|
|
314
|
+
actionLog,
|
|
315
|
+
console,
|
|
316
|
+
setTimeout,
|
|
317
|
+
setInterval,
|
|
318
|
+
clearTimeout,
|
|
319
|
+
clearInterval,
|
|
320
|
+
fetch,
|
|
321
|
+
URL,
|
|
322
|
+
Buffer,
|
|
323
|
+
};
|
|
324
|
+
})();
|
|
307
325
|
|
|
308
326
|
const helperNames = Object.keys(helpers);
|
|
309
327
|
const fn = compileExecFunction(cleanedCode, helperNames);
|
|
310
328
|
|
|
311
329
|
const result = await fn(...Object.values(helpers));
|
|
312
|
-
logger.info(
|
|
330
|
+
logger.info(`${mode}-success`, { session, hasResult: result !== undefined });
|
|
313
331
|
if (result !== undefined) {
|
|
314
332
|
console.log(
|
|
315
333
|
typeof result === "string" ? result : JSON.stringify(result, null, 2),
|
|
@@ -318,7 +336,7 @@ async function runExec(
|
|
|
318
336
|
console.log("Executed successfully");
|
|
319
337
|
}
|
|
320
338
|
} catch (err) {
|
|
321
|
-
logger.error(
|
|
339
|
+
logger.error(`${mode}-error`, {
|
|
322
340
|
error: err,
|
|
323
341
|
session,
|
|
324
342
|
codePreview: cleanedCode.slice(0, 200),
|
|
@@ -605,13 +623,13 @@ async function runIntegrationFromFile(
|
|
|
605
623
|
);
|
|
606
624
|
const payload = JSON.stringify({
|
|
607
625
|
integrationPath: args.integrationPath,
|
|
608
|
-
workflowName: args.workflowName,
|
|
609
626
|
session: args.session,
|
|
610
627
|
params: args.params,
|
|
611
628
|
headless: args.headless,
|
|
612
629
|
visualize: args.visualize,
|
|
613
630
|
authProfileDomain: args.authProfileDomain,
|
|
614
631
|
viewport: args.viewport,
|
|
632
|
+
accessMode: args.accessMode,
|
|
615
633
|
} satisfies RunIntegrationWorkerRequest);
|
|
616
634
|
const worker = spawn(
|
|
617
635
|
process.execPath,
|
|
@@ -690,6 +708,7 @@ export const execCommand = SimpleCLI.command({
|
|
|
690
708
|
.input(execInput)
|
|
691
709
|
.use(withRequiredSession())
|
|
692
710
|
.handle(async ({ input, ctx }) => {
|
|
711
|
+
assertSessionAllowsCommand(ctx.sessionState, "exec", ["write-access"]);
|
|
693
712
|
const code = input.code!;
|
|
694
713
|
const codeFromArgsOrStdin = code === "-" ? readStdinSync() : code;
|
|
695
714
|
if (codeFromArgsOrStdin === null) {
|
|
@@ -701,21 +720,55 @@ export const execCommand = SimpleCLI.command({
|
|
|
701
720
|
codeFromArgsOrStdin,
|
|
702
721
|
ctx.session,
|
|
703
722
|
ctx.logger,
|
|
704
|
-
|
|
705
|
-
|
|
723
|
+
{
|
|
724
|
+
visualize: input.visualize,
|
|
725
|
+
pageId: input.page,
|
|
726
|
+
mode: "exec",
|
|
727
|
+
},
|
|
706
728
|
);
|
|
707
729
|
});
|
|
708
730
|
|
|
709
|
-
const
|
|
731
|
+
export const readonlyExecInput = SimpleCLI.input({
|
|
732
|
+
positionals: [
|
|
733
|
+
SimpleCLI.positional("code", z.string().optional(), {
|
|
734
|
+
help: "Read-only Playwright TypeScript code to execute",
|
|
735
|
+
}),
|
|
736
|
+
],
|
|
737
|
+
named: {
|
|
738
|
+
session: sessionOption(),
|
|
739
|
+
page: pageOption(),
|
|
740
|
+
},
|
|
741
|
+
}).refine(
|
|
742
|
+
(input) => input.code !== undefined,
|
|
743
|
+
`Usage: libretto readonly-exec <code|-> [--session <name>] [--page <id>]\n echo '<code>' | libretto readonly-exec - [--session <name>] [--page <id>]`,
|
|
744
|
+
);
|
|
745
|
+
|
|
746
|
+
export const readonlyExecCommand = SimpleCLI.command({
|
|
747
|
+
description: "Execute read-only Playwright inspection code",
|
|
748
|
+
})
|
|
749
|
+
.input(readonlyExecInput)
|
|
750
|
+
.use(withRequiredSession())
|
|
751
|
+
.handle(async ({ input, ctx }) => {
|
|
752
|
+
const code = input.code!;
|
|
753
|
+
const codeFromArgsOrStdin = code === "-" ? readStdinSync() : code;
|
|
754
|
+
if (codeFromArgsOrStdin === null) {
|
|
755
|
+
throw new Error(
|
|
756
|
+
"Missing stdin input for `readonly-exec -`. Pipe inspection code into stdin.",
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
await runExec(codeFromArgsOrStdin, ctx.session, ctx.logger, {
|
|
760
|
+
pageId: input.page,
|
|
761
|
+
mode: "readonly-exec",
|
|
762
|
+
});
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
const runUsage = `Usage: libretto run <integrationFile> [--params <json> | --params-file <path>] [--tsconfig <path>] [--headed|--headless] [--read-only|--write-access] [--no-visualize] [--viewport WxH]`;
|
|
710
766
|
|
|
711
767
|
export const runInput = SimpleCLI.input({
|
|
712
768
|
positionals: [
|
|
713
769
|
SimpleCLI.positional("integrationFile", z.string().optional(), {
|
|
714
770
|
help: "Path to the integration file",
|
|
715
771
|
}),
|
|
716
|
-
SimpleCLI.positional("workflowName", z.string().optional(), {
|
|
717
|
-
help: "Workflow name to run (from workflow(name, handler))",
|
|
718
|
-
}),
|
|
719
772
|
],
|
|
720
773
|
named: {
|
|
721
774
|
session: sessionOption(),
|
|
@@ -731,6 +784,14 @@ export const runInput = SimpleCLI.input({
|
|
|
731
784
|
}),
|
|
732
785
|
headed: SimpleCLI.flag({ help: "Run in headed mode" }),
|
|
733
786
|
headless: SimpleCLI.flag({ help: "Run in headless mode" }),
|
|
787
|
+
readOnly: SimpleCLI.flag({
|
|
788
|
+
name: "read-only",
|
|
789
|
+
help: "Create the session in read-only mode",
|
|
790
|
+
}),
|
|
791
|
+
writeAccess: SimpleCLI.flag({
|
|
792
|
+
name: "write-access",
|
|
793
|
+
help: "Create the session in write-access mode (overrides config default)",
|
|
794
|
+
}),
|
|
734
795
|
noVisualize: SimpleCLI.flag({
|
|
735
796
|
name: "no-visualize",
|
|
736
797
|
help: "Disable ghost cursor + highlight visualization in headed mode",
|
|
@@ -745,7 +806,7 @@ export const runInput = SimpleCLI.input({
|
|
|
745
806
|
},
|
|
746
807
|
})
|
|
747
808
|
.refine(
|
|
748
|
-
(input) => Boolean(input.integrationFile
|
|
809
|
+
(input) => Boolean(input.integrationFile),
|
|
749
810
|
runUsage,
|
|
750
811
|
)
|
|
751
812
|
.refine(
|
|
@@ -755,6 +816,10 @@ export const runInput = SimpleCLI.input({
|
|
|
755
816
|
.refine(
|
|
756
817
|
(input) => !(input.headed && input.headless),
|
|
757
818
|
"Cannot pass both --headed and --headless.",
|
|
819
|
+
)
|
|
820
|
+
.refine(
|
|
821
|
+
(input) => !(input.readOnly && input.writeAccess),
|
|
822
|
+
"Cannot pass both --read-only and --write-access.",
|
|
758
823
|
);
|
|
759
824
|
|
|
760
825
|
function resolveRunParams(
|
|
@@ -779,11 +844,12 @@ function resolveRunParams(
|
|
|
779
844
|
}
|
|
780
845
|
|
|
781
846
|
export const runCommand = SimpleCLI.command({
|
|
782
|
-
description: "Run
|
|
847
|
+
description: "Run the default-exported Libretto workflow from a file",
|
|
783
848
|
})
|
|
784
849
|
.input(runInput)
|
|
785
850
|
.use(withAutoSession())
|
|
786
851
|
.handle(async ({ input, ctx }) => {
|
|
852
|
+
warnIfInstalledSkillOutOfDate();
|
|
787
853
|
await stopExistingFailedRunSession(ctx.session, ctx.logger);
|
|
788
854
|
assertSessionAvailableForStart(ctx.session, ctx.logger);
|
|
789
855
|
|
|
@@ -802,7 +868,6 @@ export const runCommand = SimpleCLI.command({
|
|
|
802
868
|
await runIntegrationFromFile(
|
|
803
869
|
{
|
|
804
870
|
integrationPath: input.integrationFile!,
|
|
805
|
-
workflowName: input.workflowName!,
|
|
806
871
|
session: ctx.session,
|
|
807
872
|
params,
|
|
808
873
|
tsconfigPath: input.tsconfig,
|
|
@@ -810,6 +875,7 @@ export const runCommand = SimpleCLI.command({
|
|
|
810
875
|
visualize,
|
|
811
876
|
authProfileDomain: input.authProfile,
|
|
812
877
|
viewport,
|
|
878
|
+
accessMode: input.readOnly ? "read-only" : input.writeAccess ? "write-access" : (readLibrettoConfig().sessionMode ?? "write-access"),
|
|
813
879
|
},
|
|
814
880
|
ctx.logger,
|
|
815
881
|
);
|
|
@@ -833,6 +899,7 @@ export const resumeCommand = SimpleCLI.command({
|
|
|
833
899
|
|
|
834
900
|
export const executionCommands = {
|
|
835
901
|
exec: execCommand,
|
|
902
|
+
"readonly-exec": readonlyExecCommand,
|
|
836
903
|
run: runCommand,
|
|
837
904
|
resume: resumeCommand,
|
|
838
905
|
};
|