@lovelaces-io/storyteller 0.1.0 → 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/AGENTS.md +262 -0
- package/README.md +236 -70
- package/dist/cli.cjs +221 -0
- package/dist/index.cjs +1024 -192
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +551 -59
- package/dist/index.d.ts +551 -59
- package/dist/index.js +1009 -190
- package/dist/index.js.map +1 -1
- package/llms.txt +109 -0
- package/package.json +32 -7
- package/snippets/agents-section.md +36 -0
package/dist/index.js
CHANGED
|
@@ -1,3 +1,50 @@
|
|
|
1
|
+
// src/environment.ts
|
|
2
|
+
var LEVEL_RANK = {
|
|
3
|
+
Information: 0,
|
|
4
|
+
Warning: 1,
|
|
5
|
+
Error: 2
|
|
6
|
+
};
|
|
7
|
+
var LEVEL_ALIASES = {
|
|
8
|
+
info: "Information",
|
|
9
|
+
information: "Information",
|
|
10
|
+
tell: "Information",
|
|
11
|
+
warn: "Warning",
|
|
12
|
+
warning: "Warning",
|
|
13
|
+
oops: "Error",
|
|
14
|
+
error: "Error"
|
|
15
|
+
};
|
|
16
|
+
function toStoryLevel(input) {
|
|
17
|
+
if (!input) return "Information";
|
|
18
|
+
return LEVEL_ALIASES[String(input).toLowerCase()] ?? "Information";
|
|
19
|
+
}
|
|
20
|
+
function readEnvironmentValue(name) {
|
|
21
|
+
try {
|
|
22
|
+
const runtime = globalThis;
|
|
23
|
+
const value = runtime.process?.env?.[name];
|
|
24
|
+
return typeof value === "string" && value.length ? value.trim() : void 0;
|
|
25
|
+
} catch {
|
|
26
|
+
return void 0;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function resolveMinimumLevel(requested) {
|
|
30
|
+
const value = requested ?? readEnvironmentValue("STORYTELLER_LEVEL");
|
|
31
|
+
if (!value) return "Information";
|
|
32
|
+
return LEVEL_ALIASES[String(value).toLowerCase()] ?? "Information";
|
|
33
|
+
}
|
|
34
|
+
function meetsLevel(level, minimum) {
|
|
35
|
+
return LEVEL_RANK[level] >= LEVEL_RANK[minimum];
|
|
36
|
+
}
|
|
37
|
+
function resolveOutputFormat(requested) {
|
|
38
|
+
const value = requested ?? readEnvironmentValue("STORYTELLER_FORMAT");
|
|
39
|
+
return value === "ndjson" ? "ndjson" : "text";
|
|
40
|
+
}
|
|
41
|
+
function resolveColors(requested) {
|
|
42
|
+
if (requested !== void 0) return requested;
|
|
43
|
+
const value = readEnvironmentValue("STORYTELLER_COLOR");
|
|
44
|
+
if (value === void 0) return true;
|
|
45
|
+
return !(value === "0" || value.toLowerCase() === "false");
|
|
46
|
+
}
|
|
47
|
+
|
|
1
48
|
// src/utils.ts
|
|
2
49
|
var ANSI = {
|
|
3
50
|
reset: "\x1B[0m",
|
|
@@ -5,18 +52,24 @@ var ANSI = {
|
|
|
5
52
|
yellow: "\x1B[33m",
|
|
6
53
|
red: "\x1B[38;2;250;128;114m",
|
|
7
54
|
grayLight: "\x1B[37m",
|
|
8
|
-
grayDark: "\x1B[
|
|
55
|
+
grayDark: "\x1B[90m"
|
|
9
56
|
};
|
|
10
57
|
function getLevelColor(level) {
|
|
11
|
-
if (level === "
|
|
12
|
-
if (level === "
|
|
58
|
+
if (level === "Information") return ANSI.green;
|
|
59
|
+
if (level === "Warning") return ANSI.yellow;
|
|
13
60
|
return ANSI.red;
|
|
14
61
|
}
|
|
15
62
|
function formatOrigin(origin) {
|
|
16
63
|
if (!origin?.where) return;
|
|
17
64
|
if (typeof origin.where === "string") return origin.where;
|
|
65
|
+
if (typeof origin.where !== "object" || Array.isArray(origin.where)) {
|
|
66
|
+
return String(origin.where);
|
|
67
|
+
}
|
|
18
68
|
const whereRecord = origin.where;
|
|
19
|
-
const
|
|
69
|
+
const priorityKeys = ["app", "service", "page", "component"];
|
|
70
|
+
const priorityParts = priorityKeys.filter((key) => whereRecord[key] != null).map((key) => String(whereRecord[key]));
|
|
71
|
+
const extraParts = Object.entries(whereRecord).filter(([key, value]) => !priorityKeys.includes(key) && value != null).map(([_, value]) => String(value));
|
|
72
|
+
const parts = [...priorityParts, ...extraParts];
|
|
20
73
|
return parts.length ? parts.join(" / ") : void 0;
|
|
21
74
|
}
|
|
22
75
|
function colorizeJsonSections(json, colors) {
|
|
@@ -43,192 +96,482 @@ function countBrackets(line) {
|
|
|
43
96
|
const closeCount = (line.match(/\]/g) || []).length;
|
|
44
97
|
return openCount - closeCount;
|
|
45
98
|
}
|
|
99
|
+
var CONTEXT_LINE_LIMIT = 120;
|
|
100
|
+
function summarizeContext(note) {
|
|
101
|
+
const parts = [];
|
|
102
|
+
appendContextParts(parts, note.what);
|
|
103
|
+
appendContextParts(parts, note.where);
|
|
104
|
+
if (note.error) {
|
|
105
|
+
const errorLine = [note.error.name, note.error.message].filter(Boolean).join(": ");
|
|
106
|
+
if (errorLine && errorLine !== note.note) parts.push(errorLine);
|
|
107
|
+
}
|
|
108
|
+
if (!parts.length) return void 0;
|
|
109
|
+
const joined = parts.join(" ");
|
|
110
|
+
const text = joined.length > CONTEXT_LINE_LIMIT ? `${joined.slice(0, CONTEXT_LINE_LIMIT)}\u2026` : joined;
|
|
111
|
+
return `{${text}}`;
|
|
112
|
+
}
|
|
113
|
+
function appendContextParts(parts, value) {
|
|
114
|
+
if (value == null) return;
|
|
115
|
+
if (typeof value !== "object") {
|
|
116
|
+
parts.push(String(value));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (Array.isArray(value)) {
|
|
120
|
+
parts.push(`[${value.length}]`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
124
|
+
if (entry == null) continue;
|
|
125
|
+
if (key.startsWith("@")) continue;
|
|
126
|
+
parts.push(`${key}=${typeof entry === "object" ? summarizeNested(entry) : String(entry)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function summarizeNested(value) {
|
|
130
|
+
if (Array.isArray(value)) return `[${value.length}]`;
|
|
131
|
+
if (value && typeof value === "object") return `{${Object.keys(value).length}}`;
|
|
132
|
+
return String(value);
|
|
133
|
+
}
|
|
46
134
|
|
|
47
135
|
// src/audiences/consoleAudience.ts
|
|
48
|
-
|
|
136
|
+
var LEVEL_LABELS = {
|
|
137
|
+
Information: "info",
|
|
138
|
+
Warning: "warn",
|
|
139
|
+
Error: "oops"
|
|
140
|
+
};
|
|
141
|
+
var LEVEL_STYLES = {
|
|
142
|
+
Information: "color:#16a34a;font-weight:600",
|
|
143
|
+
Warning: "color:#f59e0b;font-weight:600",
|
|
144
|
+
Error: "color:#dc2626;font-weight:600"
|
|
145
|
+
};
|
|
146
|
+
function consoleAudience(options = {}) {
|
|
147
|
+
const colors = resolveColors(options.colors);
|
|
49
148
|
return {
|
|
50
149
|
name: "console",
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const payload = JSON.stringify(event, null, 2);
|
|
57
|
-
const coloredPayload = event.level === "oops" ? `${ANSI.red}${payload}${ANSI.reset}` : payload;
|
|
58
|
-
if (event.level === "tell") {
|
|
59
|
-
console.log(header, payload);
|
|
60
|
-
} else if (event.level === "warn") {
|
|
61
|
-
console.warn(header, payload);
|
|
62
|
-
} else {
|
|
63
|
-
console.error(header, coloredPayload);
|
|
150
|
+
hears: ["note", "story"],
|
|
151
|
+
hear: (emission) => {
|
|
152
|
+
if (emission.kind === "note") {
|
|
153
|
+
printNote(emission, colors);
|
|
154
|
+
return;
|
|
64
155
|
}
|
|
65
|
-
|
|
156
|
+
printStory(emission);
|
|
66
157
|
}
|
|
67
158
|
};
|
|
68
159
|
}
|
|
160
|
+
function printNote(note, colors) {
|
|
161
|
+
const time = readClockTime(note.timestamp);
|
|
162
|
+
const label = LEVEL_LABELS[note.level];
|
|
163
|
+
const origin = formatOrigin(note.origin);
|
|
164
|
+
const context = summarizeContext(note);
|
|
165
|
+
const head = colors ? `${getLevelColor(note.level)}${label}${ANSI.reset}` : label;
|
|
166
|
+
const line = [
|
|
167
|
+
colors ? `${ANSI.grayDark}${time}${ANSI.reset}` : time,
|
|
168
|
+
head,
|
|
169
|
+
origin ? colors ? `${ANSI.grayDark}${origin}${ANSI.reset}` : origin : void 0,
|
|
170
|
+
note.note,
|
|
171
|
+
context ? colors ? `${ANSI.grayDark}${context}${ANSI.reset}` : context : void 0
|
|
172
|
+
].filter(Boolean).join(" ");
|
|
173
|
+
if (note.level === "Information") {
|
|
174
|
+
console.log(line);
|
|
175
|
+
} else if (note.level === "Warning") {
|
|
176
|
+
console.warn(line);
|
|
177
|
+
} else {
|
|
178
|
+
console.error(line);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
function printStory(event) {
|
|
182
|
+
const prefix = "Storyteller";
|
|
183
|
+
const header = `${prefix}: ${event.title}`;
|
|
184
|
+
console.groupCollapsed(`%c${header}`, LEVEL_STYLES[event.level]);
|
|
185
|
+
const payload = JSON.stringify(event, null, 2);
|
|
186
|
+
if (event.level === "Information") {
|
|
187
|
+
console.log(header, payload);
|
|
188
|
+
} else if (event.level === "Warning") {
|
|
189
|
+
console.warn(header, payload);
|
|
190
|
+
} else {
|
|
191
|
+
console.error(header, payload);
|
|
192
|
+
}
|
|
193
|
+
console.groupEnd();
|
|
194
|
+
}
|
|
195
|
+
function readClockTime(timestamp) {
|
|
196
|
+
const timePart = timestamp.slice(11, 19);
|
|
197
|
+
return timePart.length === 8 ? timePart : timestamp;
|
|
198
|
+
}
|
|
69
199
|
|
|
70
|
-
// src/
|
|
71
|
-
var
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
200
|
+
// src/normalize.ts
|
|
201
|
+
var REDACTED = "[redacted]";
|
|
202
|
+
var DEFAULT_REDACT_KEYS = [
|
|
203
|
+
"password",
|
|
204
|
+
"passphrase",
|
|
205
|
+
"token",
|
|
206
|
+
"secret",
|
|
207
|
+
"apiKey",
|
|
208
|
+
"accessKey",
|
|
209
|
+
"authorization",
|
|
210
|
+
"auth",
|
|
211
|
+
"cookie",
|
|
212
|
+
"sessionId",
|
|
213
|
+
"privateKey",
|
|
214
|
+
"clientSecret",
|
|
215
|
+
"refreshToken"
|
|
216
|
+
];
|
|
217
|
+
var DEFAULT_MAX_DEPTH = 6;
|
|
218
|
+
var DEFAULT_MAX_ARRAY_LENGTH = 100;
|
|
219
|
+
var DEFAULT_MAX_PROPERTIES = 100;
|
|
220
|
+
var DEFAULT_MAX_STRING_LENGTH = 8e3;
|
|
221
|
+
var MAX_CAUSE_DEPTH = 5;
|
|
222
|
+
var BINARY_PREVIEW_BYTES = 16;
|
|
223
|
+
function normalizeValue(input, options = {}) {
|
|
224
|
+
const resolved = {
|
|
225
|
+
maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
226
|
+
maxArrayLength: options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH,
|
|
227
|
+
maxProperties: options.maxProperties ?? DEFAULT_MAX_PROPERTIES,
|
|
228
|
+
maxStringLength: options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH,
|
|
229
|
+
redactKeys: new Set(
|
|
230
|
+
(options.redactKeys ?? DEFAULT_REDACT_KEYS).map(normalizeKeyForMatching)
|
|
231
|
+
),
|
|
232
|
+
redact: options.redact ?? true
|
|
233
|
+
};
|
|
234
|
+
try {
|
|
235
|
+
return normalizeUnknown(input, resolved, 0, "$", /* @__PURE__ */ new Map());
|
|
236
|
+
} catch (failure) {
|
|
237
|
+
return `[Unreadable: ${describeFailure(failure)}]`;
|
|
77
238
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
239
|
+
}
|
|
240
|
+
function normalizeError(rawError, options = {}) {
|
|
241
|
+
const resolved = {
|
|
242
|
+
maxDepth: options.maxDepth ?? DEFAULT_MAX_DEPTH,
|
|
243
|
+
maxArrayLength: options.maxArrayLength ?? DEFAULT_MAX_ARRAY_LENGTH,
|
|
244
|
+
maxProperties: options.maxProperties ?? DEFAULT_MAX_PROPERTIES,
|
|
245
|
+
maxStringLength: options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH,
|
|
246
|
+
redactKeys: new Set(
|
|
247
|
+
(options.redactKeys ?? DEFAULT_REDACT_KEYS).map(normalizeKeyForMatching)
|
|
248
|
+
),
|
|
249
|
+
redact: options.redact ?? true
|
|
250
|
+
};
|
|
251
|
+
return normalizeErrorInternal(rawError, resolved, 0);
|
|
252
|
+
}
|
|
253
|
+
function normalizeErrorInternal(rawError, options, causeDepth) {
|
|
254
|
+
if (!(rawError instanceof Error)) {
|
|
255
|
+
if (isPlainRecord(rawError)) {
|
|
256
|
+
const record = rawError;
|
|
257
|
+
const message = typeof record["message"] === "string" ? record["message"] : void 0;
|
|
258
|
+
const name = typeof record["name"] === "string" ? record["name"] : void 0;
|
|
259
|
+
if (message !== void 0 || name !== void 0) {
|
|
260
|
+
return {
|
|
261
|
+
...name !== void 0 ? { name } : {},
|
|
262
|
+
...message !== void 0 ? { message } : {}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return { message: safeStringify(rawError, options.maxStringLength) };
|
|
82
267
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
268
|
+
const normalized = {
|
|
269
|
+
name: rawError.name,
|
|
270
|
+
message: rawError.message
|
|
271
|
+
};
|
|
272
|
+
if (rawError.stack !== void 0) {
|
|
273
|
+
normalized.stack = truncateString(rawError.stack, options.maxStringLength);
|
|
86
274
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
275
|
+
const cause = rawError.cause;
|
|
276
|
+
if (cause !== void 0) {
|
|
277
|
+
if (causeDepth >= MAX_CAUSE_DEPTH) {
|
|
278
|
+
normalized.cause = { "@truncated": { kind: "causeChain" } };
|
|
279
|
+
} else if (cause instanceof Error) {
|
|
280
|
+
normalized.cause = normalizeErrorInternal(cause, options, causeDepth + 1);
|
|
281
|
+
} else {
|
|
282
|
+
normalized.cause = normalizeUnknown(cause, options, 0, "$.cause", /* @__PURE__ */ new Map());
|
|
283
|
+
}
|
|
90
284
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
origin;
|
|
95
|
-
notes = [];
|
|
96
|
-
constructor(options) {
|
|
97
|
-
this.origin = options?.origin;
|
|
98
|
-
this.audience.add(consoleAudience());
|
|
99
|
-
options?.audiences?.forEach((audience) => this.audience.add(audience));
|
|
285
|
+
const aggregated = rawError.errors;
|
|
286
|
+
if (Array.isArray(aggregated)) {
|
|
287
|
+
normalized.errors = aggregated.slice(0, options.maxArrayLength).map((member) => normalizeErrorInternal(member, options, causeDepth + 1));
|
|
100
288
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
...data.where ? { where: data.where } : {},
|
|
109
|
-
...data.error ? { error: normalizeError(data.error) } : {}
|
|
110
|
-
});
|
|
111
|
-
return this;
|
|
289
|
+
return normalized;
|
|
290
|
+
}
|
|
291
|
+
function normalizeUnknown(value, options, depth, path, ancestors) {
|
|
292
|
+
if (value === null) return null;
|
|
293
|
+
const valueType = typeof value;
|
|
294
|
+
if (valueType === "string") {
|
|
295
|
+
return truncateString(value, options.maxStringLength);
|
|
112
296
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
this.notes = [];
|
|
116
|
-
return this;
|
|
297
|
+
if (valueType === "number") {
|
|
298
|
+
return Number.isFinite(value) ? value : String(value);
|
|
117
299
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
} = options;
|
|
126
|
-
const event = {
|
|
127
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
128
|
-
level,
|
|
129
|
-
title,
|
|
130
|
-
...this.origin ? { origin: this.origin } : {},
|
|
131
|
-
notes: [...this.notes],
|
|
132
|
-
...error ? { error: normalizeError(error) } : {}
|
|
133
|
-
};
|
|
134
|
-
return summarizeStory(event, summaryOptions);
|
|
300
|
+
if (valueType === "boolean") return value;
|
|
301
|
+
if (valueType === "undefined") return null;
|
|
302
|
+
if (valueType === "bigint") return `${String(value)}n`;
|
|
303
|
+
if (valueType === "symbol") return String(value);
|
|
304
|
+
if (valueType === "function") {
|
|
305
|
+
const name = value.name;
|
|
306
|
+
return `[Function: ${name ? name : "anonymous"}]`;
|
|
135
307
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
308
|
+
const objectValue = value;
|
|
309
|
+
const existingPath = ancestors.get(objectValue);
|
|
310
|
+
if (existingPath !== void 0) {
|
|
311
|
+
return `[Circular \u2192 ${existingPath}]`;
|
|
139
312
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
return this.createDelivery("warn", title);
|
|
313
|
+
if (depth > options.maxDepth) {
|
|
314
|
+
return { "@truncated": { kind: "depth", depth: options.maxDepth } };
|
|
143
315
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
316
|
+
const wellKnown = normalizeWellKnown(objectValue, options, depth, path, ancestors);
|
|
317
|
+
if (wellKnown !== void 0) return wellKnown;
|
|
318
|
+
ancestors.set(objectValue, path);
|
|
319
|
+
try {
|
|
320
|
+
if (Array.isArray(objectValue)) {
|
|
321
|
+
return normalizeArray(objectValue, options, depth, path, ancestors);
|
|
322
|
+
}
|
|
323
|
+
return normalizeObject(objectValue, options, depth, path, ancestors);
|
|
324
|
+
} finally {
|
|
325
|
+
ancestors.delete(objectValue);
|
|
147
326
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
327
|
+
}
|
|
328
|
+
function normalizeWellKnown(value, options, depth, path, ancestors) {
|
|
329
|
+
if (value instanceof Error) {
|
|
330
|
+
return normalizeErrorInternal(value, options, 0);
|
|
331
|
+
}
|
|
332
|
+
if (value instanceof Date) {
|
|
333
|
+
const time = value.getTime();
|
|
334
|
+
return Number.isNaN(time) ? "[Invalid Date]" : value.toISOString();
|
|
335
|
+
}
|
|
336
|
+
if (value instanceof RegExp) return String(value);
|
|
337
|
+
if (value instanceof URL) return value.href;
|
|
338
|
+
if (value instanceof Map) {
|
|
339
|
+
const entries = {};
|
|
340
|
+
let index = 0;
|
|
341
|
+
let omitted = 0;
|
|
342
|
+
for (const [entryKey, entryValue] of value) {
|
|
343
|
+
if (index >= options.maxProperties) {
|
|
344
|
+
omitted += 1;
|
|
345
|
+
continue;
|
|
164
346
|
}
|
|
347
|
+
const keyLabel = safeStringify(entryKey, options.maxStringLength);
|
|
348
|
+
entries[keyLabel] = redactOrNormalize(
|
|
349
|
+
keyLabel,
|
|
350
|
+
entryValue,
|
|
351
|
+
options,
|
|
352
|
+
depth + 1,
|
|
353
|
+
`${path}.${keyLabel}`,
|
|
354
|
+
ancestors
|
|
355
|
+
);
|
|
356
|
+
index += 1;
|
|
357
|
+
}
|
|
358
|
+
return {
|
|
359
|
+
"@type": "Map",
|
|
360
|
+
entries,
|
|
361
|
+
...omitted ? { "@truncated": { kind: "mapEntries", omitted } } : {}
|
|
165
362
|
};
|
|
166
363
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
364
|
+
if (value instanceof Set) {
|
|
365
|
+
const values = [];
|
|
366
|
+
let omitted = 0;
|
|
367
|
+
for (const member of value) {
|
|
368
|
+
if (values.length >= options.maxArrayLength) {
|
|
369
|
+
omitted += 1;
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
values.push(
|
|
373
|
+
normalizeUnknown(member, options, depth + 1, `${path}[${values.length}]`, ancestors)
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
return {
|
|
377
|
+
"@type": "Set",
|
|
378
|
+
values,
|
|
379
|
+
...omitted ? { "@truncated": { kind: "setValues", omitted } } : {}
|
|
179
380
|
};
|
|
180
|
-
const eventWithSummary = event;
|
|
181
|
-
Object.defineProperty(eventWithSummary, "summarize", {
|
|
182
|
-
value: (options) => summarizeStory(event, options),
|
|
183
|
-
enumerable: false
|
|
184
|
-
});
|
|
185
|
-
return eventWithSummary;
|
|
186
381
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
382
|
+
if (value instanceof WeakMap) return "[WeakMap]";
|
|
383
|
+
if (value instanceof WeakSet) return "[WeakSet]";
|
|
384
|
+
if (value instanceof Promise) return "[Promise]";
|
|
385
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
|
|
386
|
+
return describeBinary(value);
|
|
387
|
+
}
|
|
388
|
+
const converted = callToJson(value);
|
|
389
|
+
if (converted !== void 0) {
|
|
390
|
+
return normalizeUnknown(converted, options, depth, path, ancestors);
|
|
391
|
+
}
|
|
392
|
+
return void 0;
|
|
393
|
+
}
|
|
394
|
+
function callToJson(value) {
|
|
395
|
+
let toJson;
|
|
396
|
+
try {
|
|
397
|
+
toJson = value.toJSON;
|
|
398
|
+
} catch {
|
|
399
|
+
return void 0;
|
|
400
|
+
}
|
|
401
|
+
if (typeof toJson !== "function") return void 0;
|
|
402
|
+
try {
|
|
403
|
+
return toJson.call(value);
|
|
404
|
+
} catch (failure) {
|
|
405
|
+
return `[Unreadable: ${describeFailure(failure)}]`;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function describeBinary(value) {
|
|
409
|
+
const typeName = readConstructorName(value) ?? "ArrayBuffer";
|
|
410
|
+
const byteLength = value.byteLength;
|
|
411
|
+
let preview;
|
|
412
|
+
try {
|
|
413
|
+
const bytes = value instanceof ArrayBuffer ? new Uint8Array(value, 0, Math.min(BINARY_PREVIEW_BYTES, byteLength)) : new Uint8Array(
|
|
414
|
+
value.buffer,
|
|
415
|
+
value.byteOffset,
|
|
416
|
+
Math.min(BINARY_PREVIEW_BYTES, value.byteLength)
|
|
192
417
|
);
|
|
418
|
+
preview = [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join(" ");
|
|
419
|
+
} catch {
|
|
420
|
+
preview = "";
|
|
193
421
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
422
|
+
return {
|
|
423
|
+
"@type": typeName,
|
|
424
|
+
byteLength,
|
|
425
|
+
...preview ? { preview } : {},
|
|
426
|
+
...byteLength > BINARY_PREVIEW_BYTES ? { "@truncated": { kind: "bytes", omitted: byteLength - BINARY_PREVIEW_BYTES } } : {}
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
function normalizeArray(value, options, depth, path, ancestors) {
|
|
430
|
+
const kept = [];
|
|
431
|
+
const limit = Math.min(value.length, options.maxArrayLength);
|
|
432
|
+
for (let index = 0; index < limit; index += 1) {
|
|
433
|
+
kept.push(
|
|
434
|
+
normalizeUnknown(value[index], options, depth + 1, `${path}[${index}]`, ancestors)
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
if (value.length > limit) {
|
|
438
|
+
kept.push({ "@truncated": { kind: "array", omitted: value.length - limit } });
|
|
439
|
+
}
|
|
440
|
+
return kept;
|
|
441
|
+
}
|
|
442
|
+
function normalizeObject(value, options, depth, path, ancestors) {
|
|
443
|
+
const result = {};
|
|
444
|
+
const className = readConstructorName(value);
|
|
445
|
+
if (className && className !== "Object") {
|
|
446
|
+
result["@type"] = className;
|
|
447
|
+
}
|
|
448
|
+
let keys;
|
|
449
|
+
try {
|
|
450
|
+
keys = Object.keys(value);
|
|
451
|
+
} catch {
|
|
452
|
+
return `[Unreadable: keys could not be listed]`;
|
|
453
|
+
}
|
|
454
|
+
let kept = 0;
|
|
455
|
+
let omitted = 0;
|
|
456
|
+
for (const key of keys) {
|
|
457
|
+
if (kept >= options.maxProperties) {
|
|
458
|
+
omitted += 1;
|
|
459
|
+
continue;
|
|
203
460
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
461
|
+
let propertyValue;
|
|
462
|
+
try {
|
|
463
|
+
propertyValue = value[key];
|
|
464
|
+
} catch (failure) {
|
|
465
|
+
result[key] = `[Unreadable: ${describeFailure(failure)}]`;
|
|
466
|
+
kept += 1;
|
|
467
|
+
continue;
|
|
207
468
|
}
|
|
208
|
-
|
|
469
|
+
if (propertyValue === void 0) continue;
|
|
470
|
+
result[key] = redactOrNormalize(
|
|
471
|
+
key,
|
|
472
|
+
propertyValue,
|
|
473
|
+
options,
|
|
474
|
+
depth + 1,
|
|
475
|
+
`${path}.${key}`,
|
|
476
|
+
ancestors
|
|
477
|
+
);
|
|
478
|
+
kept += 1;
|
|
479
|
+
}
|
|
480
|
+
if (omitted) {
|
|
481
|
+
result["@truncated"] = { kind: "properties", omitted };
|
|
209
482
|
}
|
|
210
|
-
return
|
|
483
|
+
return result;
|
|
211
484
|
}
|
|
212
|
-
function
|
|
213
|
-
if (
|
|
214
|
-
return
|
|
215
|
-
durationMs: void 0
|
|
216
|
-
};
|
|
485
|
+
function redactOrNormalize(key, value, options, depth, path, ancestors) {
|
|
486
|
+
if (options.redact && options.redactKeys.has(normalizeKeyForMatching(key))) {
|
|
487
|
+
return REDACTED;
|
|
217
488
|
}
|
|
218
|
-
|
|
219
|
-
|
|
489
|
+
return normalizeUnknown(value, options, depth, path, ancestors);
|
|
490
|
+
}
|
|
491
|
+
function normalizeKeyForMatching(key) {
|
|
492
|
+
return key.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
|
493
|
+
}
|
|
494
|
+
function readConstructorName(value) {
|
|
495
|
+
try {
|
|
496
|
+
const prototype = Object.getPrototypeOf(value);
|
|
497
|
+
if (prototype === null) return void 0;
|
|
498
|
+
const name = prototype.constructor?.name;
|
|
499
|
+
return typeof name === "string" && name.length ? name : void 0;
|
|
500
|
+
} catch {
|
|
501
|
+
return void 0;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function isPlainRecord(value) {
|
|
505
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
506
|
+
}
|
|
507
|
+
function truncateString(value, maxLength) {
|
|
508
|
+
if (value.length <= maxLength) return value;
|
|
509
|
+
return `${value.slice(0, maxLength)}\u2026[+${value.length - maxLength} chars]`;
|
|
510
|
+
}
|
|
511
|
+
function safeStringify(value, maxLength) {
|
|
512
|
+
try {
|
|
513
|
+
return truncateString(String(value), maxLength);
|
|
514
|
+
} catch {
|
|
515
|
+
return "[unstringifiable]";
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function describeFailure(failure) {
|
|
519
|
+
if (failure instanceof Error && failure.message) return failure.message;
|
|
520
|
+
try {
|
|
521
|
+
return String(failure);
|
|
522
|
+
} catch {
|
|
523
|
+
return "unknown error";
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// src/audiences/ndjsonAudience.ts
|
|
528
|
+
function ndjsonAudience(options = {}) {
|
|
529
|
+
const writer = options.stream ?? createDefaultWriter();
|
|
530
|
+
const minimumLevel = resolveMinimumLevel(options.level);
|
|
220
531
|
return {
|
|
221
|
-
|
|
532
|
+
name: options.name ?? "ndjson",
|
|
533
|
+
hears: ["note", "story"],
|
|
534
|
+
accepts: (emission) => meetsLevel(emission.level, minimumLevel),
|
|
535
|
+
hear: (emission) => {
|
|
536
|
+
writer.write(`${serializeEmission(emission)}
|
|
537
|
+
`);
|
|
538
|
+
}
|
|
222
539
|
};
|
|
223
540
|
}
|
|
224
|
-
function
|
|
541
|
+
function serializeEmission(emission) {
|
|
542
|
+
try {
|
|
543
|
+
return JSON.stringify(emission);
|
|
544
|
+
} catch {
|
|
545
|
+
try {
|
|
546
|
+
return JSON.stringify(normalizeValue(emission));
|
|
547
|
+
} catch {
|
|
548
|
+
return JSON.stringify({
|
|
549
|
+
kind: emission.kind,
|
|
550
|
+
level: emission.level,
|
|
551
|
+
error: "[Unserializable emission]"
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function createDefaultWriter() {
|
|
557
|
+
const runtime = globalThis;
|
|
558
|
+
const write = runtime.process?.stdout?.write;
|
|
559
|
+
if (typeof write === "function") {
|
|
560
|
+
const stdout = runtime.process.stdout;
|
|
561
|
+
return { write: (chunk) => write.call(stdout, chunk) };
|
|
562
|
+
}
|
|
563
|
+
return { write: (chunk) => console.log(chunk.replace(/\n$/, "")) };
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// src/formatting.ts
|
|
567
|
+
function formatStory(story, options = {}) {
|
|
225
568
|
const {
|
|
226
569
|
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
227
570
|
locale = "en-US",
|
|
228
|
-
|
|
229
|
-
|
|
571
|
+
detail = "normal",
|
|
572
|
+
noteLimit = 50,
|
|
230
573
|
showData = true,
|
|
231
|
-
|
|
574
|
+
colors = true
|
|
232
575
|
} = options;
|
|
233
576
|
const dateTimeFormatter = new Intl.DateTimeFormat(locale, {
|
|
234
577
|
timeZone: timezone,
|
|
@@ -248,15 +591,14 @@ function summarizeStory(story, options = {}) {
|
|
|
248
591
|
const orderedNotes = [...story.notes].sort(
|
|
249
592
|
(noteA, noteB) => Date.parse(noteA.timestamp) - Date.parse(noteB.timestamp)
|
|
250
593
|
);
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
const summaryNotes = slicedNotes.map((note) => ({
|
|
594
|
+
const durationMs = story.durationMs ?? calculateNoteDuration(orderedNotes);
|
|
595
|
+
const duration = durationMs != null ? formatDuration(durationMs) : void 0;
|
|
596
|
+
const slicedNotes = orderedNotes.slice(0, noteLimit);
|
|
597
|
+
const reportNotes = slicedNotes.map((note) => ({
|
|
256
598
|
timestamp: note.timestamp,
|
|
257
599
|
when: timeFormatter.format(new Date(note.timestamp)),
|
|
258
600
|
note: note.note,
|
|
259
|
-
text: formatNoteText(note,
|
|
601
|
+
text: formatNoteText(note, detail),
|
|
260
602
|
...note.who ? { who: note.who } : {},
|
|
261
603
|
...note.what ? { what: note.what } : {},
|
|
262
604
|
...note.where ? { where: note.where } : {},
|
|
@@ -266,18 +608,28 @@ function summarizeStory(story, options = {}) {
|
|
|
266
608
|
title: story.title,
|
|
267
609
|
level: story.level,
|
|
268
610
|
when: dateTimeFormatter.format(new Date(story.timestamp)),
|
|
269
|
-
...
|
|
611
|
+
...durationMs != null ? { durationMs } : {},
|
|
270
612
|
...duration ? { duration } : {},
|
|
271
613
|
...story.origin ? { origin: story.origin } : {},
|
|
272
|
-
notes:
|
|
614
|
+
notes: reportNotes,
|
|
273
615
|
...story.error ? { error: story.error } : {}
|
|
274
616
|
};
|
|
617
|
+
const lines = buildReportText(story, data, reportNotes, orderedNotes, {
|
|
618
|
+
colors,
|
|
619
|
+
detail,
|
|
620
|
+
showData,
|
|
621
|
+
duration
|
|
622
|
+
});
|
|
623
|
+
return { text: lines.join("\n"), data };
|
|
624
|
+
}
|
|
625
|
+
function buildReportText(story, data, reportNotes, orderedNotes, options) {
|
|
275
626
|
const levelColor = getLevelColor(story.level);
|
|
276
|
-
const label = (text) =>
|
|
627
|
+
const label = (text) => options.colors ? `${levelColor}${text}${ANSI.reset}` : text;
|
|
628
|
+
const originLabel = formatOrigin(story.origin);
|
|
277
629
|
const lines = [];
|
|
278
630
|
lines.push(`${label("Story")}: ${story.title}`);
|
|
279
631
|
lines.push(`${label("Level")}: ${story.level}`);
|
|
280
|
-
lines.push(`${label("Time")}: ${data.when}${duration ? ` (${duration})` : ""}`);
|
|
632
|
+
lines.push(`${label("Time")}: ${data.when}${options.duration ? ` (${options.duration})` : ""}`);
|
|
281
633
|
if (originLabel) {
|
|
282
634
|
lines.push(`${label("Origin")}: ${originLabel}`);
|
|
283
635
|
}
|
|
@@ -285,19 +637,19 @@ function summarizeStory(story, options = {}) {
|
|
|
285
637
|
const errorLine = [story.error.name, story.error.message].filter(Boolean).join(": ");
|
|
286
638
|
if (errorLine) lines.push(`${label("Error")}: ${errorLine}`);
|
|
287
639
|
}
|
|
288
|
-
if (
|
|
640
|
+
if (options.detail !== "brief" && reportNotes.length) {
|
|
289
641
|
lines.push(`${label("Notes")}:`);
|
|
290
|
-
for (const note of
|
|
642
|
+
for (const note of reportNotes) {
|
|
291
643
|
lines.push(` ${note.when} \u2014 ${note.text}`);
|
|
292
644
|
}
|
|
293
|
-
if (orderedNotes.length >
|
|
294
|
-
lines.push(` \u2026 (${orderedNotes.length -
|
|
645
|
+
if (orderedNotes.length > reportNotes.length) {
|
|
646
|
+
lines.push(` \u2026 (${orderedNotes.length - reportNotes.length} more)`);
|
|
295
647
|
}
|
|
296
648
|
}
|
|
297
|
-
if (showData) {
|
|
649
|
+
if (options.showData) {
|
|
298
650
|
lines.push(`${label("Data")}:`);
|
|
299
651
|
const json = JSON.stringify(data, null, 2);
|
|
300
|
-
if (
|
|
652
|
+
if (options.colors) {
|
|
301
653
|
const colored = colorizeJsonSections(json, {
|
|
302
654
|
base: ANSI.grayLight,
|
|
303
655
|
notes: ANSI.grayDark,
|
|
@@ -308,7 +660,13 @@ function summarizeStory(story, options = {}) {
|
|
|
308
660
|
lines.push(...json.split("\n"));
|
|
309
661
|
}
|
|
310
662
|
}
|
|
311
|
-
return
|
|
663
|
+
return lines;
|
|
664
|
+
}
|
|
665
|
+
function calculateNoteDuration(notes) {
|
|
666
|
+
if (notes.length <= 1) return void 0;
|
|
667
|
+
const startTime = Date.parse(notes[0].timestamp);
|
|
668
|
+
const endTime = Date.parse(notes[notes.length - 1].timestamp);
|
|
669
|
+
return Number.isFinite(startTime) && Number.isFinite(endTime) ? Math.max(0, endTime - startTime) : void 0;
|
|
312
670
|
}
|
|
313
671
|
function formatDuration(milliseconds) {
|
|
314
672
|
if (milliseconds < 1e3) return `${milliseconds}ms`;
|
|
@@ -318,6 +676,7 @@ function formatDuration(milliseconds) {
|
|
|
318
676
|
const remainingSeconds = Math.round(seconds % 60).toString().padStart(2, "0");
|
|
319
677
|
return `${minutes}:${remainingSeconds}m`;
|
|
320
678
|
}
|
|
679
|
+
var summarizeStory = formatStory;
|
|
321
680
|
function formatNoteText(note, verbosity) {
|
|
322
681
|
if (verbosity !== "full") return note.note;
|
|
323
682
|
const details = [];
|
|
@@ -326,13 +685,16 @@ function formatNoteText(note, verbosity) {
|
|
|
326
685
|
if (typeof what === "string") {
|
|
327
686
|
details.push(`what=${what}`);
|
|
328
687
|
} else if (what) {
|
|
329
|
-
|
|
330
|
-
|
|
688
|
+
for (const [key, value] of Object.entries(what)) {
|
|
689
|
+
if (value != null) details.push(`${key}=${String(value)}`);
|
|
690
|
+
}
|
|
331
691
|
}
|
|
332
692
|
if (typeof where === "string") {
|
|
333
693
|
details.push(`where=${where}`);
|
|
334
694
|
} else if (where) {
|
|
335
|
-
|
|
695
|
+
for (const [key, value] of Object.entries(where)) {
|
|
696
|
+
if (value != null) details.push(`${key}=${String(value)}`);
|
|
697
|
+
}
|
|
336
698
|
}
|
|
337
699
|
if (note.error) {
|
|
338
700
|
const errorLine = [note.error.name, note.error.message].filter(Boolean).join(": ");
|
|
@@ -341,11 +703,453 @@ function formatNoteText(note, verbosity) {
|
|
|
341
703
|
return details.length ? `${note.note} (${details.join(" ")})` : note.note;
|
|
342
704
|
}
|
|
343
705
|
|
|
706
|
+
// src/storyteller.ts
|
|
707
|
+
var AudienceRegistry = class {
|
|
708
|
+
members = /* @__PURE__ */ new Map();
|
|
709
|
+
/** Register an audience member, replacing any existing member with the same name */
|
|
710
|
+
add(member) {
|
|
711
|
+
this.members.set(member.name, member);
|
|
712
|
+
return this;
|
|
713
|
+
}
|
|
714
|
+
/** Remove an audience member by name */
|
|
715
|
+
remove(name) {
|
|
716
|
+
this.members.delete(name);
|
|
717
|
+
return this;
|
|
718
|
+
}
|
|
719
|
+
/** Return all registered audience members */
|
|
720
|
+
getAll() {
|
|
721
|
+
return [...this.members.values()];
|
|
722
|
+
}
|
|
723
|
+
/** Return only the audience members matching the given names */
|
|
724
|
+
getOnly(names) {
|
|
725
|
+
return names.map((name) => this.members.get(name)).filter(Boolean);
|
|
726
|
+
}
|
|
727
|
+
/** Check if an audience member is registered by name */
|
|
728
|
+
has(name) {
|
|
729
|
+
return this.members.has(name);
|
|
730
|
+
}
|
|
731
|
+
/** List the names of all registered audience members */
|
|
732
|
+
names() {
|
|
733
|
+
return [...this.members.keys()];
|
|
734
|
+
}
|
|
735
|
+
};
|
|
736
|
+
var Storyteller = class _Storyteller {
|
|
737
|
+
audience;
|
|
738
|
+
origin;
|
|
739
|
+
parentStoryId;
|
|
740
|
+
notes = [];
|
|
741
|
+
narration;
|
|
742
|
+
minimumLevel;
|
|
743
|
+
onAudienceError;
|
|
744
|
+
maxInFlight;
|
|
745
|
+
/** Deliveries currently awaiting each audience, keyed by audience name */
|
|
746
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
747
|
+
/** Emissions dropped for back-pressure since the current story began */
|
|
748
|
+
droppedEmissions = 0;
|
|
749
|
+
/** Identifies the story currently being collected; regenerated after each telling */
|
|
750
|
+
storyId = createStoryId();
|
|
751
|
+
/** Position of the next note within the current story */
|
|
752
|
+
nextSequence = 0;
|
|
753
|
+
constructor(options) {
|
|
754
|
+
const normalizedOrigin = normalizeOrigin(options?.origin);
|
|
755
|
+
if (normalizedOrigin) {
|
|
756
|
+
this.origin = normalizedOrigin;
|
|
757
|
+
}
|
|
758
|
+
if (options?.parentStoryId !== void 0) {
|
|
759
|
+
this.parentStoryId = options.parentStoryId;
|
|
760
|
+
}
|
|
761
|
+
this.narration = resolveNarration(options?.narration);
|
|
762
|
+
this.minimumLevel = resolveMinimumLevel(options?.level);
|
|
763
|
+
this.onAudienceError = options?.onAudienceError ?? reportAudienceErrorToConsole;
|
|
764
|
+
this.maxInFlight = options?.maxInFlight ?? DEFAULT_MAX_IN_FLIGHT;
|
|
765
|
+
if (options?.audience) {
|
|
766
|
+
this.audience = options.audience;
|
|
767
|
+
} else {
|
|
768
|
+
this.audience = new AudienceRegistry();
|
|
769
|
+
this.audience.add(
|
|
770
|
+
resolveOutputFormat(options?.format) === "ndjson" ? ndjsonAudience({ level: this.minimumLevel }) : consoleAudience()
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
options?.audiences?.forEach((audience) => this.audience.add(audience));
|
|
774
|
+
}
|
|
775
|
+
/**
|
|
776
|
+
* Switch between collected and live narration at runtime.
|
|
777
|
+
* Takes effect on the next note; already-buffered notes are not replayed.
|
|
778
|
+
*
|
|
779
|
+
* @param narration - `collected` to buffer, `live` to emit each note as it happens
|
|
780
|
+
* @returns `this` for chaining
|
|
781
|
+
*/
|
|
782
|
+
narrate(narration) {
|
|
783
|
+
this.narration = resolveNarration(narration);
|
|
784
|
+
return this;
|
|
785
|
+
}
|
|
786
|
+
/** The id of the story currently being collected */
|
|
787
|
+
get currentStoryId() {
|
|
788
|
+
return this.storyId;
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Start a chapter: a child storyteller whose stories are linked back to this
|
|
792
|
+
* one by `parentStoryId`.
|
|
793
|
+
*
|
|
794
|
+
* Real work nests — an agent spawns subtasks, a batch runs per-item operations.
|
|
795
|
+
* A chapter keeps each of those a complete story in its own right while leaving
|
|
796
|
+
* the run reconstructable as a tree.
|
|
797
|
+
*
|
|
798
|
+
* The child shares this storyteller's audience registry, so audiences added
|
|
799
|
+
* later reach it too, and inherits narration, level and delivery settings.
|
|
800
|
+
* Its stories are separate records — a chapter is not folded into the parent's
|
|
801
|
+
* notes.
|
|
802
|
+
*
|
|
803
|
+
* @param options - Origin to merge over the parent's, and any setting to override
|
|
804
|
+
* @returns A child Storyteller
|
|
805
|
+
*
|
|
806
|
+
* @example
|
|
807
|
+
* ```ts
|
|
808
|
+
* for (const account of accounts) {
|
|
809
|
+
* const chapter = story.chapter({ origin: { what: account.id } });
|
|
810
|
+
* chapter.report("Fetching invoices");
|
|
811
|
+
* chapter.finish(`Synced ${account.id}`);
|
|
812
|
+
* }
|
|
813
|
+
* ```
|
|
814
|
+
*/
|
|
815
|
+
chapter(options = {}) {
|
|
816
|
+
const mergedOrigin = { ...this.origin, ...options.origin };
|
|
817
|
+
return new _Storyteller({
|
|
818
|
+
audience: this.audience,
|
|
819
|
+
// Captured now, so a parent that finishes first does not orphan its chapters
|
|
820
|
+
parentStoryId: this.storyId,
|
|
821
|
+
...Object.keys(mergedOrigin).length ? { origin: mergedOrigin } : {},
|
|
822
|
+
narration: options.narration ?? this.narration,
|
|
823
|
+
level: options.level ?? this.minimumLevel,
|
|
824
|
+
onAudienceError: options.onAudienceError ?? this.onAudienceError,
|
|
825
|
+
maxInFlight: options.maxInFlight ?? this.maxInFlight
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Report a beat of the current story.
|
|
830
|
+
*
|
|
831
|
+
* In collected narration the beat is buffered and leaves with the story. In live
|
|
832
|
+
* narration it is emitted the moment you call this, so whoever is tuned in sees
|
|
833
|
+
* the work as it happens.
|
|
834
|
+
*
|
|
835
|
+
* Accepts anything, not just a string — pass an error, an API response, a Map, a
|
|
836
|
+
* class instance — and the value is normalized into a storable shape with the note
|
|
837
|
+
* text derived from it.
|
|
838
|
+
*
|
|
839
|
+
* @param input - What happened: a message, or any value to describe
|
|
840
|
+
* @param data - Optional context: who did it, what was involved, where it happened, any error
|
|
841
|
+
* @returns `this` for chaining
|
|
842
|
+
*
|
|
843
|
+
* @example
|
|
844
|
+
* ```ts
|
|
845
|
+
* story.report("Card charged", { what: { amount: "$42" }, where: "stripe" });
|
|
846
|
+
* story.report(await response.json());
|
|
847
|
+
* ```
|
|
848
|
+
*/
|
|
849
|
+
report(input, data = {}) {
|
|
850
|
+
const described = describeInput(input);
|
|
851
|
+
const level = toStoryLevel(data.level);
|
|
852
|
+
const note = {
|
|
853
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
854
|
+
sequence: this.nextSequence,
|
|
855
|
+
note: described.text,
|
|
856
|
+
...level !== "Information" ? { level } : {},
|
|
857
|
+
...data.who !== void 0 ? { who: normalizeValue(data.who) } : {},
|
|
858
|
+
...data.what !== void 0 ? { what: normalizeValue(data.what) } : described.what !== void 0 ? { what: described.what } : {},
|
|
859
|
+
...data.where !== void 0 ? { where: normalizeValue(data.where) } : {},
|
|
860
|
+
...data.error !== void 0 ? { error: normalizeError(data.error) } : described.error !== void 0 ? { error: described.error } : {}
|
|
861
|
+
};
|
|
862
|
+
this.nextSequence += 1;
|
|
863
|
+
this.notes.push(note);
|
|
864
|
+
if (this.narration === "live" || data.live) {
|
|
865
|
+
this.emitNote(note, level, data.to);
|
|
866
|
+
}
|
|
867
|
+
return this;
|
|
868
|
+
}
|
|
869
|
+
/** Clear all accumulated notes without emitting a story, and start a new story id */
|
|
870
|
+
reset() {
|
|
871
|
+
this.notes = [];
|
|
872
|
+
this.startNewStory();
|
|
873
|
+
return this;
|
|
874
|
+
}
|
|
875
|
+
/** Preview the current notes as a formatted report without emitting or clearing them */
|
|
876
|
+
summarize(options = {}) {
|
|
877
|
+
const {
|
|
878
|
+
title = "Story preview",
|
|
879
|
+
level = "Information",
|
|
880
|
+
error,
|
|
881
|
+
...reportOptions
|
|
882
|
+
} = options;
|
|
883
|
+
const event = {
|
|
884
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
885
|
+
level,
|
|
886
|
+
title,
|
|
887
|
+
storyId: this.storyId,
|
|
888
|
+
...this.parentStoryId !== void 0 ? { parentStoryId: this.parentStoryId } : {},
|
|
889
|
+
...this.origin ? { origin: this.origin } : {},
|
|
890
|
+
notes: [...this.notes],
|
|
891
|
+
...error !== void 0 ? { error: normalizeError(error) } : {}
|
|
892
|
+
};
|
|
893
|
+
return formatStory(event, reportOptions);
|
|
894
|
+
}
|
|
895
|
+
/**
|
|
896
|
+
* Finish the story: emit everything collected so far as one record, and start fresh.
|
|
897
|
+
*
|
|
898
|
+
* @param title - What the story was about
|
|
899
|
+
* @param options - Level, and the error that ended it
|
|
900
|
+
* @returns A one-shot handle whose `.to()` overrides the audience list — call it
|
|
901
|
+
* synchronously, delivery happens on the next microtask
|
|
902
|
+
*
|
|
903
|
+
* @example
|
|
904
|
+
* ```ts
|
|
905
|
+
* story.finish("Sync complete");
|
|
906
|
+
* story.finish("Sync failed", { level: "oops", error }).to("db");
|
|
907
|
+
* ```
|
|
908
|
+
*/
|
|
909
|
+
finish(title, options = {}) {
|
|
910
|
+
return this.createDelivery(toStoryLevel(options.level), title, options.error);
|
|
911
|
+
}
|
|
912
|
+
/** @deprecated Use `finish(title)`. Removed at 1.0. */
|
|
913
|
+
tell(title) {
|
|
914
|
+
warnDeprecated("tell", "finish");
|
|
915
|
+
return this.createDelivery("Information", title);
|
|
916
|
+
}
|
|
917
|
+
/** @deprecated Use `finish(title, { level: "warn" })`. Removed at 1.0. */
|
|
918
|
+
warn(title) {
|
|
919
|
+
warnDeprecated("warn", 'finish(title, { level: "warn" })');
|
|
920
|
+
return this.createDelivery("Warning", title);
|
|
921
|
+
}
|
|
922
|
+
/** @deprecated Use `finish(title, { level: "oops", error })`. Removed at 1.0. */
|
|
923
|
+
oops(title, error) {
|
|
924
|
+
warnDeprecated("oops", 'finish(title, { level: "oops", error })');
|
|
925
|
+
return this.createDelivery("Error", title, error);
|
|
926
|
+
}
|
|
927
|
+
/** @deprecated Use `report()`. Removed at 1.0. */
|
|
928
|
+
note(input, data = {}) {
|
|
929
|
+
warnDeprecated("note", "report");
|
|
930
|
+
return this.report(input, data);
|
|
931
|
+
}
|
|
932
|
+
/** Emit a single note to the audiences listening for notes */
|
|
933
|
+
emitNote(note, level, only) {
|
|
934
|
+
const emission = {
|
|
935
|
+
...note,
|
|
936
|
+
kind: "note",
|
|
937
|
+
storyId: this.storyId,
|
|
938
|
+
...this.parentStoryId !== void 0 ? { parentStoryId: this.parentStoryId } : {},
|
|
939
|
+
sequence: note.sequence ?? 0,
|
|
940
|
+
level,
|
|
941
|
+
...this.origin ? { origin: this.origin } : {}
|
|
942
|
+
};
|
|
943
|
+
void this.deliver(emission, only ? { only } : {});
|
|
944
|
+
}
|
|
945
|
+
/** Build a story event and schedule delivery, returning a handle to override the audience list */
|
|
946
|
+
createDelivery(level, title, error) {
|
|
947
|
+
const event = this.buildEvent(level, title, error);
|
|
948
|
+
let delivered = false;
|
|
949
|
+
let defaultCancelled = false;
|
|
950
|
+
queueMicrotask(() => {
|
|
951
|
+
if (delivered || defaultCancelled) return;
|
|
952
|
+
delivered = true;
|
|
953
|
+
void this.deliver(event);
|
|
954
|
+
});
|
|
955
|
+
return {
|
|
956
|
+
to: (...names) => {
|
|
957
|
+
defaultCancelled = true;
|
|
958
|
+
if (delivered) return;
|
|
959
|
+
delivered = true;
|
|
960
|
+
void this.deliver(event, { only: names });
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
}
|
|
964
|
+
/** Assemble the story event from current notes and start a fresh story */
|
|
965
|
+
buildEvent(level, title, error) {
|
|
966
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
967
|
+
const storyId = this.storyId;
|
|
968
|
+
const droppedEmissions = this.droppedEmissions;
|
|
969
|
+
const sortedNotes = [...this.notes].sort(
|
|
970
|
+
(noteA, noteB) => Date.parse(noteA.timestamp) - Date.parse(noteB.timestamp) || (noteA.sequence ?? 0) - (noteB.sequence ?? 0)
|
|
971
|
+
);
|
|
972
|
+
this.notes = [];
|
|
973
|
+
this.startNewStory();
|
|
974
|
+
const durationMs = calculateNoteDuration2(sortedNotes).durationMs;
|
|
975
|
+
const event = {
|
|
976
|
+
timestamp: now,
|
|
977
|
+
level,
|
|
978
|
+
title,
|
|
979
|
+
storyId,
|
|
980
|
+
...this.parentStoryId !== void 0 ? { parentStoryId: this.parentStoryId } : {},
|
|
981
|
+
...this.origin ? { origin: this.origin } : {},
|
|
982
|
+
notes: sortedNotes,
|
|
983
|
+
...durationMs != null ? { durationMs } : {},
|
|
984
|
+
...droppedEmissions ? { droppedEmissions } : {},
|
|
985
|
+
...error !== void 0 ? { error: normalizeError(error) } : {}
|
|
986
|
+
};
|
|
987
|
+
const eventWithSummary = event;
|
|
988
|
+
Object.defineProperty(eventWithSummary, "kind", {
|
|
989
|
+
value: "story",
|
|
990
|
+
enumerable: true
|
|
991
|
+
});
|
|
992
|
+
Object.defineProperty(eventWithSummary, "summarize", {
|
|
993
|
+
value: (options) => formatStory(event, options),
|
|
994
|
+
enumerable: false
|
|
995
|
+
});
|
|
996
|
+
return eventWithSummary;
|
|
997
|
+
}
|
|
998
|
+
/** Begin a new story: fresh id, sequence back to zero */
|
|
999
|
+
startNewStory() {
|
|
1000
|
+
this.storyId = createStoryId();
|
|
1001
|
+
this.nextSequence = 0;
|
|
1002
|
+
this.droppedEmissions = 0;
|
|
1003
|
+
}
|
|
1004
|
+
/** Deliver an emission to the audience members listening for its kind */
|
|
1005
|
+
async deliver(emission, options) {
|
|
1006
|
+
if (!meetsLevel(emission.level, this.minimumLevel)) return;
|
|
1007
|
+
const targets = options?.only?.length ? this.audience.getOnly(options.only) : this.audience.getAll();
|
|
1008
|
+
await Promise.all(
|
|
1009
|
+
targets.filter((member) => hearsKind(member, emission.kind)).filter((member) => this.acceptsSafely(member, emission)).map((member) => this.hearSafely(member, emission))
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
/** Run an audience's accepts() without letting a throw from it lose the emission */
|
|
1013
|
+
acceptsSafely(member, emission) {
|
|
1014
|
+
if (!member.accepts) return true;
|
|
1015
|
+
try {
|
|
1016
|
+
return member.accepts(emission);
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
this.handleAudienceError(error, member, emission);
|
|
1019
|
+
return false;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Hand an emission to one audience, keeping its failures and its slowness
|
|
1024
|
+
* contained: a throw is reported rather than swallowed, and a backlog is dropped
|
|
1025
|
+
* rather than grown without limit.
|
|
1026
|
+
*/
|
|
1027
|
+
async hearSafely(member, emission) {
|
|
1028
|
+
const pending = this.inFlight.get(member.name) ?? 0;
|
|
1029
|
+
if (pending >= this.maxInFlight) {
|
|
1030
|
+
this.droppedEmissions += 1;
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
this.inFlight.set(member.name, pending + 1);
|
|
1034
|
+
try {
|
|
1035
|
+
await member.hear(emission);
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
this.handleAudienceError(error, member, emission);
|
|
1038
|
+
} finally {
|
|
1039
|
+
const remaining = (this.inFlight.get(member.name) ?? 1) - 1;
|
|
1040
|
+
if (remaining > 0) this.inFlight.set(member.name, remaining);
|
|
1041
|
+
else this.inFlight.delete(member.name);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
/** Report an audience failure without ever letting it reach caller code */
|
|
1045
|
+
handleAudienceError(error, member, emission) {
|
|
1046
|
+
try {
|
|
1047
|
+
this.onAudienceError(error, member, emission);
|
|
1048
|
+
} catch {
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
var warnedDeprecations = /* @__PURE__ */ new Set();
|
|
1053
|
+
function warnDeprecated(oldName, replacement) {
|
|
1054
|
+
if (warnedDeprecations.has(oldName)) return;
|
|
1055
|
+
if (readEnvironmentValue("STORYTELLER_DEPRECATION_WARNINGS") !== "1") return;
|
|
1056
|
+
warnedDeprecations.add(oldName);
|
|
1057
|
+
console.warn(
|
|
1058
|
+
`Storyteller: ${oldName}() is deprecated and will be removed at 1.0 \u2014 use ${replacement}.`
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
var DEFAULT_MAX_IN_FLIGHT = 1e3;
|
|
1062
|
+
var AUDIENCE_ERROR_THROTTLE_MS = 5e3;
|
|
1063
|
+
var lastReportedAudienceError = /* @__PURE__ */ new Map();
|
|
1064
|
+
function reportAudienceErrorToConsole(error, member, emission) {
|
|
1065
|
+
const now = Date.now();
|
|
1066
|
+
const lastReported = lastReportedAudienceError.get(member.name);
|
|
1067
|
+
if (lastReported !== void 0 && now - lastReported < AUDIENCE_ERROR_THROTTLE_MS) {
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
lastReportedAudienceError.set(member.name, now);
|
|
1071
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1072
|
+
console.error(
|
|
1073
|
+
`Storyteller: audience "${member.name}" failed to hear a ${emission.kind} \u2014 ${reason}`
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
1076
|
+
function hearsKind(member, kind) {
|
|
1077
|
+
const kinds = member.hears ?? ["story"];
|
|
1078
|
+
return kinds.includes(kind);
|
|
1079
|
+
}
|
|
1080
|
+
function resolveNarration(requested) {
|
|
1081
|
+
const value = requested ?? readEnvironmentValue("STORYTELLER_NARRATION");
|
|
1082
|
+
if (value === "live" || value === "both") return "live";
|
|
1083
|
+
return "collected";
|
|
1084
|
+
}
|
|
1085
|
+
function createStoryId() {
|
|
1086
|
+
try {
|
|
1087
|
+
const runtimeCrypto = globalThis.crypto;
|
|
1088
|
+
if (typeof runtimeCrypto?.randomUUID === "function") {
|
|
1089
|
+
return runtimeCrypto.randomUUID();
|
|
1090
|
+
}
|
|
1091
|
+
} catch {
|
|
1092
|
+
}
|
|
1093
|
+
return `story-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
1094
|
+
}
|
|
1095
|
+
function normalizeOrigin(origin) {
|
|
1096
|
+
if (!origin) return void 0;
|
|
1097
|
+
const normalized = {
|
|
1098
|
+
...origin.who !== void 0 ? { who: normalizeValue(origin.who) } : {},
|
|
1099
|
+
...origin.what !== void 0 ? { what: normalizeValue(origin.what) } : {},
|
|
1100
|
+
...origin.where !== void 0 ? { where: normalizeValue(origin.where) } : {}
|
|
1101
|
+
};
|
|
1102
|
+
return Object.keys(normalized).length ? normalized : void 0;
|
|
1103
|
+
}
|
|
1104
|
+
function describeInput(input) {
|
|
1105
|
+
if (typeof input === "string") return { text: input };
|
|
1106
|
+
if (input instanceof Error) {
|
|
1107
|
+
const error = normalizeError(input);
|
|
1108
|
+
const label = [error.name, error.message].filter(Boolean).join(": ");
|
|
1109
|
+
return { text: label || "Error", error };
|
|
1110
|
+
}
|
|
1111
|
+
if (input === null) return { text: "null" };
|
|
1112
|
+
if (input === void 0) return { text: "undefined" };
|
|
1113
|
+
const normalized = normalizeValue(input);
|
|
1114
|
+
if (typeof normalized !== "object" || normalized === null) {
|
|
1115
|
+
return { text: String(normalized), what: normalized };
|
|
1116
|
+
}
|
|
1117
|
+
if (Array.isArray(normalized)) {
|
|
1118
|
+
return { text: `Array(${normalized.length})`, what: normalized };
|
|
1119
|
+
}
|
|
1120
|
+
for (const key of ["message", "title", "name", "summary", "event"]) {
|
|
1121
|
+
const candidate = normalized[key];
|
|
1122
|
+
if (typeof candidate === "string" && candidate.length) {
|
|
1123
|
+
return { text: candidate, what: normalized };
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
const typeName = normalized["@type"];
|
|
1127
|
+
return {
|
|
1128
|
+
text: typeof typeName === "string" ? typeName : "Object",
|
|
1129
|
+
what: normalized
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
function calculateNoteDuration2(notes) {
|
|
1133
|
+
if (notes.length <= 1) {
|
|
1134
|
+
return {
|
|
1135
|
+
durationMs: void 0
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
const startTime = Date.parse(notes[0].timestamp);
|
|
1139
|
+
const endTime = Date.parse(notes[notes.length - 1].timestamp);
|
|
1140
|
+
return {
|
|
1141
|
+
durationMs: Number.isFinite(startTime) && Number.isFinite(endTime) ? Math.max(0, endTime - startTime) : void 0
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
|
|
344
1145
|
// src/useStoryteller.ts
|
|
345
1146
|
var sharedInstance;
|
|
346
1147
|
function useStoryteller(options = {}) {
|
|
347
1148
|
if (!sharedInstance || options.reset) {
|
|
348
|
-
sharedInstance = new Storyteller({
|
|
1149
|
+
sharedInstance = new Storyteller({
|
|
1150
|
+
...options.origin !== void 0 ? { origin: options.origin } : {},
|
|
1151
|
+
...options.narration !== void 0 ? { narration: options.narration } : {}
|
|
1152
|
+
});
|
|
349
1153
|
return sharedInstance;
|
|
350
1154
|
}
|
|
351
1155
|
return sharedInstance;
|
|
@@ -355,9 +1159,11 @@ function useStoryteller(options = {}) {
|
|
|
355
1159
|
function dbAudience(insert) {
|
|
356
1160
|
return {
|
|
357
1161
|
name: "db",
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
1162
|
+
hears: ["story"],
|
|
1163
|
+
accepts: (emission) => emission.kind === "story" && (emission.level === "Warning" || emission.level === "Error"),
|
|
1164
|
+
hear: async (emission) => {
|
|
1165
|
+
if (emission.kind !== "story") return;
|
|
1166
|
+
await insert(emission);
|
|
361
1167
|
}
|
|
362
1168
|
};
|
|
363
1169
|
}
|
|
@@ -367,10 +1173,10 @@ function writeStoryReport(stories, options = {}) {
|
|
|
367
1173
|
const {
|
|
368
1174
|
timezone = Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
369
1175
|
locale = "en-US",
|
|
370
|
-
|
|
371
|
-
|
|
1176
|
+
detail = "normal",
|
|
1177
|
+
noteLimit = 50,
|
|
372
1178
|
showData = true,
|
|
373
|
-
|
|
1179
|
+
colors = true
|
|
374
1180
|
} = options;
|
|
375
1181
|
if (!stories.length) {
|
|
376
1182
|
return "Storyteller Report\n\n(no stories)\n";
|
|
@@ -407,17 +1213,17 @@ function writeStoryReport(stories, options = {}) {
|
|
|
407
1213
|
for (const [day, dayStories] of storiesByDay) {
|
|
408
1214
|
lines.push(day);
|
|
409
1215
|
for (const story of dayStories) {
|
|
410
|
-
const
|
|
1216
|
+
const report = formatStory(story, {
|
|
411
1217
|
timezone,
|
|
412
1218
|
locale,
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
1219
|
+
detail,
|
|
1220
|
+
noteLimit,
|
|
1221
|
+
colors
|
|
416
1222
|
});
|
|
417
|
-
const { data } =
|
|
1223
|
+
const { data } = report;
|
|
418
1224
|
const originLabel = formatOrigin(story.origin);
|
|
419
1225
|
const levelColor = getLevelColor(story.level);
|
|
420
|
-
const label = (text) =>
|
|
1226
|
+
const label = (text) => colors ? `${levelColor}${text}${ANSI.reset}` : text;
|
|
421
1227
|
const duration = data.duration ? ` (${data.duration})` : "";
|
|
422
1228
|
lines.push(`${label("Story")}: ${story.title}`);
|
|
423
1229
|
lines.push(`${label("Level")}: ${story.level}`);
|
|
@@ -432,10 +1238,10 @@ function writeStoryReport(stories, options = {}) {
|
|
|
432
1238
|
].filter(Boolean).join(": ");
|
|
433
1239
|
if (errorLine) lines.push(`${label("Error")}: ${errorLine}`);
|
|
434
1240
|
}
|
|
435
|
-
if (
|
|
1241
|
+
if (detail !== "brief" && data.notes.length) {
|
|
436
1242
|
lines.push(` ${label("Notes")}:`);
|
|
437
|
-
for (const
|
|
438
|
-
lines.push(` ${
|
|
1243
|
+
for (const reportNote of data.notes) {
|
|
1244
|
+
lines.push(` ${reportNote.when} \u2014 ${reportNote.text}`);
|
|
439
1245
|
}
|
|
440
1246
|
if (story.notes.length > data.notes.length) {
|
|
441
1247
|
lines.push(
|
|
@@ -446,7 +1252,7 @@ function writeStoryReport(stories, options = {}) {
|
|
|
446
1252
|
if (showData) {
|
|
447
1253
|
lines.push(`${label("Data")}:`);
|
|
448
1254
|
const json = JSON.stringify(data, null, 2);
|
|
449
|
-
if (
|
|
1255
|
+
if (colors) {
|
|
450
1256
|
const colored = colorizeJsonSections(json, {
|
|
451
1257
|
base: ANSI.grayLight,
|
|
452
1258
|
notes: ANSI.grayDark,
|
|
@@ -464,14 +1270,27 @@ function writeStoryReport(stories, options = {}) {
|
|
|
464
1270
|
}
|
|
465
1271
|
export {
|
|
466
1272
|
ANSI,
|
|
1273
|
+
AudienceRegistry,
|
|
1274
|
+
DEFAULT_REDACT_KEYS,
|
|
1275
|
+
REDACTED,
|
|
467
1276
|
Storyteller,
|
|
468
|
-
colorizeJsonSections,
|
|
469
1277
|
consoleAudience,
|
|
470
|
-
countBrackets,
|
|
471
1278
|
dbAudience,
|
|
1279
|
+
formatDuration,
|
|
472
1280
|
formatOrigin,
|
|
1281
|
+
formatStory,
|
|
473
1282
|
getLevelColor,
|
|
1283
|
+
meetsLevel,
|
|
1284
|
+
ndjsonAudience,
|
|
1285
|
+
normalizeError,
|
|
1286
|
+
normalizeValue,
|
|
1287
|
+
readEnvironmentValue,
|
|
1288
|
+
resolveColors,
|
|
1289
|
+
resolveMinimumLevel,
|
|
1290
|
+
resolveOutputFormat,
|
|
1291
|
+
summarizeContext,
|
|
474
1292
|
summarizeStory,
|
|
1293
|
+
toStoryLevel,
|
|
475
1294
|
useStoryteller,
|
|
476
1295
|
writeStoryReport
|
|
477
1296
|
};
|