libretto 0.5.0 → 0.5.2

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 (122) hide show
  1. package/README.md +109 -35
  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 +34 -29
  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 +21 -4
  10. package/dist/cli/core/api-snapshot-analyzer.js +15 -5
  11. package/dist/cli/core/browser.js +207 -37
  12. package/dist/cli/core/context.js +4 -1
  13. package/dist/cli/core/session-telemetry.js +434 -174
  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 +20 -4
  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 +17 -69
  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/dom-semantics.d.ts +8 -0
  38. package/dist/shared/dom-semantics.js +69 -0
  39. package/dist/shared/instrumentation/instrument.js +101 -5
  40. package/dist/shared/llm/ai-sdk-adapter.js +3 -1
  41. package/dist/shared/llm/client.js +3 -1
  42. package/dist/shared/logger/index.js +4 -1
  43. package/dist/shared/run/api.js +3 -1
  44. package/dist/shared/run/browser.js +47 -3
  45. package/dist/shared/state/session-state.d.ts +2 -1
  46. package/dist/shared/state/session-state.js +5 -2
  47. package/dist/shared/visualization/ghost-cursor.js +36 -14
  48. package/dist/shared/visualization/highlight.js +9 -6
  49. package/dist/shared/workflow/workflow.d.ts +4 -5
  50. package/dist/shared/workflow/workflow.js +3 -5
  51. package/package.json +6 -2
  52. package/scripts/check-skills-sync.mjs +25 -0
  53. package/scripts/compare-eval-summary.mjs +47 -0
  54. package/scripts/postinstall.mjs +15 -15
  55. package/scripts/prepare-release.sh +97 -0
  56. package/scripts/skills-libretto.mjs +103 -0
  57. package/scripts/summarize-evals.mjs +135 -0
  58. package/scripts/sync-skills.mjs +12 -0
  59. package/skills/libretto/SKILL.md +132 -54
  60. package/skills/libretto/references/action-logs.md +101 -0
  61. package/skills/libretto/references/auth-profiles.md +1 -2
  62. package/skills/libretto/references/code-generation-rules.md +210 -0
  63. package/skills/libretto/references/configuration-file-reference.md +53 -0
  64. package/skills/libretto/references/pages-and-page-targeting.md +1 -1
  65. package/skills/libretto/references/site-security-review.md +143 -0
  66. package/src/cli/cli.ts +23 -110
  67. package/src/cli/commands/browser.ts +94 -70
  68. package/src/cli/commands/execution.ts +233 -102
  69. package/src/cli/commands/init.ts +37 -33
  70. package/src/cli/commands/logs.ts +7 -7
  71. package/src/cli/commands/shared.ts +36 -37
  72. package/src/cli/commands/snapshot.ts +44 -59
  73. package/src/cli/core/ai-config.ts +24 -4
  74. package/src/cli/core/api-snapshot-analyzer.ts +17 -6
  75. package/src/cli/core/browser.ts +260 -49
  76. package/src/cli/core/context.ts +7 -2
  77. package/src/cli/core/session-telemetry.ts +449 -197
  78. package/src/cli/core/session.ts +21 -7
  79. package/src/cli/core/snapshot-analyzer.ts +26 -46
  80. package/src/cli/core/snapshot-api-config.ts +170 -175
  81. package/src/cli/core/telemetry.ts +39 -4
  82. package/src/cli/framework/simple-cli.ts +144 -77
  83. package/src/cli/router.ts +13 -21
  84. package/src/cli/workers/run-integration-runtime.ts +36 -9
  85. package/src/cli/workers/run-integration-worker-protocol.ts +2 -0
  86. package/src/cli/workers/run-integration-worker.ts +1 -4
  87. package/src/index.ts +73 -66
  88. package/src/runtime/download/download.ts +62 -58
  89. package/src/runtime/download/index.ts +5 -5
  90. package/src/runtime/extract/extract.ts +71 -61
  91. package/src/runtime/network/index.ts +3 -3
  92. package/src/runtime/network/network.ts +99 -93
  93. package/src/runtime/recovery/agent.ts +217 -212
  94. package/src/runtime/recovery/errors.ts +107 -104
  95. package/src/runtime/recovery/index.ts +3 -3
  96. package/src/runtime/recovery/recovery.ts +38 -35
  97. package/src/shared/condense-dom/condense-dom.ts +27 -82
  98. package/src/shared/config/config.ts +0 -19
  99. package/src/shared/config/index.ts +0 -5
  100. package/src/shared/debug/pause.ts +57 -51
  101. package/src/shared/dom-semantics.ts +68 -0
  102. package/src/shared/instrumentation/errors.ts +64 -62
  103. package/src/shared/instrumentation/index.ts +5 -5
  104. package/src/shared/instrumentation/instrument.ts +339 -209
  105. package/src/shared/llm/ai-sdk-adapter.ts +58 -55
  106. package/src/shared/llm/client.ts +181 -174
  107. package/src/shared/llm/types.ts +39 -39
  108. package/src/shared/logger/index.ts +11 -4
  109. package/src/shared/logger/logger.ts +312 -306
  110. package/src/shared/logger/sinks.ts +118 -114
  111. package/src/shared/paths/paths.ts +50 -49
  112. package/src/shared/paths/repo-root.ts +17 -17
  113. package/src/shared/run/api.ts +5 -1
  114. package/src/shared/run/browser.ts +65 -3
  115. package/src/shared/state/index.ts +9 -9
  116. package/src/shared/state/session-state.ts +46 -43
  117. package/src/shared/visualization/ghost-cursor.ts +180 -149
  118. package/src/shared/visualization/highlight.ts +89 -86
  119. package/src/shared/visualization/index.ts +13 -13
  120. package/src/shared/workflow/workflow.ts +19 -25
  121. package/skills/libretto/references/reverse-engineering-network-requests.md +0 -39
  122. package/skills/libretto/references/user-action-log.md +0 -31
@@ -1,9 +1,9 @@
1
1
  function generateId(): string {
2
- return Math.random().toString(36).substring(2, 15);
2
+ return Math.random().toString(36).substring(2, 15);
3
3
  }
4
4
 
5
5
  export type LogOptions = {
6
- timestamp?: Date;
6
+ timestamp?: Date;
7
7
  };
8
8
 
9
9
  /**
@@ -12,341 +12,347 @@ export type LogOptions = {
12
12
  * implement withScope, withContext, flush, etc.
13
13
  */
14
14
  export type MinimalLogger = {
15
- info: (event: string, data?: any) => void;
16
- warn: (event: string, data?: any) => void;
17
- error: (event: string, data?: any) => any;
15
+ info: (event: string, data?: any) => void;
16
+ warn: (event: string, data?: any) => void;
17
+ error: (event: string, data?: any) => any;
18
18
  };
19
19
 
20
20
  /** Default console logger used when callers omit the logger option. */
21
21
  export const defaultLogger: MinimalLogger = {
22
- info(event, data) { console.log(`[INFO] ${event}`, data ?? ""); },
23
- warn(event, data) { console.warn(`[WARN] ${event}`, data ?? ""); },
24
- error(event, data) { console.error(`[ERROR] ${event}`, data ?? ""); },
22
+ info(event, data) {
23
+ console.log(`[INFO] ${event}`, data ?? "");
24
+ },
25
+ warn(event, data) {
26
+ console.warn(`[WARN] ${event}`, data ?? "");
27
+ },
28
+ error(event, data) {
29
+ console.error(`[ERROR] ${event}`, data ?? "");
30
+ },
25
31
  };
26
32
 
27
33
  export type LoggerApi = {
28
- log: (
29
- event: string,
30
- data?: Record<string, any>,
31
- options?: LogOptions,
32
- ) => void;
33
- /**
34
- * Logs an error and returns an Error object that can be thrown
35
- *
36
- * either pass in an Error directly as data or as { error: Error, ...other_data }
37
- */
38
- error: (
39
- event: string,
40
- data?: Error | ({ error: Error } & Record<string, any>) | unknown,
41
- options?: LogOptions,
42
- ) => Error;
43
- warn: (
44
- event: string,
45
- data?: Error | ({ error: Error } & Record<string, any>) | unknown,
46
- options?: LogOptions,
47
- ) => void;
48
- info: (
49
- event: string,
50
- data?: Error | ({ error: Error } & Record<string, any>) | unknown,
51
- options?: LogOptions,
52
- ) => void;
53
-
54
- /**
55
- * Context passed in will be attached to all entries in this scope.
56
- */
57
- withScope: (scope: string, context?: Record<string, any>) => LoggerApi;
58
-
59
- /**
60
- * Context passed in will be attached to all entries.
61
- */
62
- withContext: (context: Record<string, any>) => LoggerApi;
63
-
64
- /**
65
- * Flushes all sinks in reverse order (most recently added first).
66
- */
67
- flush: () => Promise<void>;
34
+ log: (
35
+ event: string,
36
+ data?: Record<string, any>,
37
+ options?: LogOptions,
38
+ ) => void;
39
+ /**
40
+ * Logs an error and returns an Error object that can be thrown
41
+ *
42
+ * either pass in an Error directly as data or as { error: Error, ...other_data }
43
+ */
44
+ error: (
45
+ event: string,
46
+ data?: Error | ({ error: Error } & Record<string, any>) | unknown,
47
+ options?: LogOptions,
48
+ ) => Error;
49
+ warn: (
50
+ event: string,
51
+ data?: Error | ({ error: Error } & Record<string, any>) | unknown,
52
+ options?: LogOptions,
53
+ ) => void;
54
+ info: (
55
+ event: string,
56
+ data?: Error | ({ error: Error } & Record<string, any>) | unknown,
57
+ options?: LogOptions,
58
+ ) => void;
59
+
60
+ /**
61
+ * Context passed in will be attached to all entries in this scope.
62
+ */
63
+ withScope: (scope: string, context?: Record<string, any>) => LoggerApi;
64
+
65
+ /**
66
+ * Context passed in will be attached to all entries.
67
+ */
68
+ withContext: (context: Record<string, any>) => LoggerApi;
69
+
70
+ /**
71
+ * Flushes all sinks in reverse order (most recently added first).
72
+ */
73
+ flush: () => Promise<void>;
68
74
  };
69
75
 
70
76
  export type LoggerSink = {
71
- write: (args: {
72
- id: string;
73
- scope: string;
74
- level: "log" | "error" | "warn" | "info";
75
- event: string;
76
- data: Record<string, any>;
77
- options?: LogOptions;
78
- }) => void;
79
- flush?: () => Promise<void>;
80
- close?: () => Promise<void>;
77
+ write: (args: {
78
+ id: string;
79
+ scope: string;
80
+ level: "log" | "error" | "warn" | "info";
81
+ event: string;
82
+ data: Record<string, any>;
83
+ options?: LogOptions;
84
+ }) => void;
85
+ flush?: () => Promise<void>;
86
+ close?: () => Promise<void>;
81
87
  };
82
88
 
83
89
  type SinkLifecycleState = {
84
- closed: boolean;
85
- closing?: Promise<void>;
90
+ closed: boolean;
91
+ closing?: Promise<void>;
86
92
  };
87
93
 
88
94
  const sinkLifecycleState = new WeakMap<LoggerSink, SinkLifecycleState>();
89
95
 
90
96
  function getSinkLifecycleState(sink: LoggerSink): SinkLifecycleState {
91
- const existingState = sinkLifecycleState.get(sink);
92
- if (existingState) {
93
- return existingState;
94
- }
95
-
96
- const initialState: SinkLifecycleState = { closed: false };
97
- sinkLifecycleState.set(sink, initialState);
98
- return initialState;
97
+ const existingState = sinkLifecycleState.get(sink);
98
+ if (existingState) {
99
+ return existingState;
100
+ }
101
+
102
+ const initialState: SinkLifecycleState = { closed: false };
103
+ sinkLifecycleState.set(sink, initialState);
104
+ return initialState;
99
105
  }
100
106
 
101
107
  function isSinkClosedOrClosing(sink: LoggerSink): boolean {
102
- const state = sinkLifecycleState.get(sink);
103
- return Boolean(state?.closed || state?.closing);
108
+ const state = sinkLifecycleState.get(sink);
109
+ return Boolean(state?.closed || state?.closing);
104
110
  }
105
111
 
106
112
  async function closeSinkOnce(sink: LoggerSink): Promise<void> {
107
- if (!sink.close) {
108
- return;
109
- }
110
-
111
- const state = getSinkLifecycleState(sink);
112
- if (state.closed) {
113
- return;
114
- }
115
-
116
- if (state.closing) {
117
- return state.closing;
118
- }
119
-
120
- state.closing = (async () => {
121
- try {
122
- await sink.close?.();
123
- } catch {
124
- // Ignore close errors - we're likely shutting down
125
- } finally {
126
- state.closed = true;
127
- state.closing = undefined;
128
- }
129
- })();
130
-
131
- return state.closing;
113
+ if (!sink.close) {
114
+ return;
115
+ }
116
+
117
+ const state = getSinkLifecycleState(sink);
118
+ if (state.closed) {
119
+ return;
120
+ }
121
+
122
+ if (state.closing) {
123
+ return state.closing;
124
+ }
125
+
126
+ state.closing = (async () => {
127
+ try {
128
+ await sink.close?.();
129
+ } catch {
130
+ // Ignore close errors - we're likely shutting down
131
+ } finally {
132
+ state.closed = true;
133
+ state.closing = undefined;
134
+ }
135
+ })();
136
+
137
+ return state.closing;
132
138
  }
133
139
 
134
140
  function isObject(value: unknown): value is Record<string, unknown> {
135
- return typeof value === "object" && value !== null;
141
+ return typeof value === "object" && value !== null;
136
142
  }
137
143
 
138
144
  function removeUndefined(data: any): any {
139
- if (typeof data === "object" && data !== null) {
140
- return Object.fromEntries(
141
- Object.entries(data).filter(([_, value]) => value !== undefined),
142
- );
143
- }
144
- return data;
145
+ if (typeof data === "object" && data !== null) {
146
+ return Object.fromEntries(
147
+ Object.entries(data).filter(([_, value]) => value !== undefined),
148
+ );
149
+ }
150
+ return data;
145
151
  }
146
152
 
147
153
  export class Logger implements LoggerApi {
148
- private readonly prefix: string;
149
-
150
- constructor(
151
- private readonly scopes: string[] = [],
152
- private readonly sinks: LoggerSink[] = [],
153
- private readonly scopeData: Record<string, any> = {},
154
- ) {
155
- this.prefix = scopes.join(".");
156
- }
157
-
158
- entry(entry: {
159
- level: "log" | "error" | "warn" | "info";
160
- event: string;
161
- data?: Record<string, any>;
162
- options?: LogOptions;
163
- }) {
164
- this.sinks.forEach((sink) => {
165
- if (isSinkClosedOrClosing(sink)) {
166
- return;
167
- }
168
-
169
- sink.write({
170
- id: generateId(),
171
- scope: this.prefix,
172
- level: entry.level,
173
- event: entry.event,
174
- data: removeUndefined({ ...this.scopeData, ...entry.data }),
175
- options: entry.options,
176
- });
177
- });
178
- }
179
-
180
- log(event: string, data?: Record<string, any>, options?: LogOptions) {
181
- this.entry({ level: "log", event, data, options });
182
- }
183
-
184
- error(
185
- event: string,
186
- dataOrError?: Error | ({ error: Error } & Record<string, any>) | unknown,
187
- options?: LogOptions,
188
- ) {
189
- const data =
190
- dataOrError instanceof Error
191
- ? {
192
- error: {
193
- type: dataOrError.constructor.name,
194
- message: dataOrError.message,
195
- stack: dataOrError.stack || null,
196
- },
197
- }
198
- : isObject(dataOrError) && dataOrError.error instanceof Error
199
- ? {
200
- ...dataOrError,
201
- error: {
202
- type: dataOrError.error.constructor.name,
203
- message: dataOrError.error.message,
204
- stack: dataOrError.error.stack || null,
205
- },
206
- }
207
- : isObject(dataOrError)
208
- ? dataOrError
209
- : dataOrError !== undefined
210
- ? { error: dataOrError }
211
- : undefined;
212
-
213
- this.entry({
214
- level: "error",
215
- event,
216
- data: data as Error | Record<string, any>,
217
- options,
218
- });
219
-
220
- if (dataOrError instanceof Error) {
221
- return dataOrError;
222
- }
223
-
224
- if (isObject(dataOrError) && dataOrError.error instanceof Error) {
225
- return dataOrError.error;
226
- }
227
-
228
- let message = event;
229
- if (data !== undefined) {
230
- try {
231
- message += "\n" + JSON.stringify(data, undefined, 2);
232
- } catch {
233
- message += "\n[Unserializable error data]";
234
- }
235
- }
236
- return new Error(message);
237
- }
238
-
239
- warn(
240
- event: string,
241
- dataOrError?: Error | ({ error: Error } & Record<string, any>) | unknown,
242
- options?: LogOptions,
243
- ) {
244
- const data =
245
- dataOrError instanceof Error
246
- ? {
247
- error: {
248
- type: dataOrError.constructor.name,
249
- message: dataOrError.message,
250
- stack: dataOrError.stack || null,
251
- },
252
- }
253
- : isObject(dataOrError) && dataOrError.error instanceof Error
254
- ? {
255
- ...dataOrError,
256
- error: {
257
- type: dataOrError.error.constructor.name,
258
- message: dataOrError.error.message,
259
- stack: dataOrError.error.stack || null,
260
- },
261
- }
262
- : isObject(dataOrError)
263
- ? dataOrError
264
- : dataOrError !== undefined
265
- ? { error: dataOrError }
266
- : undefined;
267
-
268
- this.entry({
269
- level: "warn",
270
- event,
271
- data: data as Record<string, any>,
272
- options,
273
- });
274
- }
275
-
276
- info(
277
- event: string,
278
- dataOrError?: Error | ({ error: Error } & Record<string, any>) | unknown,
279
- options?: LogOptions,
280
- ) {
281
- const data =
282
- dataOrError instanceof Error
283
- ? {
284
- error: {
285
- type: dataOrError.constructor.name,
286
- message: dataOrError.message,
287
- stack: dataOrError.stack || null,
288
- },
289
- }
290
- : isObject(dataOrError) && dataOrError.error instanceof Error
291
- ? {
292
- ...dataOrError,
293
- error: {
294
- type: dataOrError.error.constructor.name,
295
- message: dataOrError.error.message,
296
- stack: dataOrError.error.stack || null,
297
- },
298
- }
299
- : isObject(dataOrError)
300
- ? dataOrError
301
- : dataOrError !== undefined
302
- ? { error: dataOrError }
303
- : undefined;
304
-
305
- this.entry({
306
- level: "info",
307
- event,
308
- data: data as Record<string, any>,
309
- options,
310
- });
311
- }
312
-
313
- withScope(scope: string, context: Record<string, any> = {}): LoggerApi {
314
- return new Logger([...this.scopes, scope], this.sinks, {
315
- ...this.scopeData,
316
- ...context,
317
- });
318
- }
319
-
320
- withContext(context: Record<string, any>): LoggerApi {
321
- return new Logger(this.scopes, this.sinks, {
322
- ...this.scopeData,
323
- ...context,
324
- });
325
- }
326
-
327
- withSink(sink: LoggerSink): Logger {
328
- return new Logger(this.scopes, [...this.sinks, sink]);
329
- }
330
-
331
- async flush(): Promise<void> {
332
- for (let i = this.sinks.length - 1; i >= 0; i--) {
333
- const sink = this.sinks[i];
334
- if (!sink) continue;
335
- if (isSinkClosedOrClosing(sink)) continue;
336
- try {
337
- await sink.flush?.();
338
- } catch {
339
- // Ignore flush errors - we're likely shutting down
340
- }
341
- }
342
- }
343
-
344
- async close(): Promise<void> {
345
- await this.flush();
346
- for (let i = this.sinks.length - 1; i >= 0; i--) {
347
- const sink = this.sinks[i];
348
- if (!sink) continue;
349
- await closeSinkOnce(sink);
350
- }
351
- }
154
+ private readonly prefix: string;
155
+
156
+ constructor(
157
+ private readonly scopes: string[] = [],
158
+ private readonly sinks: LoggerSink[] = [],
159
+ private readonly scopeData: Record<string, any> = {},
160
+ ) {
161
+ this.prefix = scopes.join(".");
162
+ }
163
+
164
+ entry(entry: {
165
+ level: "log" | "error" | "warn" | "info";
166
+ event: string;
167
+ data?: Record<string, any>;
168
+ options?: LogOptions;
169
+ }) {
170
+ this.sinks.forEach((sink) => {
171
+ if (isSinkClosedOrClosing(sink)) {
172
+ return;
173
+ }
174
+
175
+ sink.write({
176
+ id: generateId(),
177
+ scope: this.prefix,
178
+ level: entry.level,
179
+ event: entry.event,
180
+ data: removeUndefined({ ...this.scopeData, ...entry.data }),
181
+ options: entry.options,
182
+ });
183
+ });
184
+ }
185
+
186
+ log(event: string, data?: Record<string, any>, options?: LogOptions) {
187
+ this.entry({ level: "log", event, data, options });
188
+ }
189
+
190
+ error(
191
+ event: string,
192
+ dataOrError?: Error | ({ error: Error } & Record<string, any>) | unknown,
193
+ options?: LogOptions,
194
+ ) {
195
+ const data =
196
+ dataOrError instanceof Error
197
+ ? {
198
+ error: {
199
+ type: dataOrError.constructor.name,
200
+ message: dataOrError.message,
201
+ stack: dataOrError.stack || null,
202
+ },
203
+ }
204
+ : isObject(dataOrError) && dataOrError.error instanceof Error
205
+ ? {
206
+ ...dataOrError,
207
+ error: {
208
+ type: dataOrError.error.constructor.name,
209
+ message: dataOrError.error.message,
210
+ stack: dataOrError.error.stack || null,
211
+ },
212
+ }
213
+ : isObject(dataOrError)
214
+ ? dataOrError
215
+ : dataOrError !== undefined
216
+ ? { error: dataOrError }
217
+ : undefined;
218
+
219
+ this.entry({
220
+ level: "error",
221
+ event,
222
+ data: data as Error | Record<string, any>,
223
+ options,
224
+ });
225
+
226
+ if (dataOrError instanceof Error) {
227
+ return dataOrError;
228
+ }
229
+
230
+ if (isObject(dataOrError) && dataOrError.error instanceof Error) {
231
+ return dataOrError.error;
232
+ }
233
+
234
+ let message = event;
235
+ if (data !== undefined) {
236
+ try {
237
+ message += "\n" + JSON.stringify(data, undefined, 2);
238
+ } catch {
239
+ message += "\n[Unserializable error data]";
240
+ }
241
+ }
242
+ return new Error(message);
243
+ }
244
+
245
+ warn(
246
+ event: string,
247
+ dataOrError?: Error | ({ error: Error } & Record<string, any>) | unknown,
248
+ options?: LogOptions,
249
+ ) {
250
+ const data =
251
+ dataOrError instanceof Error
252
+ ? {
253
+ error: {
254
+ type: dataOrError.constructor.name,
255
+ message: dataOrError.message,
256
+ stack: dataOrError.stack || null,
257
+ },
258
+ }
259
+ : isObject(dataOrError) && dataOrError.error instanceof Error
260
+ ? {
261
+ ...dataOrError,
262
+ error: {
263
+ type: dataOrError.error.constructor.name,
264
+ message: dataOrError.error.message,
265
+ stack: dataOrError.error.stack || null,
266
+ },
267
+ }
268
+ : isObject(dataOrError)
269
+ ? dataOrError
270
+ : dataOrError !== undefined
271
+ ? { error: dataOrError }
272
+ : undefined;
273
+
274
+ this.entry({
275
+ level: "warn",
276
+ event,
277
+ data: data as Record<string, any>,
278
+ options,
279
+ });
280
+ }
281
+
282
+ info(
283
+ event: string,
284
+ dataOrError?: Error | ({ error: Error } & Record<string, any>) | unknown,
285
+ options?: LogOptions,
286
+ ) {
287
+ const data =
288
+ dataOrError instanceof Error
289
+ ? {
290
+ error: {
291
+ type: dataOrError.constructor.name,
292
+ message: dataOrError.message,
293
+ stack: dataOrError.stack || null,
294
+ },
295
+ }
296
+ : isObject(dataOrError) && dataOrError.error instanceof Error
297
+ ? {
298
+ ...dataOrError,
299
+ error: {
300
+ type: dataOrError.error.constructor.name,
301
+ message: dataOrError.error.message,
302
+ stack: dataOrError.error.stack || null,
303
+ },
304
+ }
305
+ : isObject(dataOrError)
306
+ ? dataOrError
307
+ : dataOrError !== undefined
308
+ ? { error: dataOrError }
309
+ : undefined;
310
+
311
+ this.entry({
312
+ level: "info",
313
+ event,
314
+ data: data as Record<string, any>,
315
+ options,
316
+ });
317
+ }
318
+
319
+ withScope(scope: string, context: Record<string, any> = {}): LoggerApi {
320
+ return new Logger([...this.scopes, scope], this.sinks, {
321
+ ...this.scopeData,
322
+ ...context,
323
+ });
324
+ }
325
+
326
+ withContext(context: Record<string, any>): LoggerApi {
327
+ return new Logger(this.scopes, this.sinks, {
328
+ ...this.scopeData,
329
+ ...context,
330
+ });
331
+ }
332
+
333
+ withSink(sink: LoggerSink): Logger {
334
+ return new Logger(this.scopes, [...this.sinks, sink]);
335
+ }
336
+
337
+ async flush(): Promise<void> {
338
+ for (let i = this.sinks.length - 1; i >= 0; i--) {
339
+ const sink = this.sinks[i];
340
+ if (!sink) continue;
341
+ if (isSinkClosedOrClosing(sink)) continue;
342
+ try {
343
+ await sink.flush?.();
344
+ } catch {
345
+ // Ignore flush errors - we're likely shutting down
346
+ }
347
+ }
348
+ }
349
+
350
+ async close(): Promise<void> {
351
+ await this.flush();
352
+ for (let i = this.sinks.length - 1; i >= 0; i--) {
353
+ const sink = this.sinks[i];
354
+ if (!sink) continue;
355
+ await closeSinkOnce(sink);
356
+ }
357
+ }
352
358
  }