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
@@ -1,191 +1,188 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join, resolve } from "node:path";
3
- import {
4
- type AiConfig,
5
- readAiConfig,
6
- } from "./ai-config.js";
3
+ import { type AiConfig, readAiConfig } from "./ai-config.js";
7
4
  import { LIBRETTO_CONFIG_PATH, REPO_ROOT } from "./context.js";
8
5
  import {
9
- hasProviderCredentials,
10
- parseModel,
11
- type Provider,
6
+ hasProviderCredentials,
7
+ parseModel,
8
+ type Provider,
12
9
  } from "../../shared/llm/client.js";
13
10
 
14
11
  const DEFAULT_SNAPSHOT_MODELS = {
15
- openai: "openai/gpt-5.4",
16
- anthropic: "anthropic/claude-sonnet-4-6",
17
- google: "google/gemini-3-flash-preview",
18
- vertex: "vertex/gemini-2.5-pro",
12
+ openai: "openai/gpt-5.4",
13
+ anthropic: "anthropic/claude-sonnet-4-6",
14
+ google: "google/gemini-3-flash-preview",
15
+ vertex: "vertex/gemini-2.5-pro",
19
16
  } as const satisfies Record<Provider, string>;
20
17
 
21
18
  export type SnapshotApiModelSelection = {
22
- model: string;
23
- provider: Provider;
24
- source:
25
- | "config"
26
- | "env:auto-openai"
27
- | "env:auto-anthropic"
28
- | "env:auto-google"
29
- | "env:auto-vertex";
19
+ model: string;
20
+ provider: Provider;
21
+ source:
22
+ | "config"
23
+ | "env:auto-openai"
24
+ | "env:auto-anthropic"
25
+ | "env:auto-google"
26
+ | "env:auto-vertex";
30
27
  };
31
28
 
32
29
  export class SnapshotApiUnavailableError extends Error {
33
- constructor(message: string) {
34
- super(message);
35
- this.name = "SnapshotApiUnavailableError";
36
- }
30
+ constructor(message: string) {
31
+ super(message);
32
+ this.name = "SnapshotApiUnavailableError";
33
+ }
37
34
  }
38
35
 
39
36
  function providerSetupSentence(provider: Provider): string {
40
- switch (provider) {
41
- case "openai":
42
- return "Add OPENAI_API_KEY to .env or as a shell environment variable.";
43
- case "anthropic":
44
- return "Add ANTHROPIC_API_KEY to .env or as a shell environment variable.";
45
- case "google":
46
- return "Add GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY to .env or as a shell environment variable.";
47
- case "vertex":
48
- return "Add GOOGLE_CLOUD_PROJECT or GCLOUD_PROJECT to .env or as a shell environment variable, and make sure application default credentials are configured.";
49
- }
37
+ switch (provider) {
38
+ case "openai":
39
+ return "Add OPENAI_API_KEY to .env or as a shell environment variable.";
40
+ case "anthropic":
41
+ return "Add ANTHROPIC_API_KEY to .env or as a shell environment variable.";
42
+ case "google":
43
+ return "Add GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY to .env or as a shell environment variable.";
44
+ case "vertex":
45
+ return "Add GOOGLE_CLOUD_PROJECT or GCLOUD_PROJECT to .env or as a shell environment variable, and make sure application default credentials are configured.";
46
+ }
50
47
  }
51
48
 
52
49
  function defaultModelCommandLine(): string {
53
- return "npx libretto ai configure openai | anthropic | gemini | vertex";
50
+ return "npx libretto ai configure openai | anthropic | gemini | vertex";
54
51
  }
55
52
 
56
53
  function providerMissingCredentialSummary(provider: Provider): string {
57
- switch (provider) {
58
- case "openai":
59
- return "OPENAI_API_KEY is missing";
60
- case "anthropic":
61
- return "ANTHROPIC_API_KEY is missing";
62
- case "google":
63
- return "GEMINI_API_KEY and GOOGLE_GENERATIVE_AI_API_KEY are missing";
64
- case "vertex":
65
- return "GOOGLE_CLOUD_PROJECT and GCLOUD_PROJECT are missing";
66
- }
54
+ switch (provider) {
55
+ case "openai":
56
+ return "OPENAI_API_KEY is missing";
57
+ case "anthropic":
58
+ return "ANTHROPIC_API_KEY is missing";
59
+ case "google":
60
+ return "GEMINI_API_KEY and GOOGLE_GENERATIVE_AI_API_KEY are missing";
61
+ case "vertex":
62
+ return "GOOGLE_CLOUD_PROJECT and GCLOUD_PROJECT are missing";
63
+ }
67
64
  }
68
65
 
69
66
  function noSnapshotApiConfiguredMessage(): string {
70
- return [
71
- "Failed to analyze snapshot because no snapshot analyzer is configured.",
72
- `Add OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY, or GOOGLE_CLOUD_PROJECT to .env or as a shell environment variable, or choose a default model with \`${defaultModelCommandLine()}\`.`,
73
- "For more info, run `npx libretto init`.",
74
- ].join(" ");
67
+ return [
68
+ "Failed to analyze snapshot because no snapshot analyzer is configured.",
69
+ `Add OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY or GOOGLE_GENERATIVE_AI_API_KEY, or GOOGLE_CLOUD_PROJECT to .env or as a shell environment variable, or choose a default model with \`${defaultModelCommandLine()}\`.`,
70
+ "For more info, run `npx libretto init`.",
71
+ ].join(" ");
75
72
  }
76
73
 
77
74
  function missingProviderSnapshotMessage(
78
- selection: SnapshotApiModelSelection,
75
+ selection: SnapshotApiModelSelection,
79
76
  ): string {
80
- const configuredSource =
81
- selection.source === "config"
82
- ? ` in ${LIBRETTO_CONFIG_PATH}`
83
- : " from process env or .env";
84
- return [
85
- `Failed to analyze snapshot because ${selection.provider} is configured${configuredSource}, but ${providerMissingCredentialSummary(selection.provider)}.`,
86
- providerSetupSentence(selection.provider),
87
- "For more info, run `npx libretto init`.",
88
- ].join(" ");
77
+ const configuredSource =
78
+ selection.source === "config"
79
+ ? ` in ${LIBRETTO_CONFIG_PATH}`
80
+ : " from process env or .env";
81
+ return [
82
+ `Failed to analyze snapshot because ${selection.provider} is configured${configuredSource}, but ${providerMissingCredentialSummary(selection.provider)}.`,
83
+ providerSetupSentence(selection.provider),
84
+ "For more info, run `npx libretto init`.",
85
+ ].join(" ");
89
86
  }
90
87
 
91
88
  function readWorktreeEnvPath(): string | null {
92
- const gitPath = join(REPO_ROOT, ".git");
93
- if (!existsSync(gitPath)) return null;
94
-
95
- try {
96
- const gitPointer = readFileSync(gitPath, "utf-8").trim();
97
- const match = gitPointer.match(/^gitdir:\s*(.+)$/i);
98
- if (!match?.[1]) return null;
99
- const worktreeGitDir = resolve(REPO_ROOT, match[1].trim());
100
- const commonGitDir = resolve(worktreeGitDir, "..", "..");
101
- return join(dirname(commonGitDir), ".env");
102
- } catch {
103
- return null;
104
- }
89
+ const gitPath = join(REPO_ROOT, ".git");
90
+ if (!existsSync(gitPath)) return null;
91
+
92
+ try {
93
+ const gitPointer = readFileSync(gitPath, "utf-8").trim();
94
+ const match = gitPointer.match(/^gitdir:\s*(.+)$/i);
95
+ if (!match?.[1]) return null;
96
+ const worktreeGitDir = resolve(REPO_ROOT, match[1].trim());
97
+ const commonGitDir = resolve(worktreeGitDir, "..", "..");
98
+ return join(dirname(commonGitDir), ".env");
99
+ } catch {
100
+ return null;
101
+ }
105
102
  }
106
103
 
107
104
  export function loadSnapshotEnv(): void {
108
- if (process.env.LIBRETTO_DISABLE_DOTENV?.trim() === "1") return;
109
-
110
- const envPathCandidates = [
111
- join(REPO_ROOT, ".env"),
112
- readWorktreeEnvPath(),
113
- ].filter((value): value is string => Boolean(value));
114
-
115
- const envPath = envPathCandidates.find((candidate) => existsSync(candidate));
116
- if (!envPath) return;
117
-
118
- for (const line of readFileSync(envPath, "utf-8").split("\n")) {
119
- const parsed = parseDotEnvAssignment(line);
120
- if (!parsed) continue;
121
- if (!(parsed.key in process.env)) {
122
- process.env[parsed.key] = parsed.value;
123
- }
124
- }
105
+ if (process.env.LIBRETTO_DISABLE_DOTENV?.trim() === "1") return;
106
+
107
+ const envPathCandidates = [
108
+ join(REPO_ROOT, ".env"),
109
+ readWorktreeEnvPath(),
110
+ ].filter((value): value is string => Boolean(value));
111
+
112
+ const envPath = envPathCandidates.find((candidate) => existsSync(candidate));
113
+ if (!envPath) return;
114
+
115
+ for (const line of readFileSync(envPath, "utf-8").split("\n")) {
116
+ const parsed = parseDotEnvAssignment(line);
117
+ if (!parsed) continue;
118
+ if (!(parsed.key in process.env)) {
119
+ process.env[parsed.key] = parsed.value;
120
+ }
121
+ }
125
122
  }
126
123
 
127
124
  export function parseDotEnvAssignment(
128
- line: string,
125
+ line: string,
129
126
  ): { key: string; value: string } | null {
130
- const trimmed = line.trim();
131
- if (!trimmed || trimmed.startsWith("#")) return null;
132
-
133
- const withoutExport = trimmed.startsWith("export ")
134
- ? trimmed.slice("export ".length).trimStart()
135
- : trimmed;
136
- const eqIdx = withoutExport.indexOf("=");
137
- if (eqIdx < 1) return null;
138
-
139
- const key = withoutExport.slice(0, eqIdx).trim();
140
- if (!key) return null;
141
-
142
- const rawValue = withoutExport.slice(eqIdx + 1).trimStart();
143
- if (!rawValue) {
144
- return { key, value: "" };
145
- }
146
-
147
- if (rawValue.startsWith('"')) {
148
- const closeIdx = rawValue.indexOf('"', 1);
149
- if (closeIdx > 0) {
150
- return { key, value: rawValue.slice(1, closeIdx) };
151
- }
152
- return { key, value: rawValue.slice(1) };
153
- }
154
-
155
- if (rawValue.startsWith("'")) {
156
- const closeIdx = rawValue.indexOf("'", 1);
157
- if (closeIdx > 0) {
158
- return { key, value: rawValue.slice(1, closeIdx) };
159
- }
160
- return { key, value: rawValue.slice(1) };
161
- }
162
-
163
- const inlineCommentIndex = rawValue.search(/\s#/);
164
- const value =
165
- inlineCommentIndex >= 0
166
- ? rawValue.slice(0, inlineCommentIndex).trimEnd()
167
- : rawValue.trim();
168
- return { key, value };
127
+ const trimmed = line.trim();
128
+ if (!trimmed || trimmed.startsWith("#")) return null;
129
+
130
+ const withoutExport = trimmed.startsWith("export ")
131
+ ? trimmed.slice("export ".length).trimStart()
132
+ : trimmed;
133
+ const eqIdx = withoutExport.indexOf("=");
134
+ if (eqIdx < 1) return null;
135
+
136
+ const key = withoutExport.slice(0, eqIdx).trim();
137
+ if (!key) return null;
138
+
139
+ const rawValue = withoutExport.slice(eqIdx + 1).trimStart();
140
+ if (!rawValue) {
141
+ return { key, value: "" };
142
+ }
143
+
144
+ if (rawValue.startsWith('"')) {
145
+ const closeIdx = rawValue.indexOf('"', 1);
146
+ if (closeIdx > 0) {
147
+ return { key, value: rawValue.slice(1, closeIdx) };
148
+ }
149
+ return { key, value: rawValue.slice(1) };
150
+ }
151
+
152
+ if (rawValue.startsWith("'")) {
153
+ const closeIdx = rawValue.indexOf("'", 1);
154
+ if (closeIdx > 0) {
155
+ return { key, value: rawValue.slice(1, closeIdx) };
156
+ }
157
+ return { key, value: rawValue.slice(1) };
158
+ }
159
+
160
+ const inlineCommentIndex = rawValue.search(/\s#/);
161
+ const value =
162
+ inlineCommentIndex >= 0
163
+ ? rawValue.slice(0, inlineCommentIndex).trimEnd()
164
+ : rawValue.trim();
165
+ return { key, value };
169
166
  }
170
167
 
171
168
  function inferAutoSnapshotModel(): SnapshotApiModelSelection | null {
172
- const providersInPriorityOrder: Provider[] = [
173
- "openai",
174
- "anthropic",
175
- "google",
176
- "vertex",
177
- ];
178
-
179
- for (const provider of providersInPriorityOrder) {
180
- if (!hasProviderCredentials(provider)) continue;
181
- return {
182
- model: DEFAULT_SNAPSHOT_MODELS[provider],
183
- provider,
184
- source: `env:auto-${provider}` as SnapshotApiModelSelection["source"],
185
- };
186
- }
187
-
188
- return null;
169
+ const providersInPriorityOrder: Provider[] = [
170
+ "openai",
171
+ "anthropic",
172
+ "google",
173
+ "vertex",
174
+ ];
175
+
176
+ for (const provider of providersInPriorityOrder) {
177
+ if (!hasProviderCredentials(provider)) continue;
178
+ return {
179
+ model: DEFAULT_SNAPSHOT_MODELS[provider],
180
+ provider,
181
+ source: `env:auto-${provider}` as SnapshotApiModelSelection["source"],
182
+ };
183
+ }
184
+
185
+ return null;
189
186
  }
190
187
 
191
188
  /**
@@ -196,41 +193,39 @@ function inferAutoSnapshotModel(): SnapshotApiModelSelection | null {
196
193
  * 2. Auto-detect from available API credentials in env
197
194
  */
198
195
  export function resolveSnapshotApiModel(
199
- config: AiConfig | null = readAiConfig(),
196
+ config: AiConfig | null = readAiConfig(),
200
197
  ): SnapshotApiModelSelection | null {
201
- loadSnapshotEnv();
202
-
203
- if (config?.model) {
204
- const { provider } = parseModel(config.model);
205
- return {
206
- model: config.model,
207
- provider,
208
- source: "config",
209
- };
210
- }
211
-
212
- return inferAutoSnapshotModel();
198
+ loadSnapshotEnv();
199
+
200
+ if (config?.model) {
201
+ const { provider } = parseModel(config.model);
202
+ return {
203
+ model: config.model,
204
+ provider,
205
+ source: "config",
206
+ };
207
+ }
208
+
209
+ return inferAutoSnapshotModel();
213
210
  }
214
211
 
215
212
  export function resolveSnapshotApiModelOrThrow(
216
- config: AiConfig | null = readAiConfig(),
213
+ config: AiConfig | null = readAiConfig(),
217
214
  ): SnapshotApiModelSelection {
218
- const selection = resolveSnapshotApiModel(config);
219
- if (!selection) {
220
- throw new SnapshotApiUnavailableError(
221
- noSnapshotApiConfiguredMessage(),
222
- );
223
- }
224
-
225
- if (!hasProviderCredentials(selection.provider)) {
226
- throw new SnapshotApiUnavailableError(
227
- missingProviderSnapshotMessage(selection),
228
- );
229
- }
230
-
231
- return selection;
215
+ const selection = resolveSnapshotApiModel(config);
216
+ if (!selection) {
217
+ throw new SnapshotApiUnavailableError(noSnapshotApiConfiguredMessage());
218
+ }
219
+
220
+ if (!hasProviderCredentials(selection.provider)) {
221
+ throw new SnapshotApiUnavailableError(
222
+ missingProviderSnapshotMessage(selection),
223
+ );
224
+ }
225
+
226
+ return selection;
232
227
  }
233
228
 
234
229
  export function isSnapshotApiUnavailableError(error: unknown): boolean {
235
- return error instanceof SnapshotApiUnavailableError;
230
+ return error instanceof SnapshotApiUnavailableError;
236
231
  }
@@ -1,4 +1,9 @@
1
- import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
1
+ import {
2
+ appendFileSync,
3
+ existsSync,
4
+ readFileSync,
5
+ writeFileSync,
6
+ } from "node:fs";
2
7
  import type { Page } from "playwright";
3
8
  import {
4
9
  getSessionActionsLogPath,
@@ -21,7 +26,12 @@ export type NetworkLogEntry = {
21
26
 
22
27
  export function readNetworkLog(
23
28
  session: string,
24
- opts: { last?: number; filter?: string; method?: string; pageId?: string } = {},
29
+ opts: {
30
+ last?: number;
31
+ filter?: string;
32
+ method?: string;
33
+ pageId?: string;
34
+ } = {},
25
35
  ): NetworkLogEntry[] {
26
36
  assertSessionStateExistsOrThrow(session);
27
37
  const logPath = getSessionNetworkLogPath(session);
@@ -99,7 +109,10 @@ export function parentLogAction(
99
109
  ): void {
100
110
  try {
101
111
  const record = { ts: new Date().toISOString(), ...entry };
102
- appendFileSync(getSessionActionsLogPath(session), JSON.stringify(record) + "\n");
112
+ appendFileSync(
113
+ getSessionActionsLogPath(session),
114
+ JSON.stringify(record) + "\n",
115
+ );
103
116
  } catch {}
104
117
  }
105
118