typebulb 0.15.8 → 0.16.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/dist/agents/claude/client.js +212 -209
- package/dist/agents/claude/styles.css +185 -2
- package/dist/format/parse.d.ts +30 -0
- package/dist/format/parse.d.ts.map +1 -1
- package/dist/format/parse.js +41 -1
- package/dist/format/parse.js.map +1 -1
- package/dist/index.js +182 -165
- package/dist/render.js +84 -81
- package/dist/servers.js +46 -46
- package/package.json +105 -105
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
--veil: rgba(0, 0, 0, 0.05); /* one shade step; alpha-stacks as panels nest */
|
|
19
19
|
--tool-bg: var(--panel);
|
|
20
20
|
--err: rgb(206, 60, 60);
|
|
21
|
+
--warn: rgb(176, 120, 12); /* amber — the cache-leak (cost-caution) tier, milder than --err */
|
|
21
22
|
--diff-add: rgb(40, 140, 70);
|
|
22
23
|
--fold-marker: rgb(24, 145, 72);
|
|
23
24
|
/* Prose measure for text (bubbles + notes) — kept to a readable line length.
|
|
@@ -37,6 +38,7 @@ html[data-theme="dark"] {
|
|
|
37
38
|
--veil: rgba(255, 255, 255, 0.06);
|
|
38
39
|
--tool-bg: var(--panel);
|
|
39
40
|
--err: rgb(230, 60, 60);
|
|
41
|
+
--warn: rgb(232, 184, 80); /* amber — the cache-leak (cost-caution) tier, milder than --err */
|
|
40
42
|
--diff-add: rgb(100, 210, 130);
|
|
41
43
|
--fold-marker: rgb(0, 255, 0);
|
|
42
44
|
color-scheme: dark;
|
|
@@ -147,13 +149,13 @@ body {
|
|
|
147
149
|
session list). Wrap = positioning context; the popover anchors bottom/right and
|
|
148
150
|
grows up-and-left — the chips sit in the right-aligned cluster, so left:0 would
|
|
149
151
|
run them off the viewport's right edge. */
|
|
150
|
-
.token-wrap, .sid-wrap, .servers-wrap { position: relative; display: inline-block; }
|
|
152
|
+
.token-wrap, .sid-wrap, .servers-wrap, .model-wrap { position: relative; display: inline-block; }
|
|
151
153
|
/* Each pill renders its wrap unconditionally, emitting an empty div when it has
|
|
152
154
|
nothing to show (no tokens / no session / nothing running). An empty wrap is
|
|
153
155
|
still a flex child, so .statusbar-actions' gap lands on both its sides —
|
|
154
156
|
doubling the visible gap between its neighbours. Collapse the empties so the
|
|
155
157
|
cluster spaces evenly regardless of which pills are present. */
|
|
156
|
-
.token-wrap:empty, .sid-wrap:empty, .servers-wrap:empty { display: none; }
|
|
158
|
+
.token-wrap:empty, .sid-wrap:empty, .servers-wrap:empty, .model-wrap:empty { display: none; }
|
|
157
159
|
.picker, .servers-pop {
|
|
158
160
|
--menu-h: 428px; /* one height for both statusbar menus — they sit side by side, so a
|
|
159
161
|
mismatched cap reads as a glitch; .picker grows to it, .servers-pop
|
|
@@ -209,6 +211,11 @@ body {
|
|
|
209
211
|
/* Shrink-to-fit, so the sweep paces the text's own length, not the container's — on a full-width
|
|
210
212
|
box the band spends most of its run off-glyph (same move as typebulb.com's shimmer.ts). */
|
|
211
213
|
display: inline-block;
|
|
214
|
+
/* background-clip:text paints the glyphs through this element's own box, so the box must be tall
|
|
215
|
+
enough to cover descenders — inheriting the statusbar's line-height:1 clips the "g" in "probing"/
|
|
216
|
+
"working…" at the baseline. A descender-clearing line-height restores the paint area; the button
|
|
217
|
+
centres this box, so the extra height never unbalances the row. */
|
|
218
|
+
line-height: 1.3;
|
|
212
219
|
background-image: linear-gradient(100deg, currentColor 35%, var(--accent) 50%, currentColor 65%) !important;
|
|
213
220
|
-webkit-background-clip: text;
|
|
214
221
|
background-clip: text;
|
|
@@ -296,6 +303,180 @@ body {
|
|
|
296
303
|
color: var(--fg);
|
|
297
304
|
}
|
|
298
305
|
|
|
306
|
+
/* Agent switcher (TB-Agent-Switcher.md) — reuses the .picker popover + rows; just its own width and
|
|
307
|
+
the no-key entry form. The active model is marked with .picker-row.current (the accent dot), like
|
|
308
|
+
the attached session; the "active" tag rides .picker-time's muted right-aligned slot. */
|
|
309
|
+
.model-picker { width: min(380px, calc(100vw - 2rem)); }
|
|
310
|
+
.model-err { color: var(--danger, #f87171); font-size: .76rem; padding: .3rem .55rem; }
|
|
311
|
+
/* Key-entry form: the popover flips to this in place of the list when no key is set yet. */
|
|
312
|
+
.key-entry { gap: .5rem; padding: .7rem; }
|
|
313
|
+
.key-entry-head { font-size: .85rem; color: var(--fg); }
|
|
314
|
+
.key-entry .bulb-filter { width: 100%; }
|
|
315
|
+
.key-entry-acts { display: flex; gap: .4rem; justify-content: flex-end; }
|
|
316
|
+
.key-entry-note { font-size: .72rem; color: var(--muted); line-height: 1.4; }
|
|
317
|
+
|
|
318
|
+
/* Agent switcher watchdog (TB-Agent-Switcher.md). TWO tiers, both surfaced live while the menu is
|
|
319
|
+
closed: RED when an Anthropic model is riding the OpenRouter route (the $-per-turn Opus footgun), and
|
|
320
|
+
AMBER when the route served a turn uncached (the cache flip — a cost leak, pennies-scale, so quieter
|
|
321
|
+
than red so the red stays meaningful). The latch outranks the .on accent (equal specificity → source
|
|
322
|
+
order wins): coloured border + text, the bar's loudest states. */
|
|
323
|
+
.pill.danger, .pill.danger:hover { border-color: var(--err); color: var(--err); }
|
|
324
|
+
.pill.warn, .pill.warn:hover { border-color: var(--warn); color: var(--warn); }
|
|
325
|
+
/* The watchdog banner: rendered by Root just above the statusbar, anchored to the .app box. Centered,
|
|
326
|
+
capped width, opaque so it reads over the message text the strip lets through. Only present when a
|
|
327
|
+
tier is live, so it never costs layout in the common case. .danger is red + a glow ring (loud);
|
|
328
|
+
.warn is amber, no ring (a quieter cost cue). */
|
|
329
|
+
.model-banner {
|
|
330
|
+
position: absolute;
|
|
331
|
+
left: 0; right: 0; bottom: 2.6rem;
|
|
332
|
+
margin: 0 auto;
|
|
333
|
+
max-width: min(640px, calc(100vw - 2rem));
|
|
334
|
+
display: flex;
|
|
335
|
+
align-items: flex-start;
|
|
336
|
+
gap: .5rem;
|
|
337
|
+
padding: .55rem .75rem;
|
|
338
|
+
border-radius: 8px;
|
|
339
|
+
background: var(--bg);
|
|
340
|
+
color: var(--fg);
|
|
341
|
+
font-size: .8rem;
|
|
342
|
+
line-height: 1.4;
|
|
343
|
+
}
|
|
344
|
+
.model-banner.danger { border: 1px solid var(--err); box-shadow: 0 0 0 2px color-mix(in srgb, var(--err) 22%, transparent), 0 4px 14px rgba(0,0,0,.25); }
|
|
345
|
+
.model-banner.warn { border: 1px solid var(--warn); box-shadow: 0 4px 14px rgba(0,0,0,.22); }
|
|
346
|
+
.model-banner-icon { flex: none; }
|
|
347
|
+
.model-banner-text { color: var(--fg); }
|
|
348
|
+
/* L2 predictor: a status line at the top of the model popover saying what the next send will bill.
|
|
349
|
+
Muted by default; red when the live model is the Anthropic-via-OpenRouter footgun. */
|
|
350
|
+
/* The model menu's top section: ONE row — the route headline (ellipsizing) + the reasoning dial pinned
|
|
351
|
+
right. Height is one model-row minus its own border, so the row + border + the popover gap to the list
|
|
352
|
+
equal one list-row pitch — the section subtracts a whole row, never a fraction, so no model is clipped
|
|
353
|
+
under the scroll. (Eyeball-tune the height; exact pixel math isn't worth it.) The headline carries only
|
|
354
|
+
what the user cares about; reroute/uncached mechanics live in its title, not on the line. */
|
|
355
|
+
.model-head {
|
|
356
|
+
display: flex; align-items: center; justify-content: space-between; gap: .5rem; flex: none;
|
|
357
|
+
height: calc(var(--menu-row-h) - 1px); box-sizing: border-box;
|
|
358
|
+
/* left padding lines "More info" up with the model titles (row padding .55rem + dot 7px + gap .6rem);
|
|
359
|
+
right padding lines the dial up with the per-row "probe" column (row padding .55rem + list
|
|
360
|
+
padding-right 4px + scrollbar ~15px — the scrollbar is effectively always present, so it's accounted
|
|
361
|
+
for unconditionally; tweak the 15px by eye). */
|
|
362
|
+
padding: 0 calc(.55rem + 4px + 15px) 0 calc(.55rem + 7px + .6rem);
|
|
363
|
+
border-bottom: 1px solid var(--border);
|
|
364
|
+
font-size: .74rem; line-height: 1.35; color: var(--muted); /* line-height clears "g"/"y" descenders */
|
|
365
|
+
}
|
|
366
|
+
/* The external-override warning is the one case that still puts words on this row. */
|
|
367
|
+
.model-route { flex: 0 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
368
|
+
.model-route.warn { color: var(--warn); }
|
|
369
|
+
/* "More info" — a quiet text link (not a pill); drills into the routing/reasoning explainer (diag chrome). */
|
|
370
|
+
.model-info-toggle {
|
|
371
|
+
appearance: none; background: none; border: 0; margin: 0; padding: 0;
|
|
372
|
+
font: inherit; font-size: .74rem; color: var(--accent); cursor: pointer; flex: none;
|
|
373
|
+
}
|
|
374
|
+
.model-info-toggle:hover { text-decoration: underline; }
|
|
375
|
+
/* The reasoning label sits a clear gap to the left of the dial; the pair hugs the right edge. */
|
|
376
|
+
.reasoning-ctl { display: flex; align-items: center; gap: .5rem; flex: none; }
|
|
377
|
+
.reasoning-label { flex: none; }
|
|
378
|
+
|
|
379
|
+
/* The reasoning dial — a Min/Low/Med/High native select styled as an accent pill (a full combobox is
|
|
380
|
+
over-powered for four fixed levels), pinned to the right of the headline so the dropdown is the row's
|
|
381
|
+
last element. */
|
|
382
|
+
.reasoning-select {
|
|
383
|
+
appearance: none; margin: 0; font: inherit; font-size: .72rem;
|
|
384
|
+
width: 4rem; /* match the probe button so the dial shares its column */
|
|
385
|
+
/* centred text (symmetric padding) so the label sits like the probe button's centred caption; the
|
|
386
|
+
chevron still rides the right padding zone. */
|
|
387
|
+
height: 1.5rem; padding: 0 1.1rem; text-align: center; text-align-last: center; border-radius: 999px;
|
|
388
|
+
border: 1px solid color-mix(in srgb, var(--accent) 50%, transparent);
|
|
389
|
+
/* OPAQUE (mixed over --panel, not transparent) so the OPEN native option list matches the CLOSED
|
|
390
|
+
pill — a transparent fill left the open list on the browser's default bg, a jarring mismatch.
|
|
391
|
+
The option rows reuse the same --rs-bg so the two states are identical. */
|
|
392
|
+
--rs-bg: color-mix(in srgb, var(--accent) 14%, var(--panel));
|
|
393
|
+
background-color: var(--rs-bg);
|
|
394
|
+
color: var(--accent); cursor: pointer;
|
|
395
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath fill='none' stroke='%237aa2fa' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M1 1l4 4 4-4'/%3E%3C/svg%3E");
|
|
396
|
+
background-repeat: no-repeat; background-position: right .5rem center;
|
|
397
|
+
}
|
|
398
|
+
.reasoning-select option { background-color: var(--rs-bg); color: var(--fg); }
|
|
399
|
+
.reasoning-select:hover { border-color: var(--accent); }
|
|
400
|
+
.reasoning-select:focus { outline: none; border-color: var(--accent); }
|
|
401
|
+
|
|
402
|
+
/* The Anthropic brand glyph stands in for the word on the default pill (same square footprint as the
|
|
403
|
+
prose toggle). currentColor tracks the pill's muted→accent state; grayscale at rest like the monkey. */
|
|
404
|
+
.anthropic-glyph { display: block; }
|
|
405
|
+
.pill.glyph .anthropic-glyph { filter: grayscale(1); opacity: .8; }
|
|
406
|
+
.pill.glyph:hover .anthropic-glyph { filter: none; opacity: 1; }
|
|
407
|
+
|
|
408
|
+
.model-row .picker-row-main { align-items: center; }
|
|
409
|
+
/* Hold the shared --menu-row-h pitch (TB-Agent-Mirror.md: "rows must match pitch exactly or the
|
|
410
|
+
side-by-side menus read as two designs"). The trailing control is the tallest per-row element; like
|
|
411
|
+
the launcher's .server-row, trim the inherited .picker-row vertical padding so it stays under the
|
|
412
|
+
--menu-row-h floor and that floor — not the padded content — governs the row height. The highlight
|
|
413
|
+
still fills the full 29px (it paints the row box, not the padding). */
|
|
414
|
+
.model-row { padding-top: .05rem; padding-bottom: .05rem; }
|
|
415
|
+
/* The Anthropic (default) row is its own group below the OpenRouter models — it's the distinct "off the
|
|
416
|
+
proxy, back to your subscription" choice, not just another model. Marked with a subtle background wash
|
|
417
|
+
rather than a divider line: a line fought the rows' rounded corners + inset highlights (poking corners,
|
|
418
|
+
uneven gaps), whereas the wash inherits the row's own 6px radius, so it reads as a contained band that
|
|
419
|
+
sits naturally. Zero layout impact; lower specificity than :hover/.active, so the row still gives its
|
|
420
|
+
normal accent feedback on top. */
|
|
421
|
+
.model-default-row { background: color-mix(in srgb, var(--fg) 6%, transparent); }
|
|
422
|
+
/* Right-aligned trailing cluster: the green `active` marker (only on the current row) then the
|
|
423
|
+
fixed-width status control. The status control is rightmost + fixed width, so its column never moves
|
|
424
|
+
as rows probe — least musical chairs. */
|
|
425
|
+
.model-trailing { flex: none; display: flex; gap: .35rem; justify-content: flex-end; align-items: center; }
|
|
426
|
+
/* ONE button look AND width shared by every trailing pill — probe, probing, the measured status, and
|
|
427
|
+
the active marker — so they all line up and a transition only ever changes colour/text, never the
|
|
428
|
+
shape. The width is snug to the widest short label ("uncached"); the labels are kept terse precisely
|
|
429
|
+
so this stays tight rather than gutter-heavy. */
|
|
430
|
+
.model-btn {
|
|
431
|
+
appearance: none; margin: 0; font: inherit; font-size: .72rem;
|
|
432
|
+
width: 4rem; height: 1.5rem; padding: 0 .25rem; border-radius: 999px;
|
|
433
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
434
|
+
border: 1px solid var(--border); background: transparent; color: var(--muted);
|
|
435
|
+
white-space: nowrap; cursor: pointer;
|
|
436
|
+
}
|
|
437
|
+
.model-status.probe:hover { color: var(--accent); border-color: var(--accent); }
|
|
438
|
+
/* Measured states. cached = the blue accent (the positive "good route" cue); uncached/unavailable =
|
|
439
|
+
red (the states that bite on cost); cache-unconfirmed = neutral. All reuse existing theme colours. */
|
|
440
|
+
.model-status.ok { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 50%, transparent); background: color-mix(in srgb, var(--accent) 12%, transparent); }
|
|
441
|
+
.model-status.bad { color: var(--err); border-color: color-mix(in srgb, var(--err) 50%, transparent); background: color-mix(in srgb, var(--err) 12%, transparent); }
|
|
442
|
+
.model-status.warn { color: var(--muted); border-color: var(--border); }
|
|
443
|
+
.model-status.ok:hover, .model-status.bad:hover, .model-status.warn:hover { filter: brightness(1.12); text-decoration: underline; }
|
|
444
|
+
/* Same pill as every other state — only the text/colour change across probe → probing → result, never
|
|
445
|
+
the box. Non-interactive (cursor default); the accent-tinted border holds against the row's accent
|
|
446
|
+
hover tint, where the faint default --border washed out. */
|
|
447
|
+
.model-status.probing { color: var(--muted); border-color: color-mix(in srgb, var(--accent) 35%, transparent); cursor: default; }
|
|
448
|
+
/* The active marker — the lime/green live-axis colour (--fold-marker), matching the launcher's running
|
|
449
|
+
:port. A cue, not a control: no hover, default cursor. */
|
|
450
|
+
.model-active { color: var(--fold-marker); border-color: color-mix(in srgb, var(--fold-marker) 50%, transparent); background: color-mix(in srgb, var(--fold-marker) 12%, transparent); cursor: default; }
|
|
451
|
+
/* Don't double-mark the current row: the green `active` pill is the marker now, so drop the accent dot
|
|
452
|
+
fill (keep the gutter transparent for name alignment). */
|
|
453
|
+
.model-picker .picker-row.current .picker-dot { background: transparent; }
|
|
454
|
+
|
|
455
|
+
/* Drill-in surface — the probe diagnostics and the "More info" routing/reasoning explainer (the
|
|
456
|
+
launcher's logs analogue). Flips the whole popover; reuses the console head/back chrome. Held at the
|
|
457
|
+
FULL menu height (not content-sized) so drilling in from the list never resizes the popover — the body
|
|
458
|
+
fills and scrolls if the content is long, leaving quiet space if it's short. */
|
|
459
|
+
.model-picker.diag-mode { padding: 0; gap: 0; height: var(--menu-h); }
|
|
460
|
+
.model-picker.diag-mode .diag-body { flex: 1; min-height: 0; overflow-y: auto; }
|
|
461
|
+
.model-picker.diag-mode:focus { outline: none; }
|
|
462
|
+
.diag-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem; padding: .35rem .5rem; background: var(--panel); border-bottom: 1px solid var(--border); }
|
|
463
|
+
.diag-head-acts { display: flex; gap: .4rem; align-items: center; flex: none; }
|
|
464
|
+
.diag-back:disabled { opacity: .5; cursor: default; }
|
|
465
|
+
/* line-height clears descenders: the popover inherits the statusbar's line-height:1, and overflow:hidden
|
|
466
|
+
would otherwise crop the tails of "g"/"y" (same root cause as .shimmer-text's fix). */
|
|
467
|
+
.diag-name { min-width: 0; color: var(--fg); font-size: .8rem; line-height: 1.35; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
468
|
+
.diag-back { font: inherit; font-size: .76rem; appearance: none; margin: 0; padding: .15rem .5rem; border-radius: 5px; background: transparent; border: 1px solid var(--border); color: var(--muted); cursor: pointer; white-space: nowrap; }
|
|
469
|
+
.diag-back:hover { color: var(--accent); border-color: var(--accent); }
|
|
470
|
+
.diag-body { display: flex; flex-direction: column; gap: .4rem; padding: .6rem; background: var(--bg); }
|
|
471
|
+
.diag-line { font-size: .8rem; line-height: 1.45; overflow-wrap: anywhere; }
|
|
472
|
+
/* Hanging-indent bullet under a lead-in line — wrapped lines align past the “• ”. */
|
|
473
|
+
.diag-bullet { padding-left: 1rem; text-indent: -0.55rem; }
|
|
474
|
+
.diag-line.ok { color: var(--diff-add); }
|
|
475
|
+
.diag-line.bad { color: var(--err); }
|
|
476
|
+
.diag-line.warn { color: var(--fg); }
|
|
477
|
+
.diag-line.muted { color: var(--muted); }
|
|
478
|
+
.diag-line.small { font-size: .72rem; }
|
|
479
|
+
|
|
299
480
|
/* Running-breakouts list — shares the popover chrome (.servers-pop above); its own dimensions +
|
|
300
481
|
per-row layout: the play/stop button + name read together on the left (▶/■ Title, name flex:1),
|
|
301
482
|
and the metadata/controls form a right-aligned cluster of fixed-width columns (logs · trust ·
|
|
@@ -1097,6 +1278,8 @@ a.server-port:hover { text-decoration: underline; }
|
|
|
1097
1278
|
}
|
|
1098
1279
|
|
|
1099
1280
|
.thinking { margin-bottom: .4rem; font-size: .82rem; color: var(--muted); }
|
|
1281
|
+
/* The <summary> toggle defaults to a text cursor — give it the pointer every other expandable uses. */
|
|
1282
|
+
.thinking summary { cursor: pointer; }
|
|
1100
1283
|
.thinking pre { white-space: pre-wrap; }
|
|
1101
1284
|
|
|
1102
1285
|
/* Copy button: an .overlay-pill revealed on bubble-hover. */
|
package/dist/format/parse.d.ts
CHANGED
|
@@ -10,6 +10,14 @@ export interface ParsedBulb {
|
|
|
10
10
|
files: Map<string, string>;
|
|
11
11
|
/** Structural defects tolerated during parse (an unterminated fence); empty when well-formed. */
|
|
12
12
|
warnings: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Line index (0-based, into the parsed content's `split('\n')`) just past the last *captured* block's
|
|
15
|
+
* closing fence — i.e. where the bulb's body ends. NOT end-of-content: trailing lines that set no block
|
|
16
|
+
* (prose, a stray `---`) sit beyond it. Equals the line after the frontmatter when no block was captured.
|
|
17
|
+
* Lets a caller slice a bulb out of surrounding prose without swallowing what follows it (see
|
|
18
|
+
* `findEmbeddedBulbs`).
|
|
19
|
+
*/
|
|
20
|
+
bodyEndLine: number;
|
|
13
21
|
}
|
|
14
22
|
/** Structured per-block content, keyed by kind. */
|
|
15
23
|
export type BulbData = {
|
|
@@ -25,6 +33,28 @@ export declare function parseBulb(content: string): ParsedBulb | null;
|
|
|
25
33
|
* `null` (not a bulb at all) has no fence warnings to report. Empty when well-formed; never throws.
|
|
26
34
|
*/
|
|
27
35
|
export declare function validateBulbStructure(content: string): string[];
|
|
36
|
+
/** A bulb found sitting "naked" in prose — frontmatter and blocks dumped straight into the text with no
|
|
37
|
+
* enclosing ````bulb```` fence. `start`/`end` are the half-open `[start, end)` line range (0-based, into
|
|
38
|
+
* `content.split('\n')`); `source` is that slice, ready to feed back to `parseBulb` or a renderer. */
|
|
39
|
+
export interface EmbeddedBulb {
|
|
40
|
+
start: number;
|
|
41
|
+
end: number;
|
|
42
|
+
source: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Find bulbs embedded unfenced in arbitrary prose. The normal path wraps a bulb in a ````bulb```` fence
|
|
46
|
+
* (and the mislabel-tolerant host promotes a fence whose body parses); but some non-SOTA models — Kimi
|
|
47
|
+
* notably — skip the fence entirely and dump the raw frontmatter + blocks into the message, using `---`
|
|
48
|
+
* thematic breaks as ad-hoc delimiters, so no fence token ever wraps the bulb. This scans for that.
|
|
49
|
+
*
|
|
50
|
+
* The frontmatter gate (`---\nformat: typebulb…`) is the same near-zero-false-positive signal the fenced
|
|
51
|
+
* mislabel tolerance leans on, so we anchor on a `---` line, run `parseBulb` from there, and accept it
|
|
52
|
+
* only if it yields at least one real block. The extent is `parseBulb`'s own `bodyEndLine` (just past the
|
|
53
|
+
* last captured block) — NOT end-of-text — so trailing prose after the bulb (Kimi's closing `---`, a
|
|
54
|
+
* follow-up paragraph) stays prose instead of being swallowed into the embed. Returned spans are
|
|
55
|
+
* non-overlapping and in document order; the caller slices the surrounding prose around them.
|
|
56
|
+
*/
|
|
57
|
+
export declare function findEmbeddedBulbs(content: string): EmbeddedBulb[];
|
|
28
58
|
/** Convert a parsed bulb to a structured per-kind object (missing blocks become ''). */
|
|
29
59
|
export declare function toBulbData(parsed: ParsedBulb): BulbData;
|
|
30
60
|
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../format/src/parse.ts"],"names":[],"mappings":"AAAA,uGAAuG;AAEvG,OAAO,EAAE,KAAK,aAAa,EAAsC,MAAM,eAAe,CAAA;AAStF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,eAAe,CAAA;IAC5B,0DAA0D;IAC1D,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC1B,iGAAiG;IACjG,QAAQ,EAAE,MAAM,EAAE,CAAA;
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../format/src/parse.ts"],"names":[],"mappings":"AAAA,uGAAuG;AAEvG,OAAO,EAAE,KAAK,aAAa,EAAsC,MAAM,eAAe,CAAA;AAStF,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,eAAe,CAAA;IAC5B,0DAA0D;IAC1D,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC1B,iGAAiG;IACjG,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB;;;;;;OAMG;IACH,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,mDAAmD;AACnD,MAAM,MAAM,QAAQ,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,CAAA;AAEvE,wFAAwF;AACxF,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAkE5D;AA6CD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAE/D;AAED;;sGAEsG;AACtG,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,GAAG,EAAE,MAAM,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;CACf;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,YAAY,EAAE,CAkBjE;AAED,wFAAwF;AACxF,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,QAAQ,CAKvD"}
|
package/dist/format/parse.js
CHANGED
|
@@ -29,6 +29,9 @@ export function parseBulb(content) {
|
|
|
29
29
|
// Parse file sections
|
|
30
30
|
const files = new Map();
|
|
31
31
|
const warnings = [];
|
|
32
|
+
// Where the body ends: bumped to the line past each captured block's closing fence. Starts at the
|
|
33
|
+
// first line after the frontmatter, so a block-less bulb reports that.
|
|
34
|
+
let bodyEndLine = i;
|
|
32
35
|
while (i < lines.length) {
|
|
33
36
|
const line = lines[i]?.trim();
|
|
34
37
|
// Look for **filename** (bold markdown, tolerant of whitespace)
|
|
@@ -56,12 +59,13 @@ export function parseBulb(content) {
|
|
|
56
59
|
i++; // Skip closing fence (no-op past EOF)
|
|
57
60
|
warnings.push(...blockWarnings(filename, contentLines, terminated));
|
|
58
61
|
files.set(filename, contentLines.join('\n'));
|
|
62
|
+
bodyEndLine = i;
|
|
59
63
|
}
|
|
60
64
|
else {
|
|
61
65
|
i++;
|
|
62
66
|
}
|
|
63
67
|
}
|
|
64
|
-
return { frontmatter, files, warnings };
|
|
68
|
+
return { frontmatter, files, warnings, bodyEndLine };
|
|
65
69
|
}
|
|
66
70
|
catch {
|
|
67
71
|
return null;
|
|
@@ -123,6 +127,42 @@ function parseFrontmatter(lines) {
|
|
|
123
127
|
export function validateBulbStructure(content) {
|
|
124
128
|
return parseBulb(content)?.warnings ?? [];
|
|
125
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* Find bulbs embedded unfenced in arbitrary prose. The normal path wraps a bulb in a ````bulb```` fence
|
|
132
|
+
* (and the mislabel-tolerant host promotes a fence whose body parses); but some non-SOTA models — Kimi
|
|
133
|
+
* notably — skip the fence entirely and dump the raw frontmatter + blocks into the message, using `---`
|
|
134
|
+
* thematic breaks as ad-hoc delimiters, so no fence token ever wraps the bulb. This scans for that.
|
|
135
|
+
*
|
|
136
|
+
* The frontmatter gate (`---\nformat: typebulb…`) is the same near-zero-false-positive signal the fenced
|
|
137
|
+
* mislabel tolerance leans on, so we anchor on a `---` line, run `parseBulb` from there, and accept it
|
|
138
|
+
* only if it yields at least one real block. The extent is `parseBulb`'s own `bodyEndLine` (just past the
|
|
139
|
+
* last captured block) — NOT end-of-text — so trailing prose after the bulb (Kimi's closing `---`, a
|
|
140
|
+
* follow-up paragraph) stays prose instead of being swallowed into the embed. Returned spans are
|
|
141
|
+
* non-overlapping and in document order; the caller slices the surrounding prose around them.
|
|
142
|
+
*/
|
|
143
|
+
export function findEmbeddedBulbs(content) {
|
|
144
|
+
const lines = content.split('\n');
|
|
145
|
+
const found = [];
|
|
146
|
+
let i = 0;
|
|
147
|
+
while (i < lines.length) {
|
|
148
|
+
if (lines[i]?.trim() !== '---') {
|
|
149
|
+
i++;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const parsed = parseBulb(lines.slice(i).join('\n'));
|
|
153
|
+
// Require at least one captured block: a lone `---…---` frontmatter region with no blocks isn't a
|
|
154
|
+
// renderable bulb (and `bodyEndLine` would be the frontmatter end — an empty slice).
|
|
155
|
+
if (parsed && parsed.files.size > 0) {
|
|
156
|
+
const end = i + parsed.bodyEndLine;
|
|
157
|
+
found.push({ start: i, end, source: lines.slice(i, end).join('\n') });
|
|
158
|
+
i = end;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
i++;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return found;
|
|
165
|
+
}
|
|
126
166
|
/** Convert a parsed bulb to a structured per-kind object (missing blocks become ''). */
|
|
127
167
|
export function toBulbData(parsed) {
|
|
128
168
|
const kinds = Object.fromEntries(orderedKinds.map(kind => [kind, parsed.files.get(blocks[kind].path) || '']));
|
package/dist/format/parse.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../format/src/parse.ts"],"names":[],"mappings":"AAAA,uGAAuG;AAEvG,OAAO,EAAsB,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACtF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAE9C,sGAAsG;AACtG,sBAAsB;AACtB,MAAM,eAAe,GAAG,gBAAgB,CAAA;AACxC,MAAM,aAAa,GAAG,mBAAmB,CAAA;AACzC,MAAM,WAAW,GAAG,IAAI,GAAG,CAAS,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../format/src/parse.ts"],"names":[],"mappings":"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"}
|