libretto 0.6.9 → 0.6.11

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.
Files changed (60) hide show
  1. package/dist/cli/cli.js +2 -0
  2. package/dist/cli/commands/auth.js +535 -0
  3. package/dist/cli/commands/billing.js +74 -0
  4. package/dist/cli/commands/browser.js +8 -3
  5. package/dist/cli/commands/deploy.js +2 -7
  6. package/dist/cli/commands/execution.js +99 -136
  7. package/dist/cli/commands/snapshot.js +38 -126
  8. package/dist/cli/core/ai-model.js +0 -3
  9. package/dist/cli/core/auth-fetch.js +195 -0
  10. package/dist/cli/core/auth-storage.js +52 -0
  11. package/dist/cli/core/browser.js +128 -202
  12. package/dist/cli/core/daemon/config.js +6 -0
  13. package/dist/cli/core/daemon/daemon.js +298 -0
  14. package/dist/cli/core/daemon/exec.js +86 -0
  15. package/dist/cli/core/daemon/index.js +16 -0
  16. package/dist/cli/core/daemon/ipc.js +171 -0
  17. package/dist/cli/core/daemon/pages.js +15 -0
  18. package/dist/cli/core/daemon/snapshot.js +86 -0
  19. package/dist/cli/core/daemon/spawn.js +90 -0
  20. package/dist/cli/core/exec-compiler.js +111 -0
  21. package/dist/cli/core/prompt.js +72 -0
  22. package/dist/cli/core/providers/libretto-cloud.js +2 -6
  23. package/dist/cli/core/readonly-exec.js +1 -1
  24. package/dist/cli/router.js +4 -0
  25. package/dist/cli/workers/run-integration-runtime.js +0 -5
  26. package/dist/shared/state/session-state.d.ts +1 -0
  27. package/dist/shared/state/session-state.js +2 -1
  28. package/docs/browser-automation-approaches.md +435 -0
  29. package/docs/releasing.md +117 -0
  30. package/package.json +4 -3
  31. package/skills/libretto/SKILL.md +14 -1
  32. package/skills/libretto-readonly/SKILL.md +1 -1
  33. package/src/cli/cli.ts +2 -0
  34. package/src/cli/commands/auth.ts +787 -0
  35. package/src/cli/commands/billing.ts +133 -0
  36. package/src/cli/commands/browser.ts +8 -2
  37. package/src/cli/commands/deploy.ts +2 -7
  38. package/src/cli/commands/execution.ts +126 -186
  39. package/src/cli/commands/snapshot.ts +46 -143
  40. package/src/cli/core/ai-model.ts +4 -5
  41. package/src/cli/core/auth-fetch.ts +283 -0
  42. package/src/cli/core/auth-storage.ts +102 -0
  43. package/src/cli/core/browser.ts +159 -242
  44. package/src/cli/core/daemon/config.ts +46 -0
  45. package/src/cli/core/daemon/daemon.ts +429 -0
  46. package/src/cli/core/daemon/exec.ts +128 -0
  47. package/src/cli/core/daemon/index.ts +24 -0
  48. package/src/cli/core/daemon/ipc.ts +294 -0
  49. package/src/cli/core/daemon/pages.ts +21 -0
  50. package/src/cli/core/daemon/snapshot.ts +114 -0
  51. package/src/cli/core/daemon/spawn.ts +171 -0
  52. package/src/cli/core/exec-compiler.ts +169 -0
  53. package/src/cli/core/prompt.ts +94 -0
  54. package/src/cli/core/providers/libretto-cloud.ts +2 -6
  55. package/src/cli/core/readonly-exec.ts +2 -1
  56. package/src/cli/router.ts +4 -0
  57. package/src/cli/workers/run-integration-runtime.ts +0 -6
  58. package/src/shared/state/session-state.ts +1 -0
  59. package/dist/cli/core/browser-daemon.js +0 -122
  60. package/src/cli/core/browser-daemon.ts +0 -198
@@ -0,0 +1,90 @@
1
+ import { openSync, closeSync } from "node:fs";
2
+ import { fileURLToPath, pathToFileURL } from "node:url";
3
+ import { createRequire } from "node:module";
4
+ import { spawn } from "node:child_process";
5
+ import { getDaemonSocketPath } from "./ipc.js";
6
+ import { DaemonClient } from "./ipc.js";
7
+ const DEFAULT_IPC_TIMEOUT_MS = 1e4;
8
+ const IPC_POLL_INTERVAL_MS = 250;
9
+ async function spawnSessionDaemon(options) {
10
+ const {
11
+ config,
12
+ session,
13
+ logger,
14
+ logPath,
15
+ ipcTimeoutMs = DEFAULT_IPC_TIMEOUT_MS,
16
+ onFailure
17
+ } = options;
18
+ const daemonEntryPath = fileURLToPath(
19
+ new URL("./daemon.js", import.meta.url)
20
+ );
21
+ const require2 = createRequire(import.meta.url);
22
+ const tsxImportPath = pathToFileURL(require2.resolve("tsx/esm")).href;
23
+ const childStderrFd = openSync(logPath, "a");
24
+ const child = spawn(
25
+ process.execPath,
26
+ ["--import", tsxImportPath, daemonEntryPath, JSON.stringify(config)],
27
+ {
28
+ detached: true,
29
+ stdio: ["ignore", "ignore", childStderrFd]
30
+ }
31
+ );
32
+ child.unref();
33
+ closeSync(childStderrFd);
34
+ const pid = child.pid;
35
+ logger.info("daemon-spawned", { pid, session });
36
+ let childSpawnError = null;
37
+ let childEarlyExit = null;
38
+ child.on("error", (err) => {
39
+ childSpawnError = err;
40
+ logger.error("daemon-spawn-error", { error: err, session });
41
+ });
42
+ child.on("exit", (code, signal) => {
43
+ childEarlyExit = { code, signal };
44
+ logger.warn("daemon-early-exit", { code, signal, session, pid });
45
+ });
46
+ const socketPath = getDaemonSocketPath(session);
47
+ const client = new DaemonClient(socketPath);
48
+ const maxAttempts = Math.ceil(ipcTimeoutMs / IPC_POLL_INTERVAL_MS);
49
+ let ipcReady = false;
50
+ for (let i = 0; i < maxAttempts; i++) {
51
+ const spawnError = childSpawnError;
52
+ if (spawnError !== null) {
53
+ await onFailure?.();
54
+ const errWithCode = spawnError;
55
+ const hint = errWithCode.code === "ENOENT" ? " Ensure Node.js is available in PATH for child processes." : "";
56
+ throw new Error(
57
+ `Failed to spawn daemon: ${spawnError.message}.${hint} Check logs: ${logPath}`
58
+ );
59
+ }
60
+ const earlyExit = childEarlyExit;
61
+ if (earlyExit !== null) {
62
+ await onFailure?.();
63
+ const status = earlyExit.code ?? earlyExit.signal ?? "unknown";
64
+ throw new Error(
65
+ `Daemon exited before startup (status: ${status}). Check logs: ${logPath}`
66
+ );
67
+ }
68
+ await new Promise((r) => setTimeout(r, IPC_POLL_INTERVAL_MS));
69
+ ipcReady = await client.ping();
70
+ if (ipcReady) break;
71
+ if (i > 0 && i % 10 === 0) {
72
+ logger.info("daemon-waiting-for-ipc", { attempt: i, session });
73
+ }
74
+ }
75
+ if (!ipcReady) {
76
+ try {
77
+ process.kill(pid, "SIGTERM");
78
+ } catch {
79
+ }
80
+ await onFailure?.();
81
+ throw new Error(
82
+ `Daemon failed to start within ${Math.ceil(ipcTimeoutMs / 1e3)}s. Check logs: ${logPath}`
83
+ );
84
+ }
85
+ logger.info("daemon-ipc-ready", { session, socketPath });
86
+ return { pid, socketPath, client };
87
+ }
88
+ export {
89
+ spawnSessionDaemon
90
+ };
@@ -0,0 +1,111 @@
1
+ import * as moduleBuiltin from "node:module";
2
+ const stripTypeScriptTypes = moduleBuiltin.stripTypeScriptTypes;
3
+ function withSuppressedStripTypeScriptWarning(action) {
4
+ const mutableProcess = process;
5
+ const originalEmitWarning = mutableProcess.emitWarning;
6
+ mutableProcess.emitWarning = (...args) => {
7
+ const warning = args[0];
8
+ const typeOrOptions = args[1];
9
+ const warningMessage = typeof warning === "string" ? warning : warning instanceof Error ? warning.message : "";
10
+ const warningType = typeof typeOrOptions === "string" ? typeOrOptions : typeof typeOrOptions === "object" && typeOrOptions !== null && "type" in typeOrOptions && typeof typeOrOptions.type === "string" ? typeOrOptions.type ?? "" : "";
11
+ if (warningType === "ExperimentalWarning" && warningMessage.includes("stripTypeScriptTypes")) {
12
+ return;
13
+ }
14
+ originalEmitWarning(...args);
15
+ };
16
+ try {
17
+ return action();
18
+ } finally {
19
+ mutableProcess.emitWarning = originalEmitWarning;
20
+ }
21
+ }
22
+ function compileTypeScriptExecFunction(code, helperNames) {
23
+ if (!stripTypeScriptTypes) return null;
24
+ const wrappedSource = `(async function __librettoExec(${helperNames.join(", ")}) {
25
+ ${code}
26
+ })`;
27
+ const jsSource = withSuppressedStripTypeScriptWarning(
28
+ () => stripTypeScriptTypes(wrappedSource, { mode: "strip" })
29
+ );
30
+ const createFunction = new Function(
31
+ `return ${jsSource}`
32
+ );
33
+ return createFunction();
34
+ }
35
+ function compileExecFunction(code, helperNames) {
36
+ const typeStripped = compileTypeScriptExecFunction(code, helperNames);
37
+ if (typeStripped) return typeStripped;
38
+ const AsyncFunction = Object.getPrototypeOf(async function() {
39
+ }).constructor;
40
+ return new AsyncFunction(...helperNames, code);
41
+ }
42
+ function stripEmptyCatchHandlers(code) {
43
+ const catchRe = /\??\s*\.catch\(\s*\(\)\s*=>\s*\{\s*\}\s*\)/g;
44
+ let strippedCount = 0;
45
+ let result = "";
46
+ let i = 0;
47
+ while (i < code.length) {
48
+ if (code[i] === "/" && code[i + 1] === "/") {
49
+ const end = code.indexOf("\n", i);
50
+ const slice = end === -1 ? code.slice(i) : code.slice(i, end + 1);
51
+ result += slice;
52
+ i += slice.length;
53
+ continue;
54
+ }
55
+ if (code[i] === "/" && code[i + 1] === "*") {
56
+ const end = code.indexOf("*/", i + 2);
57
+ const slice = end === -1 ? code.slice(i) : code.slice(i, end + 2);
58
+ result += slice;
59
+ i += slice.length;
60
+ continue;
61
+ }
62
+ if (code[i] === '"' || code[i] === "'" || code[i] === "`") {
63
+ const quote = code[i];
64
+ let j = i + 1;
65
+ while (j < code.length) {
66
+ if (code[j] === "\\" && quote !== "`") {
67
+ j += 2;
68
+ continue;
69
+ }
70
+ if (code[j] === "\\" && quote === "`") {
71
+ j += 2;
72
+ continue;
73
+ }
74
+ if (code[j] === quote) {
75
+ j++;
76
+ break;
77
+ }
78
+ if (quote === "`" && code[j] === "$" && code[j + 1] === "{") {
79
+ let depth = 1;
80
+ j += 2;
81
+ while (j < code.length && depth > 0) {
82
+ if (code[j] === "{") depth++;
83
+ else if (code[j] === "}") depth--;
84
+ j++;
85
+ }
86
+ continue;
87
+ }
88
+ j++;
89
+ }
90
+ result += code.slice(i, j);
91
+ i = j;
92
+ continue;
93
+ }
94
+ catchRe.lastIndex = i;
95
+ const match = catchRe.exec(code);
96
+ if (match && match.index === i) {
97
+ strippedCount++;
98
+ i += match[0].length;
99
+ continue;
100
+ }
101
+ result += code[i];
102
+ i++;
103
+ }
104
+ return { cleaned: result, strippedCount };
105
+ }
106
+ export {
107
+ compileExecFunction,
108
+ compileTypeScriptExecFunction,
109
+ stripEmptyCatchHandlers,
110
+ withSuppressedStripTypeScriptWarning
111
+ };
@@ -0,0 +1,72 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import { stdin, stdout } from "node:process";
3
+ async function prompt(question, opts = {}) {
4
+ const rl = createInterface({ input: stdin, output: stdout });
5
+ try {
6
+ const display = opts.defaultValue ? `${question} (${opts.defaultValue}) ` : `${question} `;
7
+ const answer = (await rl.question(display)).trim();
8
+ if (answer.length === 0 && opts.defaultValue !== void 0) {
9
+ return opts.defaultValue;
10
+ }
11
+ return answer;
12
+ } finally {
13
+ rl.close();
14
+ }
15
+ }
16
+ const CTRL_C = "";
17
+ const CR = "\r";
18
+ const LF = "\n";
19
+ const BACKSPACE = "\b";
20
+ const DELETE = "\x7F";
21
+ async function promptPassword(question) {
22
+ if (!stdin.isTTY) {
23
+ return prompt(question);
24
+ }
25
+ process.stdout.write(`${question} `);
26
+ return new Promise((resolve, reject) => {
27
+ let buffer = "";
28
+ const wasRaw = stdin.isRaw;
29
+ stdin.setRawMode(true);
30
+ stdin.resume();
31
+ stdin.setEncoding("utf8");
32
+ const cleanup = () => {
33
+ stdin.setRawMode(wasRaw);
34
+ stdin.pause();
35
+ stdin.removeListener("data", onData);
36
+ process.stdout.write("\n");
37
+ };
38
+ const onData = (chunk) => {
39
+ for (const ch of chunk) {
40
+ if (ch === LF || ch === CR) {
41
+ cleanup();
42
+ resolve(buffer);
43
+ return;
44
+ }
45
+ if (ch === CTRL_C) {
46
+ cleanup();
47
+ reject(new Error("Aborted."));
48
+ return;
49
+ }
50
+ if (ch === BACKSPACE || ch === DELETE) {
51
+ if (buffer.length > 0) {
52
+ buffer = buffer.slice(0, -1);
53
+ process.stdout.write("\b \b");
54
+ }
55
+ continue;
56
+ }
57
+ if (ch.charCodeAt(0) < 32) continue;
58
+ buffer += ch;
59
+ process.stdout.write("*");
60
+ }
61
+ };
62
+ stdin.on("data", onData);
63
+ });
64
+ }
65
+ function slugify(name) {
66
+ return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
67
+ }
68
+ export {
69
+ prompt,
70
+ promptPassword,
71
+ slugify
72
+ };
@@ -1,15 +1,11 @@
1
+ import { HOSTED_API_URL } from "../auth-fetch.js";
1
2
  function createLibrettoCloudProvider() {
2
3
  const apiKey = process.env.LIBRETTO_API_KEY;
3
4
  if (!apiKey)
4
5
  throw new Error(
5
6
  "LIBRETTO_API_KEY is required for the Libretto Cloud provider."
6
7
  );
7
- const apiUrl = process.env.LIBRETTO_API_URL;
8
- if (!apiUrl)
9
- throw new Error(
10
- "LIBRETTO_API_URL is required for the Libretto Cloud provider."
11
- );
12
- const endpoint = apiUrl.replace(/\/$/, "");
8
+ const endpoint = HOSTED_API_URL;
13
9
  return {
14
10
  async createSession() {
15
11
  const timeoutSeconds = Number(
@@ -214,7 +214,7 @@ function createReadonlyExecHelpers(page, options = {}) {
214
214
  "fetch is blocked in readonly-exec; use get() instead"
215
215
  );
216
216
  },
217
- console,
217
+ console: options.console ?? console,
218
218
  setTimeout,
219
219
  setInterval,
220
220
  clearTimeout,
@@ -1,4 +1,6 @@
1
1
  import { aiCommands } from "./commands/ai.js";
2
+ import { authCommands } from "./commands/auth.js";
3
+ import { billingCommands } from "./commands/billing.js";
2
4
  import { browserCommands } from "./commands/browser.js";
3
5
  import { deployCommand } from "./commands/deploy.js";
4
6
  import { executionCommands } from "./commands/execution.js";
@@ -11,6 +13,8 @@ const cliRoutes = {
11
13
  deploy: deployCommand,
12
14
  ...executionCommands,
13
15
  ai: aiCommands,
16
+ auth: authCommands,
17
+ billing: billingCommands,
14
18
  setup: setupCommand,
15
19
  status: statusCommand,
16
20
  snapshot: snapshotCommand
@@ -3,7 +3,6 @@ import { writeFile } from "node:fs/promises";
3
3
  import { cwd } from "node:process";
4
4
  import { isAbsolute, resolve } from "node:path";
5
5
  import { pathToFileURL } from "node:url";
6
- import { loadEnv } from "../../shared/env/load-env.js";
7
6
  import {
8
7
  getDefaultWorkflowFromModuleExports,
9
8
  getWorkflowsFromModuleExports,
@@ -127,10 +126,6 @@ async function installHeadedWorkflowVisualization(args) {
127
126
  async function runIntegrationInternal(args, options) {
128
127
  const { logger } = options;
129
128
  const absolutePath = getAbsoluteIntegrationPath(args.integrationPath);
130
- const envPath = loadEnv();
131
- if (envPath) {
132
- logger.info("loaded-env", { path: envPath });
133
- }
134
129
  const workflow = await loadDefaultWorkflow(absolutePath);
135
130
  const signalPaths = getPauseSignalPaths(args.session);
136
131
  await removeSignalIfExists(signalPaths.pausedSignalPath);
@@ -48,6 +48,7 @@ declare const SessionStateFileSchema: z.ZodObject<{
48
48
  name: z.ZodString;
49
49
  sessionId: z.ZodString;
50
50
  }, z.core.$strip>>;
51
+ daemonSocketPath: z.ZodOptional<z.ZodString>;
51
52
  }, z.core.$strip>;
52
53
  type SessionStatus = z.infer<typeof SessionStatusSchema>;
53
54
  type SessionAccessMode = z.infer<typeof SessionAccessModeSchema>;
@@ -27,7 +27,8 @@ const SessionStateFileSchema = z.object({
27
27
  status: SessionStatusSchema.optional(),
28
28
  mode: SessionAccessModeSchema.default("write-access"),
29
29
  viewport: SessionViewportSchema.optional(),
30
- provider: ProviderStateSchema.optional()
30
+ provider: ProviderStateSchema.optional(),
31
+ daemonSocketPath: z.string().optional()
31
32
  });
32
33
  function formatIssues(error) {
33
34
  return error.issues.map((issue) => {