libretto 0.5.0 → 0.5.1

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 (116) hide show
  1. package/README.md +106 -36
  2. package/dist/cli/cli.js +22 -97
  3. package/dist/cli/commands/browser.js +86 -59
  4. package/dist/cli/commands/execution.js +199 -86
  5. package/dist/cli/commands/init.js +30 -8
  6. package/dist/cli/commands/logs.js +4 -5
  7. package/dist/cli/commands/shared.js +30 -29
  8. package/dist/cli/commands/snapshot.js +26 -39
  9. package/dist/cli/core/ai-config.js +9 -2
  10. package/dist/cli/core/api-snapshot-analyzer.js +15 -5
  11. package/dist/cli/core/browser.js +132 -29
  12. package/dist/cli/core/context.js +4 -1
  13. package/dist/cli/core/session-telemetry.js +5 -2
  14. package/dist/cli/core/session.js +21 -8
  15. package/dist/cli/core/snapshot-analyzer.js +14 -31
  16. package/dist/cli/core/snapshot-api-config.js +2 -6
  17. package/dist/cli/core/telemetry.js +10 -2
  18. package/dist/cli/framework/simple-cli.js +45 -25
  19. package/dist/cli/router.js +14 -21
  20. package/dist/cli/workers/run-integration-runtime.js +24 -5
  21. package/dist/cli/workers/run-integration-worker-protocol.js +3 -1
  22. package/dist/cli/workers/run-integration-worker.js +1 -4
  23. package/dist/index.d.ts +1 -2
  24. package/dist/index.js +7 -10
  25. package/dist/runtime/download/download.js +5 -1
  26. package/dist/runtime/extract/extract.js +11 -2
  27. package/dist/runtime/network/network.js +8 -1
  28. package/dist/runtime/recovery/agent.js +6 -2
  29. package/dist/runtime/recovery/errors.js +3 -1
  30. package/dist/runtime/recovery/recovery.js +3 -1
  31. package/dist/shared/condense-dom/condense-dom.js +6 -13
  32. package/dist/shared/config/config.d.ts +1 -9
  33. package/dist/shared/config/config.js +0 -18
  34. package/dist/shared/config/index.d.ts +2 -1
  35. package/dist/shared/config/index.js +0 -10
  36. package/dist/shared/debug/pause.js +9 -3
  37. package/dist/shared/instrumentation/instrument.js +101 -5
  38. package/dist/shared/llm/ai-sdk-adapter.js +3 -1
  39. package/dist/shared/llm/client.js +3 -1
  40. package/dist/shared/logger/index.js +4 -1
  41. package/dist/shared/run/api.js +3 -1
  42. package/dist/shared/run/browser.js +7 -2
  43. package/dist/shared/state/session-state.d.ts +2 -1
  44. package/dist/shared/state/session-state.js +5 -2
  45. package/dist/shared/visualization/ghost-cursor.js +19 -10
  46. package/dist/shared/visualization/highlight.js +9 -6
  47. package/dist/shared/workflow/workflow.d.ts +4 -5
  48. package/dist/shared/workflow/workflow.js +3 -5
  49. package/package.json +6 -2
  50. package/scripts/check-skills-sync.mjs +25 -0
  51. package/scripts/compare-eval-summary.mjs +47 -0
  52. package/scripts/postinstall.mjs +15 -15
  53. package/scripts/prepare-release.sh +97 -0
  54. package/scripts/skills-libretto.mjs +103 -0
  55. package/scripts/summarize-evals.mjs +135 -0
  56. package/scripts/sync-skills.mjs +12 -0
  57. package/skills/libretto/SKILL.md +113 -49
  58. package/skills/libretto/references/code-generation-rules.md +208 -0
  59. package/skills/libretto/references/configuration-file-reference.md +53 -0
  60. package/skills/libretto/references/site-security-review.md +143 -0
  61. package/src/cli/cli.ts +23 -110
  62. package/src/cli/commands/browser.ts +94 -70
  63. package/src/cli/commands/execution.ts +233 -102
  64. package/src/cli/commands/init.ts +32 -9
  65. package/src/cli/commands/logs.ts +7 -7
  66. package/src/cli/commands/shared.ts +36 -37
  67. package/src/cli/commands/snapshot.ts +44 -59
  68. package/src/cli/core/ai-config.ts +12 -3
  69. package/src/cli/core/api-snapshot-analyzer.ts +17 -6
  70. package/src/cli/core/browser.ts +178 -41
  71. package/src/cli/core/context.ts +7 -2
  72. package/src/cli/core/session-telemetry.ts +19 -8
  73. package/src/cli/core/session.ts +21 -7
  74. package/src/cli/core/snapshot-analyzer.ts +26 -46
  75. package/src/cli/core/snapshot-api-config.ts +170 -175
  76. package/src/cli/core/telemetry.ts +16 -3
  77. package/src/cli/framework/simple-cli.ts +144 -77
  78. package/src/cli/router.ts +13 -21
  79. package/src/cli/workers/run-integration-runtime.ts +36 -9
  80. package/src/cli/workers/run-integration-worker-protocol.ts +2 -0
  81. package/src/cli/workers/run-integration-worker.ts +1 -4
  82. package/src/index.ts +73 -66
  83. package/src/runtime/download/download.ts +62 -58
  84. package/src/runtime/download/index.ts +5 -5
  85. package/src/runtime/extract/extract.ts +71 -61
  86. package/src/runtime/network/index.ts +3 -3
  87. package/src/runtime/network/network.ts +99 -93
  88. package/src/runtime/recovery/agent.ts +217 -212
  89. package/src/runtime/recovery/errors.ts +107 -104
  90. package/src/runtime/recovery/index.ts +3 -3
  91. package/src/runtime/recovery/recovery.ts +38 -35
  92. package/src/shared/condense-dom/condense-dom.ts +15 -18
  93. package/src/shared/config/config.ts +0 -19
  94. package/src/shared/config/index.ts +0 -5
  95. package/src/shared/debug/pause.ts +57 -51
  96. package/src/shared/instrumentation/errors.ts +64 -62
  97. package/src/shared/instrumentation/index.ts +5 -5
  98. package/src/shared/instrumentation/instrument.ts +339 -209
  99. package/src/shared/llm/ai-sdk-adapter.ts +58 -55
  100. package/src/shared/llm/client.ts +181 -174
  101. package/src/shared/llm/types.ts +39 -39
  102. package/src/shared/logger/index.ts +11 -4
  103. package/src/shared/logger/logger.ts +312 -306
  104. package/src/shared/logger/sinks.ts +118 -114
  105. package/src/shared/paths/paths.ts +50 -49
  106. package/src/shared/paths/repo-root.ts +17 -17
  107. package/src/shared/run/api.ts +5 -1
  108. package/src/shared/run/browser.ts +12 -3
  109. package/src/shared/state/index.ts +9 -9
  110. package/src/shared/state/session-state.ts +46 -43
  111. package/src/shared/visualization/ghost-cursor.ts +161 -148
  112. package/src/shared/visualization/highlight.ts +89 -86
  113. package/src/shared/visualization/index.ts +13 -13
  114. package/src/shared/workflow/workflow.ts +19 -25
  115. package/skills/libretto/references/reverse-engineering-network-requests.md +0 -39
  116. package/skills/libretto/references/user-action-log.md +0 -31
@@ -3,142 +3,146 @@ import * as path from "node:path";
3
3
  import type { LoggerSink } from "./logger.js";
4
4
 
5
5
  export function createFileLogSink({
6
- filePath,
6
+ filePath,
7
7
  }: {
8
- filePath: string;
8
+ filePath: string;
9
9
  }): LoggerSink {
10
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
10
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
11
11
 
12
- const writeStream = fs.createWriteStream(filePath, { flags: "a" });
12
+ const writeStream = fs.createWriteStream(filePath, { flags: "a" });
13
13
 
14
- return {
15
- write: ({ id, scope, level, event, data, options }) => {
16
- if (writeStream.destroyed || writeStream.writableEnded) {
17
- return;
18
- }
19
- const timestamp = options?.timestamp || new Date();
14
+ return {
15
+ write: ({ id, scope, level, event, data, options }) => {
16
+ if (writeStream.destroyed || writeStream.writableEnded) {
17
+ return;
18
+ }
19
+ const timestamp = options?.timestamp || new Date();
20
20
 
21
- const logEntry = {
22
- timestamp: timestamp.toISOString(),
23
- id,
24
- level,
25
- scope,
26
- event,
27
- data,
28
- };
21
+ const logEntry = {
22
+ timestamp: timestamp.toISOString(),
23
+ id,
24
+ level,
25
+ scope,
26
+ event,
27
+ data,
28
+ };
29
29
 
30
- const jsonLine = JSON.stringify(logEntry) + "\n";
30
+ const jsonLine = JSON.stringify(logEntry) + "\n";
31
31
 
32
- try {
33
- writeStream.write(jsonLine, (error) => {
34
- if (error) {
35
- console.error("Failed to write to log file:", error);
36
- console[level]({ id, scope, event, data, timestamp });
37
- }
38
- });
39
- } catch (error) {
40
- console.error("Failed to write to log file:", error);
41
- console[level]({ id, scope, event, data, timestamp });
42
- }
43
- },
44
- flush: () =>
45
- new Promise<void>((resolve, reject) => {
46
- if (!writeStream.writable || writeStream.writableEnded || writeStream.destroyed) {
47
- resolve();
48
- return;
49
- }
50
- writeStream.write("", (error) => {
51
- if (error) {
52
- reject(error);
53
- } else {
54
- resolve();
55
- }
56
- });
57
- }),
58
- close: () =>
59
- new Promise<void>((resolve) => {
60
- if (writeStream.destroyed || writeStream.closed) {
61
- resolve();
62
- return;
63
- }
64
- let settled = false;
65
- const done = () => {
66
- if (settled) return;
67
- settled = true;
68
- resolve();
69
- };
70
- writeStream.once("finish", done);
71
- writeStream.once("close", done);
72
- writeStream.once("error", done);
73
- try {
74
- writeStream.end();
75
- } catch {
76
- done();
77
- }
78
- }),
79
- };
32
+ try {
33
+ writeStream.write(jsonLine, (error) => {
34
+ if (error) {
35
+ console.error("Failed to write to log file:", error);
36
+ console[level]({ id, scope, event, data, timestamp });
37
+ }
38
+ });
39
+ } catch (error) {
40
+ console.error("Failed to write to log file:", error);
41
+ console[level]({ id, scope, event, data, timestamp });
42
+ }
43
+ },
44
+ flush: () =>
45
+ new Promise<void>((resolve, reject) => {
46
+ if (
47
+ !writeStream.writable ||
48
+ writeStream.writableEnded ||
49
+ writeStream.destroyed
50
+ ) {
51
+ resolve();
52
+ return;
53
+ }
54
+ writeStream.write("", (error) => {
55
+ if (error) {
56
+ reject(error);
57
+ } else {
58
+ resolve();
59
+ }
60
+ });
61
+ }),
62
+ close: () =>
63
+ new Promise<void>((resolve) => {
64
+ if (writeStream.destroyed || writeStream.closed) {
65
+ resolve();
66
+ return;
67
+ }
68
+ let settled = false;
69
+ const done = () => {
70
+ if (settled) return;
71
+ settled = true;
72
+ resolve();
73
+ };
74
+ writeStream.once("finish", done);
75
+ writeStream.once("close", done);
76
+ writeStream.once("error", done);
77
+ try {
78
+ writeStream.end();
79
+ } catch {
80
+ done();
81
+ }
82
+ }),
83
+ };
80
84
  }
81
85
 
82
86
  // ANSI color codes
83
87
  const colors = {
84
- reset: "\x1b[0m",
85
- gray: "\x1b[90m",
86
- red: "\x1b[31m",
87
- yellow: "\x1b[33m",
88
- blue: "\x1b[34m",
89
- cyan: "\x1b[36m",
88
+ reset: "\x1b[0m",
89
+ gray: "\x1b[90m",
90
+ red: "\x1b[31m",
91
+ yellow: "\x1b[33m",
92
+ blue: "\x1b[34m",
93
+ cyan: "\x1b[36m",
90
94
  };
91
95
 
92
96
  function formatTimestamp(date: Date): string {
93
- return date.toISOString().replace("T", " ").replace("Z", "");
97
+ return date.toISOString().replace("T", " ").replace("Z", "");
94
98
  }
95
99
 
96
100
  export const prettyConsoleSink: LoggerSink = {
97
- write: ({ scope, level, event, data, options }) => {
98
- const timestamp = `${colors.gray}${formatTimestamp(options?.timestamp || new Date())}${colors.reset}`;
99
- const levelColor =
100
- level === "error"
101
- ? colors.red
102
- : level === "warn"
103
- ? colors.yellow
104
- : colors.blue;
105
- const coloredScope = scope ? `${colors.cyan}[${scope}]${colors.reset}` : "";
101
+ write: ({ scope, level, event, data, options }) => {
102
+ const timestamp = `${colors.gray}${formatTimestamp(options?.timestamp || new Date())}${colors.reset}`;
103
+ const levelColor =
104
+ level === "error"
105
+ ? colors.red
106
+ : level === "warn"
107
+ ? colors.yellow
108
+ : colors.blue;
109
+ const coloredScope = scope ? `${colors.cyan}[${scope}]${colors.reset}` : "";
106
110
 
107
- const logPrefix = `${timestamp} ${levelColor}${level.toUpperCase()}${colors.reset} ${coloredScope} ${event}`;
111
+ const logPrefix = `${timestamp} ${levelColor}${level.toUpperCase()}${colors.reset} ${coloredScope} ${event}`;
108
112
 
109
- if (level === "error" && data.error) {
110
- const { error, ...otherData } = data;
111
- console.error(logPrefix);
112
- if (error.stack) {
113
- console.error(` ${error.stack}`);
114
- } else if (error.type && error.message) {
115
- console.error(` ${error.type}: ${error.message}`);
116
- }
117
- if (Object.keys(otherData).length > 0) {
118
- console.error(JSON.stringify(otherData, null, 2));
119
- }
120
- } else {
121
- console[level](logPrefix);
122
- if (Object.keys(data).length > 0) {
123
- console[level](JSON.stringify(data, null, 2));
124
- }
125
- }
126
- },
113
+ if (level === "error" && data.error) {
114
+ const { error, ...otherData } = data;
115
+ console.error(logPrefix);
116
+ if (error.stack) {
117
+ console.error(` ${error.stack}`);
118
+ } else if (error.type && error.message) {
119
+ console.error(` ${error.type}: ${error.message}`);
120
+ }
121
+ if (Object.keys(otherData).length > 0) {
122
+ console.error(JSON.stringify(otherData, null, 2));
123
+ }
124
+ } else {
125
+ console[level](logPrefix);
126
+ if (Object.keys(data).length > 0) {
127
+ console[level](JSON.stringify(data, null, 2));
128
+ }
129
+ }
130
+ },
127
131
  };
128
132
 
129
133
  export const jsonlConsoleSink: LoggerSink = {
130
- write: ({ id, scope, level, event, data, options }) => {
131
- const timestamp = options?.timestamp || new Date();
134
+ write: ({ id, scope, level, event, data, options }) => {
135
+ const timestamp = options?.timestamp || new Date();
132
136
 
133
- const logEntry = {
134
- timestamp: timestamp.toISOString(),
135
- id,
136
- level,
137
- scope: scope || undefined,
138
- event,
139
- data,
140
- };
137
+ const logEntry = {
138
+ timestamp: timestamp.toISOString(),
139
+ id,
140
+ level,
141
+ scope: scope || undefined,
142
+ event,
143
+ data,
144
+ };
141
145
 
142
- console.log(JSON.stringify(logEntry));
143
- },
146
+ console.log(JSON.stringify(logEntry));
147
+ },
144
148
  };
@@ -11,99 +11,100 @@ const PAUSED_SIGNAL_SUFFIX = "paused";
11
11
  const RESUME_SIGNAL_SUFFIX = "resume";
12
12
 
13
13
  function getLibrettoRoot(cwd: string = process.cwd()): string {
14
- return join(resolveLibrettoRepoRoot(cwd), LIBRETTO_DIRNAME);
14
+ return join(resolveLibrettoRepoRoot(cwd), LIBRETTO_DIRNAME);
15
15
  }
16
16
 
17
17
  function getLibrettoSessionsDir(cwd: string = process.cwd()): string {
18
- return join(getLibrettoRoot(cwd), LIBRETTO_SESSIONS_DIRNAME);
18
+ return join(getLibrettoRoot(cwd), LIBRETTO_SESSIONS_DIRNAME);
19
19
  }
20
20
 
21
21
  function getLibrettoSessionDir(
22
- sessionName: string,
23
- cwd: string = process.cwd(),
22
+ sessionName: string,
23
+ cwd: string = process.cwd(),
24
24
  ): string {
25
- return join(getLibrettoSessionsDir(cwd), sessionName);
25
+ return join(getLibrettoSessionsDir(cwd), sessionName);
26
26
  }
27
27
 
28
28
  function getLibrettoSessionStatePath(
29
- sessionName: string,
30
- cwd: string = process.cwd(),
29
+ sessionName: string,
30
+ cwd: string = process.cwd(),
31
31
  ): string {
32
- return join(getLibrettoSessionDir(sessionName, cwd), SESSION_STATE_FILENAME);
32
+ return join(getLibrettoSessionDir(sessionName, cwd), SESSION_STATE_FILENAME);
33
33
  }
34
34
 
35
35
  export function getLibrettoPauseSignalDir(
36
- sessionName: string,
37
- cwd: string = process.cwd(),
36
+ sessionName: string,
37
+ cwd: string = process.cwd(),
38
38
  ): string {
39
- return getLibrettoSessionDir(sessionName, cwd);
39
+ return getLibrettoSessionDir(sessionName, cwd);
40
40
  }
41
41
 
42
42
  function getLibrettoRunnerLogDir(
43
- sessionName: string,
44
- cwd: string = process.cwd(),
43
+ sessionName: string,
44
+ cwd: string = process.cwd(),
45
45
  ): string {
46
- return join(getLibrettoSessionDir(sessionName, cwd), RUNNER_LOG_DIRNAME);
46
+ return join(getLibrettoSessionDir(sessionName, cwd), RUNNER_LOG_DIRNAME);
47
47
  }
48
48
 
49
49
  export function getRunnerLogPathForDir(logDir: string): string {
50
- return join(logDir, RUNNER_LOG_FILENAME);
50
+ return join(logDir, RUNNER_LOG_FILENAME);
51
51
  }
52
52
 
53
53
  export function getPauseSignalPathForDir(
54
- signalDir: string,
55
- sessionName: string,
56
- signal: "paused" | "resume",
54
+ signalDir: string,
55
+ sessionName: string,
56
+ signal: "paused" | "resume",
57
57
  ): string {
58
- const suffix = signal === "paused" ? PAUSED_SIGNAL_SUFFIX : RESUME_SIGNAL_SUFFIX;
59
- return join(signalDir, `${sessionName}.${suffix}`);
58
+ const suffix =
59
+ signal === "paused" ? PAUSED_SIGNAL_SUFFIX : RESUME_SIGNAL_SUFFIX;
60
+ return join(signalDir, `${sessionName}.${suffix}`);
60
61
  }
61
62
 
62
63
  export function getLibrettoPausedSignalPath(
63
- sessionName: string,
64
- cwd: string = process.cwd(),
64
+ sessionName: string,
65
+ cwd: string = process.cwd(),
65
66
  ): string {
66
- return getPauseSignalPathForDir(
67
- getLibrettoPauseSignalDir(sessionName, cwd),
68
- sessionName,
69
- "paused",
70
- );
67
+ return getPauseSignalPathForDir(
68
+ getLibrettoPauseSignalDir(sessionName, cwd),
69
+ sessionName,
70
+ "paused",
71
+ );
71
72
  }
72
73
 
73
74
  export function getLibrettoResumeSignalPath(
74
- sessionName: string,
75
- cwd: string = process.cwd(),
75
+ sessionName: string,
76
+ cwd: string = process.cwd(),
76
77
  ): string {
77
- return getPauseSignalPathForDir(
78
- getLibrettoPauseSignalDir(sessionName, cwd),
79
- sessionName,
80
- "resume",
81
- );
78
+ return getPauseSignalPathForDir(
79
+ getLibrettoPauseSignalDir(sessionName, cwd),
80
+ sessionName,
81
+ "resume",
82
+ );
82
83
  }
83
84
 
84
85
  export function ensureLibrettoSessionStatePath(
85
- sessionName: string,
86
- cwd: string = process.cwd(),
86
+ sessionName: string,
87
+ cwd: string = process.cwd(),
87
88
  ): string {
88
- const filePath = getLibrettoSessionStatePath(sessionName, cwd);
89
- mkdirSync(dirname(filePath), { recursive: true });
90
- return filePath;
89
+ const filePath = getLibrettoSessionStatePath(sessionName, cwd);
90
+ mkdirSync(dirname(filePath), { recursive: true });
91
+ return filePath;
91
92
  }
92
93
 
93
94
  export function ensureLibrettoPauseSignalDir(
94
- sessionName: string,
95
- cwd: string = process.cwd(),
95
+ sessionName: string,
96
+ cwd: string = process.cwd(),
96
97
  ): string {
97
- const dir = getLibrettoPauseSignalDir(sessionName, cwd);
98
- mkdirSync(dir, { recursive: true });
99
- return dir;
98
+ const dir = getLibrettoPauseSignalDir(sessionName, cwd);
99
+ mkdirSync(dir, { recursive: true });
100
+ return dir;
100
101
  }
101
102
 
102
103
  export function ensureLibrettoRunnerLogDir(
103
- sessionName: string,
104
- cwd: string = process.cwd(),
104
+ sessionName: string,
105
+ cwd: string = process.cwd(),
105
106
  ): string {
106
- const dir = getLibrettoRunnerLogDir(sessionName, cwd);
107
- mkdirSync(dir, { recursive: true });
108
- return dir;
107
+ const dir = getLibrettoRunnerLogDir(sessionName, cwd);
108
+ mkdirSync(dir, { recursive: true });
109
+ return dir;
109
110
  }
@@ -4,24 +4,24 @@ import { resolve } from "node:path";
4
4
  const repoRootCache = new Map<string, string>();
5
5
 
6
6
  export function resolveLibrettoRepoRoot(cwd: string = process.cwd()): string {
7
- const override = process.env.LIBRETTO_REPO_ROOT?.trim();
8
- if (override) {
9
- return resolve(override);
10
- }
7
+ const override = process.env.LIBRETTO_REPO_ROOT?.trim();
8
+ if (override) {
9
+ return resolve(override);
10
+ }
11
11
 
12
- const normalizedCwd = resolve(cwd);
13
- const cached = repoRootCache.get(normalizedCwd);
14
- if (cached) {
15
- return cached;
16
- }
12
+ const normalizedCwd = resolve(cwd);
13
+ const cached = repoRootCache.get(normalizedCwd);
14
+ if (cached) {
15
+ return cached;
16
+ }
17
17
 
18
- const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
19
- cwd: normalizedCwd,
20
- encoding: "utf-8",
21
- });
18
+ const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
19
+ cwd: normalizedCwd,
20
+ encoding: "utf-8",
21
+ });
22
22
 
23
- const repoRoot =
24
- result.status === 0 && result.stdout ? result.stdout.trim() : normalizedCwd;
25
- repoRootCache.set(normalizedCwd, repoRoot);
26
- return repoRoot;
23
+ const repoRoot =
24
+ result.status === 0 && result.stdout ? result.stdout.trim() : normalizedCwd;
25
+ repoRootCache.set(normalizedCwd, repoRoot);
26
+ return repoRoot;
27
27
  }
@@ -1,2 +1,6 @@
1
1
  // --- Browser ---
2
- export { launchBrowser, type LaunchBrowserArgs, type BrowserSession } from "./browser.js";
2
+ export {
3
+ launchBrowser,
4
+ type LaunchBrowserArgs,
5
+ type BrowserSession,
6
+ } from "./browser.js";
@@ -1,8 +1,16 @@
1
- import { chromium, type Browser, type BrowserContext, type Page } from "playwright";
1
+ import {
2
+ chromium,
3
+ type Browser,
4
+ type BrowserContext,
5
+ type Page,
6
+ } from "playwright";
2
7
  import { createServer } from "node:net";
3
8
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
9
  import { ensureLibrettoSessionStatePath } from "../paths/paths.js";
5
- import { SESSION_STATE_VERSION, SessionStateFileSchema } from "../state/session-state.js";
10
+ import {
11
+ SESSION_STATE_VERSION,
12
+ SessionStateFileSchema,
13
+ } from "../state/session-state.js";
6
14
 
7
15
  async function pickFreePort(): Promise<number> {
8
16
  return await new Promise((resolve, reject) => {
@@ -65,7 +73,8 @@ export async function launchBrowser({
65
73
  ? (JSON.parse(readFileSync(metadataPath, "utf-8")) as unknown)
66
74
  : undefined;
67
75
 
68
- const parsedExistingState = SessionStateFileSchema.safeParse(existingStateRaw);
76
+ const parsedExistingState =
77
+ SessionStateFileSchema.safeParse(existingStateRaw);
69
78
 
70
79
  writeFileSync(
71
80
  metadataPath,
@@ -1,11 +1,11 @@
1
1
  export {
2
- SESSION_STATE_VERSION,
3
- SessionStatusSchema,
4
- SessionStateFileSchema,
5
- parseSessionStateData,
6
- parseSessionStateContent,
7
- serializeSessionState,
8
- type SessionStatus,
9
- type SessionState,
10
- type SessionStateFile,
2
+ SESSION_STATE_VERSION,
3
+ SessionStatusSchema,
4
+ SessionStateFileSchema,
5
+ parseSessionStateData,
6
+ parseSessionStateContent,
7
+ serializeSessionState,
8
+ type SessionStatus,
9
+ type SessionState,
10
+ type SessionStateFile,
11
11
  } from "./session-state.js";
@@ -3,25 +3,26 @@ import { z } from "zod";
3
3
  export const SESSION_STATE_VERSION = 1;
4
4
 
5
5
  export const SessionStatusSchema = z.enum([
6
- "active",
7
- "paused",
8
- "completed",
9
- "failed",
10
- "exited",
6
+ "active",
7
+ "paused",
8
+ "completed",
9
+ "failed",
10
+ "exited",
11
11
  ]);
12
12
  export const SessionViewportSchema = z.object({
13
- width: z.number().int().min(1),
14
- height: z.number().int().min(1),
13
+ width: z.number().int().min(1),
14
+ height: z.number().int().min(1),
15
15
  });
16
16
 
17
17
  export const SessionStateFileSchema = z.object({
18
- version: z.literal(SESSION_STATE_VERSION),
19
- port: z.number().int().min(0).max(65535),
20
- pid: z.number().int(),
21
- session: z.string().min(1),
22
- startedAt: z.string().datetime({ offset: true }),
23
- status: SessionStatusSchema.optional(),
24
- viewport: SessionViewportSchema.optional(),
18
+ version: z.literal(SESSION_STATE_VERSION),
19
+ port: z.number().int().min(0).max(65535),
20
+ pid: z.number().int().optional(),
21
+ cdpEndpoint: z.string().url().optional(),
22
+ session: z.string().min(1),
23
+ startedAt: z.string().datetime({ offset: true }),
24
+ status: SessionStatusSchema.optional(),
25
+ viewport: SessionViewportSchema.optional(),
25
26
  });
26
27
 
27
28
  export type SessionStatus = z.infer<typeof SessionStatusSchema>;
@@ -29,46 +30,48 @@ export type SessionStateFile = z.infer<typeof SessionStateFileSchema>;
29
30
  export type SessionState = Omit<SessionStateFile, "version">;
30
31
 
31
32
  function formatIssues(error: z.ZodError): string {
32
- return error.issues
33
- .map((issue) => {
34
- const path = issue.path.join(".") || "root";
35
- return `${path}: ${issue.message}`;
36
- })
37
- .join("; ");
33
+ return error.issues
34
+ .map((issue) => {
35
+ const path = issue.path.join(".") || "root";
36
+ return `${path}: ${issue.message}`;
37
+ })
38
+ .join("; ");
38
39
  }
39
40
 
40
41
  export function parseSessionStateData(
41
- rawState: unknown,
42
- source: string,
42
+ rawState: unknown,
43
+ source: string,
43
44
  ): SessionState {
44
- const parsed = SessionStateFileSchema.safeParse(rawState);
45
- if (!parsed.success) {
46
- throw new Error(`Session state at ${source} is invalid: ${formatIssues(parsed.error)}`);
47
- }
45
+ const parsed = SessionStateFileSchema.safeParse(rawState);
46
+ if (!parsed.success) {
47
+ throw new Error(
48
+ `Session state at ${source} is invalid: ${formatIssues(parsed.error)}`,
49
+ );
50
+ }
48
51
 
49
- const { version: _version, ...state } = parsed.data;
50
- return state;
52
+ const { version: _version, ...state } = parsed.data;
53
+ return state;
51
54
  }
52
55
 
53
56
  export function parseSessionStateContent(
54
- content: string,
55
- source: string,
57
+ content: string,
58
+ source: string,
56
59
  ): SessionState {
57
- let rawState: unknown;
58
- try {
59
- rawState = JSON.parse(content);
60
- } catch (error) {
61
- throw new Error(
62
- `Session state at ${source} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
63
- );
64
- }
60
+ let rawState: unknown;
61
+ try {
62
+ rawState = JSON.parse(content);
63
+ } catch (error) {
64
+ throw new Error(
65
+ `Session state at ${source} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
66
+ );
67
+ }
65
68
 
66
- return parseSessionStateData(rawState, source);
69
+ return parseSessionStateData(rawState, source);
67
70
  }
68
71
 
69
72
  export function serializeSessionState(state: SessionState): SessionStateFile {
70
- return SessionStateFileSchema.parse({
71
- version: SESSION_STATE_VERSION,
72
- ...state,
73
- });
73
+ return SessionStateFileSchema.parse({
74
+ version: SESSION_STATE_VERSION,
75
+ ...state,
76
+ });
74
77
  }