libretto 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/package.json +12 -12
- package/skill/SKILL.md +20 -18
- package/skill/code-generation-rules.md +3 -3
- package/skill/integration-approach-selection.md +3 -3
- package/dist/cli/cli.js +0 -209
- package/dist/cli/commands/ai.js +0 -21
- package/dist/cli/commands/browser.js +0 -82
- package/dist/cli/commands/execution.js +0 -461
- package/dist/cli/commands/init.js +0 -95
- package/dist/cli/commands/logs.js +0 -93
- package/dist/cli/commands/snapshot.js +0 -106
- package/dist/cli/core/ai-config.js +0 -149
- package/dist/cli/core/browser.js +0 -648
- package/dist/cli/core/context.js +0 -118
- package/dist/cli/core/pause-signals.js +0 -29
- package/dist/cli/core/session-telemetry.js +0 -491
- package/dist/cli/core/session.js +0 -183
- package/dist/cli/core/snapshot-analyzer.js +0 -492
- package/dist/cli/core/telemetry.js +0 -362
- package/dist/cli/index.js +0 -13
- package/dist/cli/workers/run-integration-runtime.js +0 -227
- package/dist/cli/workers/run-integration-worker-protocol.js +0 -12
- package/dist/cli/workers/run-integration-worker.js +0 -66
- package/dist/index.cjs +0 -116
- package/dist/index.d.cts +0 -21
- package/dist/index.d.ts +0 -21
- package/dist/index.js +0 -97
- package/dist/runtime/download/download.cjs +0 -70
- package/dist/runtime/download/download.d.cts +0 -35
- package/dist/runtime/download/download.d.ts +0 -35
- package/dist/runtime/download/download.js +0 -45
- package/dist/runtime/download/index.cjs +0 -30
- package/dist/runtime/download/index.d.cts +0 -3
- package/dist/runtime/download/index.d.ts +0 -3
- package/dist/runtime/download/index.js +0 -8
- package/dist/runtime/extract/extract.cjs +0 -88
- package/dist/runtime/extract/extract.d.cts +0 -23
- package/dist/runtime/extract/extract.d.ts +0 -23
- package/dist/runtime/extract/extract.js +0 -64
- package/dist/runtime/extract/index.cjs +0 -28
- package/dist/runtime/extract/index.d.cts +0 -5
- package/dist/runtime/extract/index.d.ts +0 -5
- package/dist/runtime/extract/index.js +0 -4
- package/dist/runtime/network/index.cjs +0 -28
- package/dist/runtime/network/index.d.cts +0 -4
- package/dist/runtime/network/index.d.ts +0 -4
- package/dist/runtime/network/index.js +0 -6
- package/dist/runtime/network/network.cjs +0 -91
- package/dist/runtime/network/network.d.cts +0 -28
- package/dist/runtime/network/network.d.ts +0 -28
- package/dist/runtime/network/network.js +0 -67
- package/dist/runtime/recovery/agent.cjs +0 -223
- package/dist/runtime/recovery/agent.d.cts +0 -13
- package/dist/runtime/recovery/agent.d.ts +0 -13
- package/dist/runtime/recovery/agent.js +0 -199
- package/dist/runtime/recovery/errors.cjs +0 -124
- package/dist/runtime/recovery/errors.d.cts +0 -31
- package/dist/runtime/recovery/errors.d.ts +0 -31
- package/dist/runtime/recovery/errors.js +0 -100
- package/dist/runtime/recovery/index.cjs +0 -34
- package/dist/runtime/recovery/index.d.cts +0 -7
- package/dist/runtime/recovery/index.d.ts +0 -7
- package/dist/runtime/recovery/index.js +0 -10
- package/dist/runtime/recovery/recovery.cjs +0 -55
- package/dist/runtime/recovery/recovery.d.cts +0 -12
- package/dist/runtime/recovery/recovery.d.ts +0 -12
- package/dist/runtime/recovery/recovery.js +0 -31
- package/dist/shared/config/config.cjs +0 -44
- package/dist/shared/config/config.d.cts +0 -10
- package/dist/shared/config/config.d.ts +0 -10
- package/dist/shared/config/config.js +0 -18
- package/dist/shared/config/index.cjs +0 -32
- package/dist/shared/config/index.d.cts +0 -1
- package/dist/shared/config/index.d.ts +0 -1
- package/dist/shared/config/index.js +0 -10
- package/dist/shared/debug/index.cjs +0 -30
- package/dist/shared/debug/index.d.cts +0 -1
- package/dist/shared/debug/index.d.ts +0 -1
- package/dist/shared/debug/index.js +0 -5
- package/dist/shared/debug/pause.cjs +0 -90
- package/dist/shared/debug/pause.d.cts +0 -16
- package/dist/shared/debug/pause.d.ts +0 -16
- package/dist/shared/debug/pause.js +0 -55
- package/dist/shared/instrumentation/errors.cjs +0 -81
- package/dist/shared/instrumentation/errors.d.cts +0 -12
- package/dist/shared/instrumentation/errors.d.ts +0 -12
- package/dist/shared/instrumentation/errors.js +0 -57
- package/dist/shared/instrumentation/index.cjs +0 -35
- package/dist/shared/instrumentation/index.d.cts +0 -6
- package/dist/shared/instrumentation/index.d.ts +0 -6
- package/dist/shared/instrumentation/index.js +0 -12
- package/dist/shared/instrumentation/instrument.cjs +0 -206
- package/dist/shared/instrumentation/instrument.d.cts +0 -32
- package/dist/shared/instrumentation/instrument.d.ts +0 -32
- package/dist/shared/instrumentation/instrument.js +0 -190
- package/dist/shared/llm/ai-sdk-adapter.cjs +0 -67
- package/dist/shared/llm/ai-sdk-adapter.d.cts +0 -22
- package/dist/shared/llm/ai-sdk-adapter.d.ts +0 -22
- package/dist/shared/llm/ai-sdk-adapter.js +0 -43
- package/dist/shared/llm/client.cjs +0 -139
- package/dist/shared/llm/client.d.cts +0 -6
- package/dist/shared/llm/client.d.ts +0 -6
- package/dist/shared/llm/client.js +0 -115
- package/dist/shared/llm/index.cjs +0 -31
- package/dist/shared/llm/index.d.cts +0 -5
- package/dist/shared/llm/index.d.ts +0 -5
- package/dist/shared/llm/index.js +0 -6
- package/dist/shared/llm/types.cjs +0 -16
- package/dist/shared/llm/types.d.cts +0 -66
- package/dist/shared/llm/types.d.ts +0 -66
- package/dist/shared/llm/types.js +0 -0
- package/dist/shared/logger/index.cjs +0 -37
- package/dist/shared/logger/index.d.cts +0 -2
- package/dist/shared/logger/index.d.ts +0 -2
- package/dist/shared/logger/index.js +0 -13
- package/dist/shared/logger/logger.cjs +0 -213
- package/dist/shared/logger/logger.d.cts +0 -82
- package/dist/shared/logger/logger.d.ts +0 -82
- package/dist/shared/logger/logger.js +0 -188
- package/dist/shared/logger/sinks.cjs +0 -160
- package/dist/shared/logger/sinks.d.cts +0 -9
- package/dist/shared/logger/sinks.d.ts +0 -9
- package/dist/shared/logger/sinks.js +0 -124
- package/dist/shared/paths/paths.cjs +0 -104
- package/dist/shared/paths/paths.d.cts +0 -10
- package/dist/shared/paths/paths.d.ts +0 -10
- package/dist/shared/paths/paths.js +0 -73
- package/dist/shared/run/api.cjs +0 -28
- package/dist/shared/run/api.d.cts +0 -2
- package/dist/shared/run/api.d.ts +0 -2
- package/dist/shared/run/api.js +0 -4
- package/dist/shared/run/browser.cjs +0 -98
- package/dist/shared/run/browser.d.cts +0 -22
- package/dist/shared/run/browser.d.ts +0 -22
- package/dist/shared/run/browser.js +0 -74
- package/dist/shared/state/index.cjs +0 -38
- package/dist/shared/state/index.d.cts +0 -2
- package/dist/shared/state/index.d.ts +0 -2
- package/dist/shared/state/index.js +0 -16
- package/dist/shared/state/session-state.cjs +0 -85
- package/dist/shared/state/session-state.d.cts +0 -34
- package/dist/shared/state/session-state.d.ts +0 -34
- package/dist/shared/state/session-state.js +0 -56
- package/dist/shared/visualization/ghost-cursor.cjs +0 -174
- package/dist/shared/visualization/ghost-cursor.d.cts +0 -37
- package/dist/shared/visualization/ghost-cursor.d.ts +0 -37
- package/dist/shared/visualization/ghost-cursor.js +0 -145
- package/dist/shared/visualization/highlight.cjs +0 -134
- package/dist/shared/visualization/highlight.d.cts +0 -22
- package/dist/shared/visualization/highlight.d.ts +0 -22
- package/dist/shared/visualization/highlight.js +0 -108
- package/dist/shared/visualization/index.cjs +0 -45
- package/dist/shared/visualization/index.d.cts +0 -3
- package/dist/shared/visualization/index.d.ts +0 -3
- package/dist/shared/visualization/index.js +0 -24
- package/dist/shared/workflow/workflow.cjs +0 -47
- package/dist/shared/workflow/workflow.d.cts +0 -21
- package/dist/shared/workflow/workflow.d.ts +0 -21
- package/dist/shared/workflow/workflow.js +0 -21
|
@@ -1,461 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { spawn } from "node:child_process";
|
|
3
|
-
import * as moduleBuiltin from "node:module";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { installInstrumentation } from "../../shared/instrumentation/index.js";
|
|
6
|
-
import {
|
|
7
|
-
connect,
|
|
8
|
-
disconnectBrowser
|
|
9
|
-
} from "../core/browser.js";
|
|
10
|
-
import { getPauseSignalPaths } from "../core/pause-signals.js";
|
|
11
|
-
import {
|
|
12
|
-
assertSessionAvailableForStart,
|
|
13
|
-
clearSessionState,
|
|
14
|
-
readSessionState,
|
|
15
|
-
readSessionStateOrThrow,
|
|
16
|
-
setSessionStatus
|
|
17
|
-
} from "../core/session.js";
|
|
18
|
-
import {
|
|
19
|
-
readActionLog,
|
|
20
|
-
readNetworkLog,
|
|
21
|
-
wrapPageForActionLogging
|
|
22
|
-
} from "../core/telemetry.js";
|
|
23
|
-
const stripTypeScriptTypes = moduleBuiltin.stripTypeScriptTypes;
|
|
24
|
-
function withSuppressedStripTypeScriptWarning(action) {
|
|
25
|
-
const mutableProcess = process;
|
|
26
|
-
const originalEmitWarning = mutableProcess.emitWarning;
|
|
27
|
-
mutableProcess.emitWarning = (...args) => {
|
|
28
|
-
const warning = args[0];
|
|
29
|
-
const typeOrOptions = args[1];
|
|
30
|
-
const warningMessage = typeof warning === "string" ? warning : warning instanceof Error ? warning.message : "";
|
|
31
|
-
const warningType = typeof typeOrOptions === "string" ? typeOrOptions : typeof typeOrOptions === "object" && typeOrOptions !== null && "type" in typeOrOptions && typeof typeOrOptions.type === "string" ? typeOrOptions.type ?? "" : "";
|
|
32
|
-
if (warningType === "ExperimentalWarning" && warningMessage.includes("stripTypeScriptTypes")) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
originalEmitWarning(...args);
|
|
36
|
-
};
|
|
37
|
-
try {
|
|
38
|
-
return action();
|
|
39
|
-
} finally {
|
|
40
|
-
mutableProcess.emitWarning = originalEmitWarning;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function compileTypeScriptExecFunction(code, helperNames) {
|
|
44
|
-
if (!stripTypeScriptTypes) return null;
|
|
45
|
-
const wrappedSource = `(async function __librettoExec(${helperNames.join(", ")}) {
|
|
46
|
-
${code}
|
|
47
|
-
})`;
|
|
48
|
-
const jsSource = withSuppressedStripTypeScriptWarning(
|
|
49
|
-
() => stripTypeScriptTypes(wrappedSource, { mode: "strip" })
|
|
50
|
-
);
|
|
51
|
-
const createFunction = new Function(
|
|
52
|
-
`return ${jsSource}`
|
|
53
|
-
);
|
|
54
|
-
return createFunction();
|
|
55
|
-
}
|
|
56
|
-
function compileExecFunction(code, helperNames) {
|
|
57
|
-
const typeStripped = compileTypeScriptExecFunction(code, helperNames);
|
|
58
|
-
if (typeStripped) return typeStripped;
|
|
59
|
-
const AsyncFunction = Object.getPrototypeOf(async function() {
|
|
60
|
-
}).constructor;
|
|
61
|
-
return new AsyncFunction(...helperNames, code);
|
|
62
|
-
}
|
|
63
|
-
async function runExec(code, session, logger, visualize = false, pageId) {
|
|
64
|
-
readSessionStateOrThrow(session);
|
|
65
|
-
logger.info("exec-start", {
|
|
66
|
-
session,
|
|
67
|
-
codeLength: code.length,
|
|
68
|
-
codePreview: code.slice(0, 200),
|
|
69
|
-
visualize,
|
|
70
|
-
pageId
|
|
71
|
-
});
|
|
72
|
-
const { browser, context, page, pageId: resolvedPageId } = await connect(
|
|
73
|
-
session,
|
|
74
|
-
logger,
|
|
75
|
-
1e4,
|
|
76
|
-
{
|
|
77
|
-
pageId,
|
|
78
|
-
requireSinglePage: true
|
|
79
|
-
}
|
|
80
|
-
);
|
|
81
|
-
const STALL_THRESHOLD_MS = 6e4;
|
|
82
|
-
let lastActivityTs = Date.now();
|
|
83
|
-
const onActivity = () => {
|
|
84
|
-
lastActivityTs = Date.now();
|
|
85
|
-
};
|
|
86
|
-
const stallInterval = setInterval(() => {
|
|
87
|
-
const silenceMs = Date.now() - lastActivityTs;
|
|
88
|
-
if (silenceMs >= STALL_THRESHOLD_MS) {
|
|
89
|
-
logger.warn("exec-stall-warning", {
|
|
90
|
-
session,
|
|
91
|
-
silenceMs,
|
|
92
|
-
codePreview: code.slice(0, 200)
|
|
93
|
-
});
|
|
94
|
-
console.warn(
|
|
95
|
-
`[stall-warning] No Playwright activity for ${Math.round(silenceMs / 1e3)}s \u2014 exec may be hung (code: ${code.slice(0, 100)}...)`
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}, STALL_THRESHOLD_MS);
|
|
99
|
-
const execStartTs = Date.now();
|
|
100
|
-
const sigintHandler = () => {
|
|
101
|
-
logger.info("exec-interrupted", {
|
|
102
|
-
session,
|
|
103
|
-
duration: Date.now() - execStartTs,
|
|
104
|
-
codePreview: code.slice(0, 200)
|
|
105
|
-
});
|
|
106
|
-
};
|
|
107
|
-
process.on("SIGINT", sigintHandler);
|
|
108
|
-
wrapPageForActionLogging(page, session, resolvedPageId, onActivity);
|
|
109
|
-
if (visualize) {
|
|
110
|
-
await installInstrumentation(page, { visualize: true, logger });
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
const execState = {};
|
|
114
|
-
const networkLog = (opts = {}) => {
|
|
115
|
-
return readNetworkLog(session, opts);
|
|
116
|
-
};
|
|
117
|
-
const actionLog = (opts = {}) => {
|
|
118
|
-
return readActionLog(session, opts);
|
|
119
|
-
};
|
|
120
|
-
const helpers = {
|
|
121
|
-
page,
|
|
122
|
-
context,
|
|
123
|
-
state: execState,
|
|
124
|
-
browser,
|
|
125
|
-
networkLog,
|
|
126
|
-
actionLog,
|
|
127
|
-
console,
|
|
128
|
-
setTimeout,
|
|
129
|
-
setInterval,
|
|
130
|
-
clearTimeout,
|
|
131
|
-
clearInterval,
|
|
132
|
-
fetch,
|
|
133
|
-
URL,
|
|
134
|
-
Buffer
|
|
135
|
-
};
|
|
136
|
-
const helperNames = Object.keys(helpers);
|
|
137
|
-
const fn = compileExecFunction(code, helperNames);
|
|
138
|
-
const result = await fn(...Object.values(helpers));
|
|
139
|
-
logger.info("exec-success", { session, hasResult: result !== void 0 });
|
|
140
|
-
if (result !== void 0) {
|
|
141
|
-
console.log(
|
|
142
|
-
typeof result === "string" ? result : JSON.stringify(result, null, 2)
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
} catch (err) {
|
|
146
|
-
logger.error("exec-error", {
|
|
147
|
-
error: err,
|
|
148
|
-
session,
|
|
149
|
-
codePreview: code.slice(0, 200)
|
|
150
|
-
});
|
|
151
|
-
throw err;
|
|
152
|
-
} finally {
|
|
153
|
-
clearInterval(stallInterval);
|
|
154
|
-
process.removeListener("SIGINT", sigintHandler);
|
|
155
|
-
disconnectBrowser(browser, logger, session);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
function parseJsonArg(label, raw) {
|
|
159
|
-
try {
|
|
160
|
-
return JSON.parse(raw);
|
|
161
|
-
} catch (error) {
|
|
162
|
-
throw new Error(
|
|
163
|
-
`Invalid JSON in ${label}: ${error instanceof Error ? error.message : String(error)}`
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
function isProcessRunning(pid) {
|
|
168
|
-
try {
|
|
169
|
-
process.kill(pid, 0);
|
|
170
|
-
return true;
|
|
171
|
-
} catch {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
async function stopExistingFailedRunSession(session, logger) {
|
|
176
|
-
const existingState = readSessionState(session, logger);
|
|
177
|
-
if (!existingState || existingState.status !== "failed") {
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
logger.info("run-release-existing-failed-session", {
|
|
181
|
-
session,
|
|
182
|
-
pid: existingState.pid,
|
|
183
|
-
port: existingState.port
|
|
184
|
-
});
|
|
185
|
-
clearSessionState(session, logger);
|
|
186
|
-
const stopDeadline = Date.now() + 3e3;
|
|
187
|
-
while (isProcessRunning(existingState.pid) && Date.now() < stopDeadline) {
|
|
188
|
-
await new Promise((resolveWait) => setTimeout(resolveWait, 100));
|
|
189
|
-
}
|
|
190
|
-
if (isProcessRunning(existingState.pid)) {
|
|
191
|
-
logger.warn("run-release-existing-failed-session-timeout", {
|
|
192
|
-
session,
|
|
193
|
-
pid: existingState.pid
|
|
194
|
-
});
|
|
195
|
-
console.warn(
|
|
196
|
-
`Existing failed workflow process for session "${session}" (pid ${existingState.pid}) is still shutting down; continuing.`
|
|
197
|
-
);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
console.log(
|
|
201
|
-
`Closed existing failed workflow process for session "${session}" (pid ${existingState.pid}).`
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
function readJsonFileIfExists(path) {
|
|
205
|
-
if (!existsSync(path)) return null;
|
|
206
|
-
try {
|
|
207
|
-
return JSON.parse(readFileSync(path, "utf8"));
|
|
208
|
-
} catch {
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
function readFailureMessage(path) {
|
|
213
|
-
const raw = readJsonFileIfExists(path);
|
|
214
|
-
if (!raw || typeof raw !== "object") return null;
|
|
215
|
-
const message = raw.message;
|
|
216
|
-
return typeof message === "string" ? message : null;
|
|
217
|
-
}
|
|
218
|
-
async function waitForFailureMessage(path, timeoutMs = 1e3) {
|
|
219
|
-
const deadline = Date.now() + timeoutMs;
|
|
220
|
-
while (Date.now() < deadline) {
|
|
221
|
-
const message = readFailureMessage(path);
|
|
222
|
-
if (message) return message;
|
|
223
|
-
await new Promise((resolveWait) => setTimeout(resolveWait, 25));
|
|
224
|
-
}
|
|
225
|
-
return readFailureMessage(path);
|
|
226
|
-
}
|
|
227
|
-
function streamOutputSince(path, offset) {
|
|
228
|
-
if (!existsSync(path)) return offset;
|
|
229
|
-
const output = readFileSync(path);
|
|
230
|
-
if (output.length <= offset) return output.length;
|
|
231
|
-
process.stdout.write(output.subarray(offset));
|
|
232
|
-
return output.length;
|
|
233
|
-
}
|
|
234
|
-
function clearSignalIfExists(path) {
|
|
235
|
-
if (!existsSync(path)) return;
|
|
236
|
-
try {
|
|
237
|
-
unlinkSync(path);
|
|
238
|
-
} catch {
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
async function waitForWorkflowOutcome(args) {
|
|
242
|
-
const signalPaths = getPauseSignalPaths(args.session);
|
|
243
|
-
if (args.pid <= 0) {
|
|
244
|
-
return { status: "exited" };
|
|
245
|
-
}
|
|
246
|
-
let outputOffset = 0;
|
|
247
|
-
while (true) {
|
|
248
|
-
outputOffset = streamOutputSince(signalPaths.outputSignalPath, outputOffset);
|
|
249
|
-
if (existsSync(signalPaths.failedSignalPath)) {
|
|
250
|
-
outputOffset = streamOutputSince(signalPaths.outputSignalPath, outputOffset);
|
|
251
|
-
const message = await waitForFailureMessage(signalPaths.failedSignalPath);
|
|
252
|
-
return { status: "failed", message: message ?? void 0 };
|
|
253
|
-
}
|
|
254
|
-
if (existsSync(signalPaths.completedSignalPath)) {
|
|
255
|
-
outputOffset = streamOutputSince(signalPaths.outputSignalPath, outputOffset);
|
|
256
|
-
return { status: "completed" };
|
|
257
|
-
}
|
|
258
|
-
if (existsSync(signalPaths.pausedSignalPath)) {
|
|
259
|
-
outputOffset = streamOutputSince(signalPaths.outputSignalPath, outputOffset);
|
|
260
|
-
return { status: "paused" };
|
|
261
|
-
}
|
|
262
|
-
if (!isProcessRunning(args.pid)) {
|
|
263
|
-
outputOffset = streamOutputSince(signalPaths.outputSignalPath, outputOffset);
|
|
264
|
-
return { status: "exited" };
|
|
265
|
-
}
|
|
266
|
-
await new Promise((resolveWait) => setTimeout(resolveWait, 250));
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
async function runResume(session, logger) {
|
|
270
|
-
const state = readSessionStateOrThrow(session);
|
|
271
|
-
const {
|
|
272
|
-
pausedSignalPath,
|
|
273
|
-
resumeSignalPath,
|
|
274
|
-
completedSignalPath,
|
|
275
|
-
failedSignalPath,
|
|
276
|
-
outputSignalPath
|
|
277
|
-
} = getPauseSignalPaths(session);
|
|
278
|
-
if (!existsSync(pausedSignalPath)) {
|
|
279
|
-
throw new Error(
|
|
280
|
-
`Session "${session}" is not paused. Run "libretto-cli run ... --session ${session}" and call pause() first.`
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
if (!isProcessRunning(state.pid)) {
|
|
284
|
-
throw new Error(
|
|
285
|
-
`No active paused workflow found for session "${session}" (worker pid ${state.pid} is not running).`
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
clearSignalIfExists(pausedSignalPath);
|
|
289
|
-
clearSignalIfExists(outputSignalPath);
|
|
290
|
-
clearSignalIfExists(completedSignalPath);
|
|
291
|
-
clearSignalIfExists(failedSignalPath);
|
|
292
|
-
setSessionStatus(session, "active", logger);
|
|
293
|
-
writeFileSync(
|
|
294
|
-
resumeSignalPath,
|
|
295
|
-
JSON.stringify(
|
|
296
|
-
{
|
|
297
|
-
resumedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
298
|
-
sourcePid: process.pid
|
|
299
|
-
},
|
|
300
|
-
null,
|
|
301
|
-
2
|
|
302
|
-
),
|
|
303
|
-
"utf8"
|
|
304
|
-
);
|
|
305
|
-
console.log(`Resume signal sent for session "${session}".`);
|
|
306
|
-
const outcome = await waitForWorkflowOutcome({
|
|
307
|
-
session,
|
|
308
|
-
pid: state.pid
|
|
309
|
-
});
|
|
310
|
-
if (outcome.status === "completed") {
|
|
311
|
-
setSessionStatus(session, "completed", logger);
|
|
312
|
-
console.log("Integration completed.");
|
|
313
|
-
return;
|
|
314
|
-
}
|
|
315
|
-
if (outcome.status === "failed") {
|
|
316
|
-
setSessionStatus(session, "failed", logger);
|
|
317
|
-
throw new Error(
|
|
318
|
-
outcome.message ? `Workflow failed after resume: ${outcome.message}` : "Workflow failed after resume."
|
|
319
|
-
);
|
|
320
|
-
}
|
|
321
|
-
if (outcome.status === "exited") {
|
|
322
|
-
setSessionStatus(session, "exited", logger);
|
|
323
|
-
throw new Error(
|
|
324
|
-
`Workflow process for session "${session}" exited before reporting completion or pause.`
|
|
325
|
-
);
|
|
326
|
-
}
|
|
327
|
-
setSessionStatus(session, "paused", logger);
|
|
328
|
-
console.log("Workflow paused.");
|
|
329
|
-
}
|
|
330
|
-
async function runIntegrationFromFile(args, logger) {
|
|
331
|
-
await stopExistingFailedRunSession(args.session, logger);
|
|
332
|
-
assertSessionAvailableForStart(args.session, logger);
|
|
333
|
-
const signalPaths = getPauseSignalPaths(args.session);
|
|
334
|
-
clearSignalIfExists(signalPaths.pausedSignalPath);
|
|
335
|
-
clearSignalIfExists(signalPaths.resumeSignalPath);
|
|
336
|
-
clearSignalIfExists(signalPaths.completedSignalPath);
|
|
337
|
-
clearSignalIfExists(signalPaths.failedSignalPath);
|
|
338
|
-
clearSignalIfExists(signalPaths.outputSignalPath);
|
|
339
|
-
const workerEntryPath = fileURLToPath(
|
|
340
|
-
new URL("../workers/run-integration-worker.js", import.meta.url)
|
|
341
|
-
);
|
|
342
|
-
const payload = JSON.stringify(args);
|
|
343
|
-
const worker = spawn(process.execPath, [workerEntryPath, payload], {
|
|
344
|
-
detached: true,
|
|
345
|
-
stdio: "ignore",
|
|
346
|
-
env: process.env
|
|
347
|
-
});
|
|
348
|
-
worker.unref();
|
|
349
|
-
const outcome = await waitForWorkflowOutcome({
|
|
350
|
-
session: args.session,
|
|
351
|
-
pid: worker.pid ?? 0
|
|
352
|
-
});
|
|
353
|
-
if (outcome.status === "paused") {
|
|
354
|
-
setSessionStatus(args.session, "paused", logger);
|
|
355
|
-
console.log("Workflow paused.");
|
|
356
|
-
return;
|
|
357
|
-
}
|
|
358
|
-
if (outcome.status === "failed") {
|
|
359
|
-
setSessionStatus(args.session, "failed", logger);
|
|
360
|
-
throw new Error(
|
|
361
|
-
`${outcome.message ?? "Workflow failed during run."}
|
|
362
|
-
Browser is still open. You can use \`exec\` to inspect it. Call \`run\` to re-run the workflow.`
|
|
363
|
-
);
|
|
364
|
-
}
|
|
365
|
-
if (outcome.status === "exited") {
|
|
366
|
-
setSessionStatus(args.session, "exited", logger);
|
|
367
|
-
throw new Error(
|
|
368
|
-
"Workflow process exited before reporting completion or pause during run."
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
setSessionStatus(args.session, "completed", logger);
|
|
372
|
-
}
|
|
373
|
-
function registerExecutionCommands(yargs, logger) {
|
|
374
|
-
return yargs.command(
|
|
375
|
-
"exec [code..]",
|
|
376
|
-
"Execute Playwright TypeScript code",
|
|
377
|
-
(cmd) => cmd.option("visualize", { type: "boolean", default: false }).option("page", { type: "string" }),
|
|
378
|
-
async (argv) => {
|
|
379
|
-
const codeParts = Array.isArray(argv.code) ? argv.code : argv.code ? [String(argv.code)] : [];
|
|
380
|
-
const code = codeParts.join(" ");
|
|
381
|
-
if (!code) {
|
|
382
|
-
throw new Error(
|
|
383
|
-
"Usage: libretto-cli exec <code> [--session <name>] [--visualize]"
|
|
384
|
-
);
|
|
385
|
-
}
|
|
386
|
-
await runExec(
|
|
387
|
-
code,
|
|
388
|
-
String(argv.session),
|
|
389
|
-
logger,
|
|
390
|
-
Boolean(argv.visualize),
|
|
391
|
-
argv.page ? String(argv.page) : void 0
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
).command(
|
|
395
|
-
"run [integrationFile] [integrationExport]",
|
|
396
|
-
"Run an exported Libretto workflow from a file",
|
|
397
|
-
(cmd) => cmd.option("params", { type: "string" }).option("params-file", { type: "string" }).option("headed", { type: "boolean", default: false }).option("headless", { type: "boolean", default: false }).option("auth-profile", { type: "string", describe: "Domain for local auth profile (e.g. apps.example.com)" }),
|
|
398
|
-
async (argv) => {
|
|
399
|
-
const usage = "Usage: libretto-cli run <integrationFile> <integrationExport> [--params <json> | --params-file <path>] [--headed|--headless]";
|
|
400
|
-
const integrationPath = argv.integrationFile;
|
|
401
|
-
const exportName = argv.integrationExport;
|
|
402
|
-
const legacyDebug = argv.debug;
|
|
403
|
-
if (legacyDebug !== void 0) {
|
|
404
|
-
throw new Error(
|
|
405
|
-
"The --debug flag has been removed. Run the command without --debug."
|
|
406
|
-
);
|
|
407
|
-
}
|
|
408
|
-
if (!integrationPath || !exportName) {
|
|
409
|
-
throw new Error(usage);
|
|
410
|
-
}
|
|
411
|
-
const session = String(argv.session);
|
|
412
|
-
const rawInlineParams = argv.params;
|
|
413
|
-
const paramsFile = argv["params-file"];
|
|
414
|
-
if (rawInlineParams && paramsFile) {
|
|
415
|
-
throw new Error("Pass either --params or --params-file, not both.");
|
|
416
|
-
}
|
|
417
|
-
const params = (() => {
|
|
418
|
-
if (paramsFile) {
|
|
419
|
-
let content;
|
|
420
|
-
try {
|
|
421
|
-
content = readFileSync(paramsFile, "utf8");
|
|
422
|
-
} catch {
|
|
423
|
-
throw new Error(
|
|
424
|
-
`Could not read --params-file "${paramsFile}". Ensure the file exists and is readable.`
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
return parseJsonArg("--params-file", content);
|
|
428
|
-
}
|
|
429
|
-
if (rawInlineParams) {
|
|
430
|
-
return parseJsonArg("--params", rawInlineParams);
|
|
431
|
-
}
|
|
432
|
-
return {};
|
|
433
|
-
})();
|
|
434
|
-
const hasHeadedFlag = Boolean(argv.headed);
|
|
435
|
-
const hasHeadlessFlag = Boolean(argv.headless);
|
|
436
|
-
if (hasHeadedFlag && hasHeadlessFlag) {
|
|
437
|
-
throw new Error("Cannot pass both --headed and --headless.");
|
|
438
|
-
}
|
|
439
|
-
const headlessMode = hasHeadedFlag ? false : hasHeadlessFlag ? true : void 0;
|
|
440
|
-
const authProfileDomain = argv["auth-profile"];
|
|
441
|
-
await runIntegrationFromFile({
|
|
442
|
-
integrationPath,
|
|
443
|
-
exportName,
|
|
444
|
-
session,
|
|
445
|
-
params,
|
|
446
|
-
headless: headlessMode ?? false,
|
|
447
|
-
authProfileDomain
|
|
448
|
-
}, logger);
|
|
449
|
-
}
|
|
450
|
-
).command(
|
|
451
|
-
"resume",
|
|
452
|
-
"Resume a paused workflow for the current session",
|
|
453
|
-
(cmd) => cmd,
|
|
454
|
-
async (argv) => {
|
|
455
|
-
await runResume(String(argv.session), logger);
|
|
456
|
-
}
|
|
457
|
-
);
|
|
458
|
-
}
|
|
459
|
-
export {
|
|
460
|
-
registerExecutionCommands
|
|
461
|
-
};
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, cpSync, readdirSync } from "node:fs";
|
|
2
|
-
import { join, dirname } from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { REPO_ROOT } from "../core/context.js";
|
|
6
|
-
function getSkillSourceDir() {
|
|
7
|
-
const thisDir = dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const pkgRoot = join(thisDir, "..", "..", "..");
|
|
9
|
-
const skillDir = join(pkgRoot, "skill");
|
|
10
|
-
if (existsSync(skillDir)) return skillDir;
|
|
11
|
-
const skillsDir = join(pkgRoot, "skills");
|
|
12
|
-
if (existsSync(skillsDir)) return skillsDir;
|
|
13
|
-
throw new Error(
|
|
14
|
-
"Could not find skill/ or skills/ directory in the libretto package."
|
|
15
|
-
);
|
|
16
|
-
}
|
|
17
|
-
function copySkills() {
|
|
18
|
-
const src = getSkillSourceDir();
|
|
19
|
-
const files = readdirSync(src);
|
|
20
|
-
if (files.length === 0) {
|
|
21
|
-
console.log(" No skill files found to copy.");
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
const targets = [
|
|
25
|
-
join(REPO_ROOT, ".agents", "skills", "libretto"),
|
|
26
|
-
join(REPO_ROOT, ".claude", "skills", "libretto")
|
|
27
|
-
];
|
|
28
|
-
for (const target of targets) {
|
|
29
|
-
mkdirSync(target, { recursive: true });
|
|
30
|
-
cpSync(src, target, { recursive: true });
|
|
31
|
-
console.log(` \u2713 Copied skill files to ${target}`);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function installBrowsers() {
|
|
35
|
-
console.log("\nInstalling Playwright Chromium...");
|
|
36
|
-
const result = spawnSync("npx", ["playwright", "install", "chromium"], {
|
|
37
|
-
stdio: "inherit",
|
|
38
|
-
shell: true
|
|
39
|
-
});
|
|
40
|
-
if (result.status === 0) {
|
|
41
|
-
console.log(" \u2713 Playwright Chromium installed");
|
|
42
|
-
} else {
|
|
43
|
-
console.error(
|
|
44
|
-
" \u2717 Failed to install Playwright Chromium. Run manually: npx playwright install chromium"
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
function checkSnapshotLLM() {
|
|
49
|
-
const hasAnyCreds = process.env.GOOGLE_CLOUD_PROJECT || process.env.GCLOUD_PROJECT || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY;
|
|
50
|
-
console.log("\nSnapshot LLM configuration:");
|
|
51
|
-
if (hasAnyCreds) {
|
|
52
|
-
console.log(" \u2713 LLM credentials detected");
|
|
53
|
-
} else {
|
|
54
|
-
console.log(" \u2717 No LLM credentials found.");
|
|
55
|
-
console.log(" Set one of the following environment variables:");
|
|
56
|
-
console.log(" GOOGLE_CLOUD_PROJECT (for Vertex AI / Gemini)");
|
|
57
|
-
console.log(" ANTHROPIC_API_KEY (for Claude)");
|
|
58
|
-
console.log(" OPENAI_API_KEY (for GPT)");
|
|
59
|
-
console.log(
|
|
60
|
-
" Then configure via: npx libretto ai configure <preset>"
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function registerInitCommand(yargs) {
|
|
65
|
-
return yargs.command(
|
|
66
|
-
"init",
|
|
67
|
-
"Initialize libretto in the current project",
|
|
68
|
-
(cmd) => cmd.option("skip-browsers", {
|
|
69
|
-
type: "boolean",
|
|
70
|
-
default: false,
|
|
71
|
-
describe: "Skip Playwright Chromium installation"
|
|
72
|
-
}),
|
|
73
|
-
(argv) => {
|
|
74
|
-
console.log("Initializing libretto...\n");
|
|
75
|
-
console.log("Copying skill files...");
|
|
76
|
-
try {
|
|
77
|
-
copySkills();
|
|
78
|
-
} catch (err) {
|
|
79
|
-
console.error(
|
|
80
|
-
` \u2717 ${err instanceof Error ? err.message : String(err)}`
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
if (!argv["skip-browsers"]) {
|
|
84
|
-
installBrowsers();
|
|
85
|
-
} else {
|
|
86
|
-
console.log("\nSkipping browser installation (--skip-browsers)");
|
|
87
|
-
}
|
|
88
|
-
checkSnapshotLLM();
|
|
89
|
-
console.log("\n\u2713 libretto init complete");
|
|
90
|
-
}
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
export {
|
|
94
|
-
registerInitCommand
|
|
95
|
-
};
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { listOpenPages } from "../core/browser.js";
|
|
2
|
-
import { withSessionLogger } from "../core/context.js";
|
|
3
|
-
import {
|
|
4
|
-
clearActionLog,
|
|
5
|
-
clearNetworkLog,
|
|
6
|
-
formatActionEntry,
|
|
7
|
-
formatNetworkEntry,
|
|
8
|
-
readActionLog,
|
|
9
|
-
readNetworkLog
|
|
10
|
-
} from "../core/telemetry.js";
|
|
11
|
-
async function resolvePageId(session, pageId) {
|
|
12
|
-
if (!pageId) return void 0;
|
|
13
|
-
const pages = await withSessionLogger(
|
|
14
|
-
session,
|
|
15
|
-
async (logger) => listOpenPages(session, logger)
|
|
16
|
-
);
|
|
17
|
-
const foundPage = pages.find((page) => page.id === pageId);
|
|
18
|
-
if (!foundPage) {
|
|
19
|
-
throw new Error(
|
|
20
|
-
`Page "${pageId}" was not found in session "${session}". Run "libretto-cli pages --session ${session}" to list ids.`
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
return pageId;
|
|
24
|
-
}
|
|
25
|
-
function registerLogCommands(yargs) {
|
|
26
|
-
return yargs.command(
|
|
27
|
-
"network",
|
|
28
|
-
"View captured network requests",
|
|
29
|
-
(cmd) => cmd.option("last", { type: "number" }).option("filter", { type: "string" }).option("method", { type: "string" }).option("page", { type: "string" }).option("clear", { type: "boolean", default: false }),
|
|
30
|
-
async (argv) => {
|
|
31
|
-
const session = String(argv.session);
|
|
32
|
-
if (argv.clear) {
|
|
33
|
-
clearNetworkLog(session);
|
|
34
|
-
console.log("Network log cleared.");
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
const pageId = await resolvePageId(
|
|
38
|
-
session,
|
|
39
|
-
argv.page ? String(argv.page) : void 0
|
|
40
|
-
);
|
|
41
|
-
const entries = readNetworkLog(session, {
|
|
42
|
-
last: typeof argv.last === "number" ? argv.last : void 0,
|
|
43
|
-
filter: argv.filter,
|
|
44
|
-
method: argv.method,
|
|
45
|
-
pageId
|
|
46
|
-
});
|
|
47
|
-
if (entries.length === 0) {
|
|
48
|
-
console.log("No network requests captured.");
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
for (const entry of entries) {
|
|
52
|
-
console.log(formatNetworkEntry(entry));
|
|
53
|
-
}
|
|
54
|
-
console.log(`
|
|
55
|
-
${entries.length} request(s) shown.`);
|
|
56
|
-
}
|
|
57
|
-
).command(
|
|
58
|
-
"actions",
|
|
59
|
-
"View captured actions",
|
|
60
|
-
(cmd) => cmd.option("last", { type: "number" }).option("filter", { type: "string" }).option("action", { type: "string" }).option("source", { type: "string" }).option("page", { type: "string" }).option("clear", { type: "boolean", default: false }),
|
|
61
|
-
async (argv) => {
|
|
62
|
-
const session = String(argv.session);
|
|
63
|
-
if (argv.clear) {
|
|
64
|
-
clearActionLog(session);
|
|
65
|
-
console.log("Action log cleared.");
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const pageId = await resolvePageId(
|
|
69
|
-
session,
|
|
70
|
-
argv.page ? String(argv.page) : void 0
|
|
71
|
-
);
|
|
72
|
-
const entries = readActionLog(session, {
|
|
73
|
-
last: typeof argv.last === "number" ? argv.last : void 0,
|
|
74
|
-
filter: argv.filter,
|
|
75
|
-
action: argv.action,
|
|
76
|
-
source: argv.source,
|
|
77
|
-
pageId
|
|
78
|
-
});
|
|
79
|
-
if (entries.length === 0) {
|
|
80
|
-
console.log("No actions captured.");
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
for (const entry of entries) {
|
|
84
|
-
console.log(formatActionEntry(entry));
|
|
85
|
-
}
|
|
86
|
-
console.log(`
|
|
87
|
-
${entries.length} action(s) shown.`);
|
|
88
|
-
}
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
export {
|
|
92
|
-
registerLogCommands
|
|
93
|
-
};
|