@zeph-to/cli 2.15.0 → 2.17.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.
@@ -0,0 +1,397 @@
1
+ "use strict";
2
+ /**
3
+ * Claude Code session transcript (`~/.claude/projects/<hash>/<uuid>.jsonl`) →
4
+ * the small events the agent chat timeline draws.
5
+ *
6
+ * Two pure pieces, deliberately split: reading bytes off a growing file, and
7
+ * deciding what of it is worth putting on the wire. Neither touches the socket,
8
+ * so both are testable without tmux, WebSocket, or a clock.
9
+ *
10
+ * Everything here is sized by what a real transcript actually contains, measured
11
+ * 2026-09-08 over 1443 files in `~/.claude/projects`: the largest file was
12
+ * 75.9 MB (p90 1.1 MB) and the longest single line 915,292 bytes. Those two
13
+ * numbers are why the reader starts near the end and refuses oversized lines
14
+ * before parsing them — a tail that starts at byte 0, or that hands a ~1 MB
15
+ * string to `JSON.parse` and only then decides to drop it, has already paid the
16
+ * cost the cap exists to avoid.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.projectTranscriptEntries = exports.readTranscriptDelta = exports.initialTailState = exports.MAX_EVENT_TEXT_CHARS = exports.MAX_EVENT_FIELD_CHARS = exports.MAX_TRANSCRIPT_LINE_CHARS = exports.MAX_TAIL_BYTES_PER_TICK = exports.TRANSCRIPT_BACKFILL_BYTES = void 0;
20
+ const node_fs_1 = require("node:fs");
21
+ /**
22
+ * How far back from the end of the file the first read starts.
23
+ *
24
+ * Not zero: a fresh viewer that sees nothing until the *next* tool call is a
25
+ * blank screen for whoever just picked up their phone mid-turn. Not the whole
26
+ * file either — that is up to 75.9 MB. A fixed window from the end costs the
27
+ * same on an 8 KB transcript and a 76 MB one.
28
+ */
29
+ exports.TRANSCRIPT_BACKFILL_BYTES = 256 * 1024;
30
+ /** Ceiling on one tick's read, so a burst of appends cannot be swallowed whole. */
31
+ exports.MAX_TAIL_BYTES_PER_TICK = 256 * 1024;
32
+ /**
33
+ * A line longer than this is abandoned unparsed, the reader skipping to the next
34
+ * newline. The measured worst case is a 915 KB `tool_result`, whose body this
35
+ * module drops anyway — buffering one to completion would be paying megabytes to
36
+ * learn a tool id.
37
+ */
38
+ exports.MAX_TRANSCRIPT_LINE_CHARS = 1024 * 1024;
39
+ /**
40
+ * Ceiling on a tool's label — its name, and the short string saying what it
41
+ * acted on. A path or a one-line description; anything longer is a runaway
42
+ * input, not a label.
43
+ */
44
+ exports.MAX_EVENT_FIELD_CHARS = 512;
45
+ /**
46
+ * Ceiling on prose — what the agent wrote, and what the person asked.
47
+ *
48
+ * Deliberately far above the label cap: 512 characters is a filename, and
49
+ * applying it here cut every reply off mid-sentence. 5000 is what the
50
+ * completion push already carries for the same text (`zeph-stop.sh`), so the
51
+ * live lane and the push that replaces it agree on how much of a reply is
52
+ * worth sending.
53
+ */
54
+ exports.MAX_EVENT_TEXT_CHARS = 5000;
55
+ /**
56
+ * How much of `buffer[0..read)` ends on a UTF-8 character boundary.
57
+ *
58
+ * A sequence is at most 4 bytes, so at most 3 trailing bytes can belong to a
59
+ * character whose remainder has not arrived. Walk back over continuation bytes
60
+ * (`10xxxxxx`) to the lead byte and ask how long its character should be; if the
61
+ * buffer holds all of it, the read is already complete.
62
+ */
63
+ const completeUtf8Length = (buffer, read) => {
64
+ for (let back = 1; back <= 3 && back <= read; back++) {
65
+ const byte = buffer[read - back];
66
+ if ((byte & 0b1100_0000) === 0b1000_0000)
67
+ continue; // continuation — keep walking
68
+ const width = byte >= 0xf0 ? 4 : byte >= 0xe0 ? 3 : byte >= 0xc0 ? 2 : 1;
69
+ return back >= width ? read : read - back;
70
+ }
71
+ return read;
72
+ };
73
+ /**
74
+ * No state yet. `null` rather than a sentinel-filled record: "never read this
75
+ * file" and "read it and got nothing" are different, and an out-of-band value
76
+ * that has to stay below every real offset is a trap for the next `>=`.
77
+ */
78
+ const initialTailState = () => null;
79
+ exports.initialTailState = initialTailState;
80
+ /**
81
+ * Read what was appended since `prev`, or `null` when there is nothing to do —
82
+ * the file is unreadable, or neither its size nor its mtime moved.
83
+ *
84
+ * `null` is the common case (an idle session) and is deliberately allocation
85
+ * free: no buffer, no string, no array.
86
+ */
87
+ const readTranscriptDelta = (path, prev) => {
88
+ let stat;
89
+ try {
90
+ stat = (0, node_fs_1.statSync)(path);
91
+ }
92
+ catch {
93
+ return null;
94
+ }
95
+ const { size, mtimeMs, ino } = stat;
96
+ // Caught up AND untouched. `offset >= size` matters on its own: a tick that
97
+ // stopped at MAX_TAIL_BYTES_PER_TICK leaves bytes behind on a file that has
98
+ // not changed since, and skipping there would strand them until the next
99
+ // append. `ino` matters here too, not only below: a replacement that matched
100
+ // the old size and mtime would otherwise skip out before the rotation check
101
+ // downstream ever ran.
102
+ if (prev && prev.offset >= size && size === prev.size && mtimeMs === prev.mtimeMs && ino === prev.ino) {
103
+ return null;
104
+ }
105
+ // A new inode at the same path is a different file, and a file that shrank
106
+ // below the offset was truncated in place. Size alone misses the first case:
107
+ // a replacement that happens to be longer than the old read position reads as
108
+ // an ordinary append, and the offset then points into the middle of a record
109
+ // that was never there.
110
+ //
111
+ // Neither covers a `/clear`, which writes a file under a *different* name —
112
+ // the old one simply stops growing. Following that means re-resolving the
113
+ // session id, which is the caller's job (`turn-watch` re-resolves on a
114
+ // cadence) because only it knows which session this path was for.
115
+ //
116
+ // Either way — first attach or rotation — everything remembered about the old
117
+ // contents is meaningless, so the read restarts from a window off the end.
118
+ const restart = !prev || size < prev.offset || ino !== prev.ino;
119
+ const start = restart ? Math.max(0, size - exports.TRANSCRIPT_BACKFILL_BYTES) : prev.offset;
120
+ // A window that starts mid-line yields a fragment of a record that began
121
+ // before it, so that fragment is dropped rather than parsed.
122
+ const truncatedStart = restart && start > 0;
123
+ const carry = restart ? '' : prev.carry;
124
+ let resyncing = restart ? false : prev.resyncing;
125
+ const want = Math.min(size - start, exports.MAX_TAIL_BYTES_PER_TICK);
126
+ if (want <= 0) {
127
+ return {
128
+ lines: [],
129
+ state: { offset: start, size, mtimeMs, ino, carry, resyncing },
130
+ bytesRead: 0,
131
+ droppedLines: 0,
132
+ };
133
+ }
134
+ const buffer = Buffer.allocUnsafe(want);
135
+ let read = 0;
136
+ let fd;
137
+ try {
138
+ fd = (0, node_fs_1.openSync)(path, 'r');
139
+ read = (0, node_fs_1.readSync)(fd, buffer, 0, want, start);
140
+ }
141
+ catch {
142
+ return null;
143
+ }
144
+ finally {
145
+ if (fd !== undefined)
146
+ (0, node_fs_1.closeSync)(fd);
147
+ }
148
+ // A tick that stops at MAX_TAIL_BYTES_PER_TICK can land inside a multi-byte
149
+ // character. Decoding to that boundary turns both halves into U+FFFD, and the
150
+ // line then fails JSON.parse and vanishes without even being counted as
151
+ // dropped — a silent hole that only shows up on non-ASCII transcripts under
152
+ // bursty appends. Back the read up to the last complete sequence instead; the
153
+ // bytes are not lost, the next tick starts on them.
154
+ const usable = read < want ? read : completeUtf8Length(buffer, read);
155
+ const chunk = buffer.toString('utf8', 0, usable);
156
+ const lines = [];
157
+ let droppedLines = 0;
158
+ let pending = carry;
159
+ let cursor = 0;
160
+ while (cursor <= chunk.length) {
161
+ const nl = chunk.indexOf('\n', cursor);
162
+ if (nl === -1) {
163
+ pending += chunk.slice(cursor);
164
+ break;
165
+ }
166
+ const complete = pending + chunk.slice(cursor, nl);
167
+ cursor = nl + 1;
168
+ pending = '';
169
+ if (resyncing) {
170
+ // This newline ends the oversized line; the next one starts clean.
171
+ resyncing = false;
172
+ continue;
173
+ }
174
+ if (complete.length > exports.MAX_TRANSCRIPT_LINE_CHARS) {
175
+ droppedLines += 1;
176
+ continue;
177
+ }
178
+ lines.push(complete);
179
+ }
180
+ // The window began mid-line, so whatever came before the first newline is a
181
+ // fragment of a record that started before `start`.
182
+ if (truncatedStart && lines.length > 0)
183
+ lines.shift();
184
+ if (pending.length > exports.MAX_TRANSCRIPT_LINE_CHARS) {
185
+ // Still no newline and already past the cap — stop growing the buffer and
186
+ // discard bytes until the line ends. Holding it to completion would cost
187
+ // megabytes for a record whose body never reaches the wire anyway.
188
+ droppedLines += 1;
189
+ pending = '';
190
+ resyncing = true;
191
+ }
192
+ return {
193
+ lines,
194
+ state: { offset: start + usable, size, mtimeMs, ino, carry: pending, resyncing },
195
+ bytesRead: usable,
196
+ droppedLines,
197
+ };
198
+ };
199
+ exports.readTranscriptDelta = readTranscriptDelta;
200
+ /**
201
+ * Which input field reads as "what this call is about". Ordered, first match
202
+ * wins: `description` leads because it is the sentence a human wrote about the
203
+ * call, which is exactly what a collapsed timeline row wants.
204
+ *
205
+ * `command` is deliberately absent. It would only ever be reached for a Bash
206
+ * call with no `description`, and measured over the 60 most recent transcripts
207
+ * that is 0 of 2985 calls — while a command line is the single field here most
208
+ * likely to carry a secret (an `Authorization` header, a token in a URL, a
209
+ * profile name). Zero measured value against the worst downside on the list.
210
+ *
211
+ * This list is Claude Code's vocabulary. Another agent names its inputs
212
+ * differently, so a second agent means a second projector, not a longer list —
213
+ * see the note on `projectTranscriptEntries`.
214
+ */
215
+ const TARGET_KEYS = ['description', 'file_path', 'path', 'pattern', 'query', 'url', 'skill'];
216
+ const clamp = (value, max = exports.MAX_EVENT_FIELD_CHARS) => value.length > max ? value.slice(0, max) : value;
217
+ const targetOf = (input) => {
218
+ if (!input || typeof input !== 'object')
219
+ return undefined;
220
+ const record = input;
221
+ for (const key of TARGET_KEYS) {
222
+ const value = record[key];
223
+ if (typeof value === 'string' && value.trim())
224
+ return clamp(value.trim());
225
+ }
226
+ return undefined;
227
+ };
228
+ const contentBlocks = (entry) => {
229
+ const message = entry.message;
230
+ if (!message || typeof message !== 'object')
231
+ return [];
232
+ const content = message.content;
233
+ if (!Array.isArray(content))
234
+ return [];
235
+ return content.filter((b) => !!b && typeof b === 'object');
236
+ };
237
+ /**
238
+ * A user entry whose content is a plain string is something the person typed.
239
+ * One carrying an array of `tool_result` blocks is the harness answering the
240
+ * model, and counting it as a prompt would cut every turn into fragments —
241
+ * the same distinction `plugin/hooks/zeph-stop.sh` draws to scope a turn.
242
+ */
243
+ /**
244
+ * Harness plumbing that Claude Code writes into the conversation as if a person
245
+ * had said it: background-task notifications, injected reminders, the caveat
246
+ * wrapper around a slash command's own output.
247
+ *
248
+ * Dropping these is not cosmetic. A `<task-notification>` rendered as a prompt
249
+ * bubble puts internal ids, tool-use ids and whatever a tool reported on a
250
+ * phone screen, attributed to the user — and there is no bound on what a future
251
+ * notification carries.
252
+ */
253
+ const SYNTHETIC_BLOCKS = /<(task-notification|system-reminder|local-command-caveat|local-command-stdout|command-message|command-name|command-args)>[\s\S]*?<\/\1>/g;
254
+ /** `<command-name>/simplify</command-name>` — the one wrapper worth keeping, as what it names. */
255
+ const COMMAND_NAME = /<command-name>([^<]*)<\/command-name>/;
256
+ /**
257
+ * Whether this entry is a person speaking.
258
+ *
259
+ * `origin.kind` is the honest answer where Claude Code writes it: `human` for
260
+ * both a typed message and a slash command, something else for everything the
261
+ * harness injects. Absent — an older version — it falls through to the tag
262
+ * check below, so a build that does not stamp provenance still cannot leak a
263
+ * notification it happens to phrase as a user turn.
264
+ */
265
+ const isHumanTurn = (entry) => {
266
+ if (entry.promptSource === 'system')
267
+ return false;
268
+ const origin = entry.origin;
269
+ if (origin && typeof origin === 'object') {
270
+ const kind = origin.kind;
271
+ if (typeof kind === 'string')
272
+ return kind === 'human';
273
+ }
274
+ return true;
275
+ };
276
+ const promptTextOf = (entry) => {
277
+ if (entry.type !== 'user' || entry.isMeta)
278
+ return null;
279
+ if (!isHumanTurn(entry))
280
+ return null;
281
+ const message = entry.message;
282
+ if (!message || typeof message !== 'object')
283
+ return null;
284
+ const content = message.content;
285
+ if (typeof content === 'string')
286
+ return nonEmpty(stripAttachmentMarkers(content));
287
+ // An array is usually the harness answering the model, but the same shape
288
+ // carries what a person typed when they attached something — the prompt
289
+ // arrives as text blocks beside the attachment. Take those; a message with
290
+ // no text block at all is a pure tool_result carrier and not a prompt.
291
+ if (!Array.isArray(content))
292
+ return null;
293
+ const text = content
294
+ .filter((b) => !!b && typeof b === 'object' && b.type === 'text')
295
+ .map((b) => (typeof b.text === 'string' ? b.text : ''))
296
+ .join('\n')
297
+ .trim();
298
+ return text ? nonEmpty(stripAttachmentMarkers(text)) : null;
299
+ };
300
+ /**
301
+ * Drop the `[Image: source: /abs/path]` markers Claude Code substitutes for an
302
+ * attached image.
303
+ *
304
+ * They are a local filesystem path and nothing else: useless on the phone, which
305
+ * cannot open it, and a directory listing of this machine if it goes anywhere
306
+ * else. The attachment itself is not on this wire — showing that one exists is a
307
+ * separate feature, not a reason to ship the path.
308
+ */
309
+ const stripAttachmentMarkers = (text) => {
310
+ const command = text.match(COMMAND_NAME)?.[1]?.trim();
311
+ const body = text.replace(SYNTHETIC_BLOCKS, '');
312
+ // A slash command is a person's turn, and its name is the whole of what they
313
+ // said — the wrapper around it is not.
314
+ const withCommand = command ? `${command}\n${body}` : body;
315
+ return withCommand.replace(/\[Image:[^\]]*\]/g, '').replace(/\n{3,}/g, '\n\n');
316
+ };
317
+ /** Trimmed, or null when nothing survived — a message that was only an attachment. */
318
+ const nonEmpty = (text) => {
319
+ const trimmed = text.trim();
320
+ return trimmed ? trimmed : null;
321
+ };
322
+ /**
323
+ * Turn raw JSONL lines into wire events.
324
+ *
325
+ * Nothing a tool read or wrote survives this function: only the tool's name, a
326
+ * short label for what it acted on, and whether it worked. That is the whole
327
+ * privacy story of the feature — the transcript holds file contents and command
328
+ * output, and this is the one place that decides none of it leaves the machine.
329
+ *
330
+ * `sinceLastPrompt` is for the backfill window: finished turns already exist in
331
+ * the chat as their completion pushes, so replaying them would double every
332
+ * message. Only the turn still in flight is new information.
333
+ *
334
+ * The prompt a person typed IS carried, unlike anything a tool read or wrote.
335
+ * It is their own words, it is the same class of content the completion push
336
+ * already sends, and without it the timeline is a list of tool names with no
337
+ * record of what was asked.
338
+ *
339
+ * Everything here — the block shapes, the entry types, `TARGET_KEYS` — is Claude
340
+ * Code's transcript format. Supporting another agent (pi, Codex) means a
341
+ * projector of its own alongside this one, reached the way `REMOTE_AGENTS`
342
+ * already reaches per-agent session resolvers; `turn-watch` takes the reader as
343
+ * a dependency and needs no change for it.
344
+ */
345
+ const projectTranscriptEntries = (lines, opts = {}) => {
346
+ const events = [];
347
+ for (const raw of lines) {
348
+ let entry;
349
+ try {
350
+ const parsed = JSON.parse(raw);
351
+ if (!parsed || typeof parsed !== 'object')
352
+ continue;
353
+ entry = parsed;
354
+ }
355
+ catch {
356
+ continue;
357
+ }
358
+ // A subagent's own tool calls belong to the Task/Agent call that spawned
359
+ // it, which the parent transcript already shows as one row.
360
+ if (entry.isSidechain)
361
+ continue;
362
+ const at = typeof entry.timestamp === 'string' ? entry.timestamp : undefined;
363
+ const prompt = promptTextOf(entry);
364
+ if (prompt !== null) {
365
+ events.push({ kind: 'prompt', text: clamp(prompt, exports.MAX_EVENT_TEXT_CHARS), ...(at ? { at } : {}) });
366
+ continue;
367
+ }
368
+ for (const block of contentBlocks(entry)) {
369
+ if (block.type === 'tool_use' && typeof block.id === 'string' && typeof block.name === 'string') {
370
+ const target = targetOf(block.input);
371
+ events.push({ kind: 'tool', id: block.id, name: clamp(block.name), ...(target ? { target } : {}), ...(at ? { at } : {}) });
372
+ continue;
373
+ }
374
+ if (block.type === 'tool_result' && typeof block.tool_use_id === 'string') {
375
+ events.push({ kind: 'tool_result', id: block.tool_use_id, ok: block.is_error !== true, ...(at ? { at } : {}) });
376
+ continue;
377
+ }
378
+ // Prose, only from the model. A user entry reaching here has already
379
+ // been offered to `promptTextOf`; treating its text blocks as
380
+ // assistant output is how an attachment marker ends up rendered as
381
+ // the agent's own words.
382
+ if (entry.type === 'assistant' &&
383
+ block.type === 'text' &&
384
+ typeof block.text === 'string' &&
385
+ block.text.trim()) {
386
+ events.push({ kind: 'text', text: clamp(block.text, exports.MAX_EVENT_TEXT_CHARS), ...(at ? { at } : {}) });
387
+ }
388
+ // `thinking` falls through on purpose — the timeline shows what the
389
+ // agent did, not what it considered.
390
+ }
391
+ }
392
+ if (!opts.sinceLastPrompt)
393
+ return events;
394
+ const lastPrompt = events.map((e) => e.kind).lastIndexOf('prompt');
395
+ return lastPrompt === -1 ? events : events.slice(lastPrompt);
396
+ };
397
+ exports.projectTranscriptEntries = projectTranscriptEntries;
@@ -0,0 +1,85 @@
1
+ import type { TurnEvent } from './transcript-tail.js';
2
+ /**
3
+ * What the daemon has already sent a chat viewer, kept on disk so a
4
+ * re-subscribe can replay it.
5
+ *
6
+ * Why this exists at all: the live lane is ephemeral, and the transcript
7
+ * backfill deliberately replays only since the last prompt — finished turns are
8
+ * supposed to already exist in the chat as their completion pushes. Under the
9
+ * `quiet` push dial they do not exist, because that dial suppresses every
10
+ * auto-push without a `high` marker. A turn's closing words were therefore
11
+ * visible while it ran and gone the moment the viewer re-armed, with no record
12
+ * on the phone, on the server, or in the 256KB backfill window.
13
+ *
14
+ * Re-deriving those turns from the transcript is not an option: measured over
15
+ * this machine's transcripts larger than the window, the last 256KB holds a
16
+ * median of one human prompt and none at all in 8 of 19 files — a single
17
+ * `tool_result` line can be most of a megabyte. Projected events are a tool
18
+ * name, a short label and a verdict, so the same bytes hold turns rather than
19
+ * fragments of one.
20
+ *
21
+ * It lives beside `known-sessions.json` under `stateDir()` rather than in
22
+ * `~/.zeph`, for the same reason that file does: this is derived session state
23
+ * that expires, not configuration or keys.
24
+ *
25
+ * Everything here is best-effort. A ring that cannot be written costs the
26
+ * scrollback, never the live send — the daemon's job is to keep talking.
27
+ */
28
+ /**
29
+ * One ring file. Trimming is by bytes and not by event count on purpose: a
30
+ * prose event carries up to `MAX_EVENT_TEXT_CHARS` (5000) while a tool event's
31
+ * label is clamped at `MAX_EVENT_FIELD_CHARS` (512), so a count would bound two
32
+ * things an order of magnitude apart.
33
+ */
34
+ export declare const MAX_TURN_RING_BYTES: number;
35
+ /** Sessions with a ring. Past this the oldest by mtime are dropped. */
36
+ export declare const MAX_TURN_RING_FILES = 20;
37
+ /** A week unwatched. Scrollback older than that is history, not context. */
38
+ export declare const TURN_RING_TTL_MS: number;
39
+ export declare const turnRingDir: () => string;
40
+ /**
41
+ * The session name is chosen by whoever sent the watch request, so it never
42
+ * reaches the filesystem — a hash does. Same move as `hashListenerId`, at twice
43
+ * the width: 64 bits, because these names collide within one machine's
44
+ * directory rather than identifying one device.
45
+ */
46
+ export declare const turnRingPath: (sessionName: string) => string;
47
+ /**
48
+ * Record events this watch just sent. Call it with the same array that went out
49
+ * — the ring's whole meaning is "what the viewer already received", and a replay
50
+ * is that array sent again.
51
+ */
52
+ export declare const appendTurnRing: (sessionName: string, events: readonly TurnEvent[]) => boolean;
53
+ /**
54
+ * What this session's viewers have already been sent, oldest first.
55
+ *
56
+ * An expired ring is deleted here rather than merely ignored: returning an empty
57
+ * array and leaving the file would let the next append land on top of pre-expiry
58
+ * content, and the TTL would bound nothing.
59
+ */
60
+ export declare const readTurnRing: (sessionName: string, now?: number) => TurnEvent[];
61
+ /** Drop expired rings, then the oldest ones past `MAX_TURN_RING_FILES`. */
62
+ export declare const sweepTurnRings: (now?: number) => void;
63
+ /**
64
+ * Forget one session's scrollback outright — the machine was told to forget the
65
+ * session. False when the file is still there, which the caller has to say out
66
+ * loud: answering "forgotten" while a week of prompts and tool targets sits on
67
+ * disk is the one wrong answer here.
68
+ */
69
+ export declare const removeTurnRing: (sessionName: string) => boolean;
70
+ /**
71
+ * The three operations a watcher needs, as one injectable surface.
72
+ *
73
+ * The watcher takes this rather than importing the module so its tests can hold
74
+ * the ring in memory — what the disk does is settled here, and re-proving it
75
+ * through a socket-and-transcript harness would only make those tests slower and
76
+ * no more truthful.
77
+ */
78
+ export interface TurnRing {
79
+ read: (sessionName: string) => TurnEvent[];
80
+ /** False when nothing was written — a full disk, a directory that is not ours. */
81
+ append: (sessionName: string, events: readonly TurnEvent[]) => boolean;
82
+ sweep: () => void;
83
+ }
84
+ export declare const diskTurnRing: TurnRing;
85
+ //# sourceMappingURL=turn-ring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn-ring.d.ts","sourceRoot":"","sources":["../src/turn-ring.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,QAAa,CAAC;AAE9C,uEAAuE;AACvE,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,QAA0B,CAAC;AAExD,eAAO,MAAM,WAAW,QAAO,MAAmC,CAAC;AAEnE;;;;;GAKG;AACH,eAAO,MAAM,YAAY,GAAI,aAAa,MAAM,KAAG,MACoD,CAAC;AAyExG;;;;GAIG;AACH,eAAO,MAAM,cAAc,GAAI,aAAa,MAAM,EAAE,QAAQ,SAAS,SAAS,EAAE,KAAG,OAwBlF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,GAAI,aAAa,MAAM,EAAE,MAAK,MAAmB,KAAG,SAAS,EAkCrF,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,cAAc,GAAI,MAAK,MAAmB,KAAG,IAgCzD,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,aAAa,MAAM,KAAG,OAgBpD,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,WAAW,QAAQ;IACrB,IAAI,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,SAAS,EAAE,CAAC;IAC3C,kFAAkF;IAClF,MAAM,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,SAAS,EAAE,KAAK,OAAO,CAAC;IACvE,KAAK,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,eAAO,MAAM,YAAY,EAAE,QAI1B,CAAC"}