pilotswarm-web 0.1.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.
Files changed (57) hide show
  1. package/README.md +144 -0
  2. package/auth/authz/engine.js +139 -0
  3. package/auth/config.js +110 -0
  4. package/auth/index.js +153 -0
  5. package/auth/normalize/entra.js +22 -0
  6. package/auth/providers/entra.js +76 -0
  7. package/auth/providers/none.js +24 -0
  8. package/auth.js +10 -0
  9. package/bin/serve.js +53 -0
  10. package/config.js +20 -0
  11. package/dist/app.js +469 -0
  12. package/dist/assets/index-BSVg-lGb.css +1 -0
  13. package/dist/assets/index-BXD5YP7A.js +24 -0
  14. package/dist/assets/msal-CytV9RFv.js +7 -0
  15. package/dist/assets/pilotswarm-WX3NED6m.js +40 -0
  16. package/dist/assets/react-jg0oazEi.js +1 -0
  17. package/dist/index.html +16 -0
  18. package/node_modules/pilotswarm-ui-core/README.md +6 -0
  19. package/node_modules/pilotswarm-ui-core/package.json +32 -0
  20. package/node_modules/pilotswarm-ui-core/src/commands.js +72 -0
  21. package/node_modules/pilotswarm-ui-core/src/context-usage.js +212 -0
  22. package/node_modules/pilotswarm-ui-core/src/controller.js +3613 -0
  23. package/node_modules/pilotswarm-ui-core/src/formatting.js +872 -0
  24. package/node_modules/pilotswarm-ui-core/src/history.js +571 -0
  25. package/node_modules/pilotswarm-ui-core/src/index.js +13 -0
  26. package/node_modules/pilotswarm-ui-core/src/layout.js +196 -0
  27. package/node_modules/pilotswarm-ui-core/src/reducer.js +1027 -0
  28. package/node_modules/pilotswarm-ui-core/src/selectors.js +2786 -0
  29. package/node_modules/pilotswarm-ui-core/src/session-tree.js +109 -0
  30. package/node_modules/pilotswarm-ui-core/src/state.js +80 -0
  31. package/node_modules/pilotswarm-ui-core/src/store.js +23 -0
  32. package/node_modules/pilotswarm-ui-core/src/system-titles.js +24 -0
  33. package/node_modules/pilotswarm-ui-core/src/themes/catppuccin-mocha.js +56 -0
  34. package/node_modules/pilotswarm-ui-core/src/themes/cobalt2.js +56 -0
  35. package/node_modules/pilotswarm-ui-core/src/themes/dark-high-contrast.js +56 -0
  36. package/node_modules/pilotswarm-ui-core/src/themes/dracula.js +56 -0
  37. package/node_modules/pilotswarm-ui-core/src/themes/github-dark.js +56 -0
  38. package/node_modules/pilotswarm-ui-core/src/themes/gruvbox-dark.js +56 -0
  39. package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-matrix.js +56 -0
  40. package/node_modules/pilotswarm-ui-core/src/themes/hacker-x-orion-prime.js +56 -0
  41. package/node_modules/pilotswarm-ui-core/src/themes/helpers.js +77 -0
  42. package/node_modules/pilotswarm-ui-core/src/themes/index.js +42 -0
  43. package/node_modules/pilotswarm-ui-core/src/themes/noctis-viola.js +56 -0
  44. package/node_modules/pilotswarm-ui-core/src/themes/noctis.js +56 -0
  45. package/node_modules/pilotswarm-ui-core/src/themes/nord.js +56 -0
  46. package/node_modules/pilotswarm-ui-core/src/themes/solarized-dark.js +56 -0
  47. package/node_modules/pilotswarm-ui-core/src/themes/tokyo-night.js +56 -0
  48. package/node_modules/pilotswarm-ui-react/README.md +5 -0
  49. package/node_modules/pilotswarm-ui-react/package.json +36 -0
  50. package/node_modules/pilotswarm-ui-react/src/components.js +1316 -0
  51. package/node_modules/pilotswarm-ui-react/src/index.js +4 -0
  52. package/node_modules/pilotswarm-ui-react/src/platform.js +15 -0
  53. package/node_modules/pilotswarm-ui-react/src/use-controller-state.js +38 -0
  54. package/node_modules/pilotswarm-ui-react/src/web-app.js +2661 -0
  55. package/package.json +64 -0
  56. package/runtime.js +146 -0
  57. package/server.js +311 -0
@@ -0,0 +1,872 @@
1
+ const STYLE_TAG_RE = /\{(\/?)([a-z-]+)\}/g;
2
+
3
+ const DEFAULT_STYLE = {
4
+ color: null,
5
+ bold: false,
6
+ underline: false,
7
+ };
8
+
9
+ const ARTIFACT_URI_RE = /artifact:\/\/([a-f0-9-]+)\/([^\s"'{}]+)/g;
10
+ const SESSION_URI_RE = /session:\/\/([A-Za-z0-9-]+)/g;
11
+
12
+ const COLOR_NAME_MAP = {
13
+ black: "black",
14
+ red: "red",
15
+ green: "green",
16
+ yellow: "yellow",
17
+ blue: "blue",
18
+ magenta: "magenta",
19
+ cyan: "cyan",
20
+ white: "white",
21
+ gray: "gray",
22
+ grey: "gray",
23
+ };
24
+
25
+ function cloneStyle(style) {
26
+ return {
27
+ color: style.color,
28
+ bold: style.bold,
29
+ underline: style.underline,
30
+ };
31
+ }
32
+
33
+ function applyStyleTag(style, tagName, closing) {
34
+ const next = cloneStyle(style);
35
+ if (tagName === "bold") {
36
+ next.bold = !closing;
37
+ return next;
38
+ }
39
+ if (tagName === "underline") {
40
+ next.underline = !closing;
41
+ return next;
42
+ }
43
+ if (tagName.endsWith("-fg")) {
44
+ const colorName = tagName.slice(0, -3);
45
+ next.color = closing ? null : (COLOR_NAME_MAP[colorName] || null);
46
+ return next;
47
+ }
48
+ return next;
49
+ }
50
+
51
+ export function stripTerminalMarkupTags(input) {
52
+ return String(input || "").replace(STYLE_TAG_RE, "");
53
+ }
54
+
55
+ export function parseTerminalMarkupRuns(input) {
56
+ const lines = [];
57
+ const source = String(input || "");
58
+ const rawLines = source.split("\n");
59
+
60
+ for (const rawLine of rawLines) {
61
+ const lineRuns = [];
62
+ let style = cloneStyle(DEFAULT_STYLE);
63
+ let cursor = 0;
64
+ let match;
65
+
66
+ while ((match = STYLE_TAG_RE.exec(rawLine)) !== null) {
67
+ const [fullMatch, slash, tagName] = match;
68
+ if (match.index > cursor) {
69
+ lineRuns.push({
70
+ text: rawLine.slice(cursor, match.index),
71
+ ...cloneStyle(style),
72
+ });
73
+ }
74
+ style = applyStyleTag(style, tagName, slash === "/");
75
+ cursor = match.index + fullMatch.length;
76
+ }
77
+
78
+ if (cursor < rawLine.length || lineRuns.length === 0) {
79
+ lineRuns.push({
80
+ text: rawLine.slice(cursor),
81
+ ...cloneStyle(style),
82
+ });
83
+ }
84
+
85
+ lines.push(lineRuns);
86
+ STYLE_TAG_RE.lastIndex = 0;
87
+ }
88
+
89
+ return lines;
90
+ }
91
+
92
+ export function shortSessionId(sessionId) {
93
+ const value = String(sessionId || "");
94
+ return value.slice(0, 8);
95
+ }
96
+
97
+ export function shortModelName(model) {
98
+ const value = String(model || "");
99
+ if (!value) return "";
100
+ return value.includes(":") ? value.split(":").slice(1).join(":") : value;
101
+ }
102
+
103
+ export function formatTimestamp(value) {
104
+ if (!value) return "";
105
+ try {
106
+ const date = value instanceof Date ? value : new Date(value);
107
+ return date.toLocaleTimeString(undefined, {
108
+ hour: "2-digit",
109
+ minute: "2-digit",
110
+ second: "2-digit",
111
+ hour12: false,
112
+ });
113
+ } catch {
114
+ return "";
115
+ }
116
+ }
117
+
118
+ export function formatDisplayDateTime(value, opts = {}) {
119
+ if (!value) return "";
120
+ try {
121
+ const date = value instanceof Date ? value : new Date(value);
122
+ return date.toLocaleString("en-GB", {
123
+ month: "short",
124
+ day: "numeric",
125
+ hour: "2-digit",
126
+ minute: "2-digit",
127
+ hour12: false,
128
+ ...opts,
129
+ });
130
+ } catch {
131
+ return "";
132
+ }
133
+ }
134
+
135
+ export function formatHumanDurationSeconds(value) {
136
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0) return "?";
137
+ const totalSeconds = Math.round(value);
138
+ const hours = Math.floor(totalSeconds / 3600);
139
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
140
+ const seconds = totalSeconds % 60;
141
+
142
+ if (hours > 0) return `${hours}h ${minutes}m`;
143
+ if (minutes > 0) return `${minutes}m ${seconds}s`;
144
+ return `${seconds}s`;
145
+ }
146
+
147
+ export function summarizeJson(value) {
148
+ if (value == null) return "";
149
+ if (typeof value === "string") return value;
150
+ try {
151
+ return JSON.stringify(value);
152
+ } catch {
153
+ return String(value);
154
+ }
155
+ }
156
+
157
+ function findClosingDelimiter(source, delimiter, startIndex) {
158
+ const nextIndex = source.indexOf(delimiter, startIndex);
159
+ return nextIndex >= 0 ? nextIndex : -1;
160
+ }
161
+
162
+ function countRepeatedDelimiter(source, delimiter, startIndex) {
163
+ let count = 0;
164
+ while (source.slice(startIndex + count, startIndex + count + delimiter.length) === delimiter) {
165
+ count += delimiter.length;
166
+ }
167
+ return count;
168
+ }
169
+
170
+ function pushTextRun(runs, text, style = {}) {
171
+ if (!text) return;
172
+ const lastRun = runs[runs.length - 1];
173
+ if (lastRun
174
+ && lastRun.color === (style.color || null)
175
+ && Boolean(lastRun.bold) === Boolean(style.bold)
176
+ && Boolean(lastRun.underline) === Boolean(style.underline)
177
+ && lastRun.backgroundColor === style.backgroundColor) {
178
+ lastRun.text += text;
179
+ return;
180
+ }
181
+ runs.push({
182
+ text,
183
+ color: style.color || null,
184
+ bold: Boolean(style.bold),
185
+ underline: Boolean(style.underline),
186
+ backgroundColor: style.backgroundColor || undefined,
187
+ });
188
+ }
189
+
190
+ function flattenRunsText(runs) {
191
+ return (runs || []).map((run) => run?.text || "").join("");
192
+ }
193
+
194
+ function measureRunsWidth(runs) {
195
+ return displayWidth(flattenRunsText(runs));
196
+ }
197
+
198
+ function fitRunsToDisplayWidth(runs, maxWidth) {
199
+ const safeWidth = Math.max(0, Number(maxWidth) || 0);
200
+ if (safeWidth <= 0) return [];
201
+
202
+ const output = [];
203
+ let remaining = safeWidth;
204
+
205
+ for (const run of runs || []) {
206
+ if (remaining <= 0) break;
207
+ const text = String(run?.text || "");
208
+ if (!text) continue;
209
+ const chunk = sliceTextToDisplayWidth(text, remaining);
210
+ if (!chunk) continue;
211
+ output.push({
212
+ ...run,
213
+ text: chunk,
214
+ });
215
+ remaining -= displayWidth(chunk);
216
+ }
217
+
218
+ return output;
219
+ }
220
+
221
+ function padRunsToDisplayWidth(runs, width) {
222
+ const safeWidth = Math.max(0, Number(width) || 0);
223
+ const fitted = fitRunsToDisplayWidth(runs, safeWidth);
224
+ const used = measureRunsWidth(fitted);
225
+ const padding = Math.max(0, safeWidth - used);
226
+ if (padding > 0) {
227
+ fitted.push({ text: " ".repeat(padding), color: null, bold: false, underline: false });
228
+ }
229
+ return fitted;
230
+ }
231
+
232
+ function wrapRunsToDisplayWidth(runs, width) {
233
+ const safeWidth = Math.max(1, Number(width) || 1);
234
+ const wrapped = [];
235
+ let currentRuns = [];
236
+ let currentWidth = 0;
237
+
238
+ function flushCurrentLine() {
239
+ if (currentRuns.length === 0) {
240
+ wrapped.push([{ text: "", color: null, bold: false, underline: false }]);
241
+ return;
242
+ }
243
+ wrapped.push(currentRuns);
244
+ currentRuns = [];
245
+ currentWidth = 0;
246
+ }
247
+
248
+ for (const run of runs || []) {
249
+ let remainingText = String(run?.text || "");
250
+ if (!remainingText) continue;
251
+
252
+ while (remainingText.length > 0) {
253
+ const spaceLeft = Math.max(1, safeWidth - currentWidth);
254
+ const hardChunk = sliceTextToDisplayWidth(remainingText, spaceLeft);
255
+ if (!hardChunk) break;
256
+
257
+ // If the hard slice fits the entire remaining text, or it ends
258
+ // at a natural break, just use it as-is.
259
+ if (hardChunk.length === remainingText.length || displayWidth(hardChunk) < spaceLeft) {
260
+ currentRuns.push({ ...run, text: hardChunk });
261
+ currentWidth += displayWidth(hardChunk);
262
+ remainingText = remainingText.slice(hardChunk.length);
263
+ if (currentWidth >= safeWidth) flushCurrentLine();
264
+ continue;
265
+ }
266
+
267
+ // The chunk fills the line — try to find a word boundary to break at.
268
+ const lastSpace = hardChunk.lastIndexOf(" ");
269
+ if (lastSpace > 0 && (currentWidth > 0 || lastSpace > 0)) {
270
+ // Break at the last space: include the space on this line, then wrap.
271
+ const softChunk = hardChunk.slice(0, lastSpace + 1);
272
+ currentRuns.push({ ...run, text: softChunk });
273
+ currentWidth += displayWidth(softChunk);
274
+ remainingText = remainingText.slice(softChunk.length);
275
+ flushCurrentLine();
276
+ } else {
277
+ // No space found (single long word) — fall back to hard break.
278
+ currentRuns.push({ ...run, text: hardChunk });
279
+ currentWidth += displayWidth(hardChunk);
280
+ remainingText = remainingText.slice(hardChunk.length);
281
+ if (currentWidth >= safeWidth) flushCurrentLine();
282
+ }
283
+ }
284
+ }
285
+
286
+ if (currentRuns.length > 0 || wrapped.length === 0) {
287
+ flushCurrentLine();
288
+ }
289
+
290
+ return wrapped;
291
+ }
292
+
293
+ function recolorRuns(runs, color) {
294
+ if (!color) return runs;
295
+ return (runs || []).map((run) => {
296
+ const text = String(run?.text || "");
297
+ if (!text.trim()) return { ...run };
298
+ return {
299
+ ...run,
300
+ color,
301
+ };
302
+ });
303
+ }
304
+
305
+ export function normalizeArtifactFilename(filename) {
306
+ if (!filename) return filename;
307
+
308
+ let normalized = String(filename);
309
+ while (true) {
310
+ const next = normalized
311
+ .replace(/[*_`]+$/g, "")
312
+ .replace(/[)\]}>!,;:?]+$/g, "")
313
+ .replace(/(\.[A-Za-z0-9]{1,16})\.+$/g, "$1");
314
+ if (next === normalized) return normalized;
315
+ normalized = next;
316
+ }
317
+ }
318
+
319
+ export function extractArtifactLinks(text) {
320
+ const source = String(text || "");
321
+ ARTIFACT_URI_RE.lastIndex = 0;
322
+ return [...source.matchAll(ARTIFACT_URI_RE)]
323
+ .map((match) => {
324
+ const sessionId = match[1];
325
+ const filename = normalizeArtifactFilename(match[2]);
326
+ if (!sessionId || !filename) return null;
327
+ return {
328
+ sessionId,
329
+ filename,
330
+ uri: `artifact://${sessionId}/${filename}`,
331
+ };
332
+ })
333
+ .filter(Boolean);
334
+ }
335
+
336
+ export function decorateArtifactLinksForChat(text) {
337
+ const source = String(text || "");
338
+ if (!source) return source;
339
+ ARTIFACT_URI_RE.lastIndex = 0;
340
+ const withArtifacts = source.replace(ARTIFACT_URI_RE, (_match, sessionId, rawFilename) => {
341
+ const filename = normalizeArtifactFilename(rawFilename);
342
+ if (!sessionId || !filename) return _match;
343
+ return `[artifact: ${filename}](artifact://${sessionId}/${filename}) (press a to download)`;
344
+ });
345
+ SESSION_URI_RE.lastIndex = 0;
346
+ return withArtifacts.replace(SESSION_URI_RE, (_match, sessionId) => {
347
+ if (!sessionId) return _match;
348
+ return `[session: ${shortSessionId(sessionId)}](session://${sessionId})`;
349
+ });
350
+ }
351
+
352
+ function parseInlineMarkdownRuns(source) {
353
+ const text = String(source || "");
354
+ const runs = [];
355
+ let index = 0;
356
+
357
+ while (index < text.length) {
358
+ if (text[index] === "`") {
359
+ const tickCount = countRepeatedDelimiter(text, "`", index);
360
+ const delimiter = "`".repeat(tickCount);
361
+ const closeIndex = findClosingDelimiter(text, delimiter, index + tickCount);
362
+ if (closeIndex > index + tickCount - 1) {
363
+ pushTextRun(runs, text.slice(index + tickCount, closeIndex), { color: "cyan" });
364
+ index = closeIndex + tickCount;
365
+ continue;
366
+ }
367
+ }
368
+
369
+ if (text.startsWith("**", index) || text.startsWith("__", index)) {
370
+ const delimiter = text.slice(index, index + 2);
371
+ const closeIndex = findClosingDelimiter(text, delimiter, index + 2);
372
+ if (closeIndex > index + 2) {
373
+ pushTextRun(runs, text.slice(index + 2, closeIndex), { bold: true });
374
+ index = closeIndex + 2;
375
+ continue;
376
+ }
377
+ }
378
+
379
+ if (text[index] === "[") {
380
+ const match = /^\[([^\]]+)\]\(([^)]+)\)/.exec(text.slice(index));
381
+ if (match) {
382
+ pushTextRun(runs, match[1], { color: "cyan", underline: true });
383
+ index += match[0].length;
384
+ continue;
385
+ }
386
+ }
387
+
388
+ if ((text[index] === "*" || text[index] === "_")
389
+ && text[index + 1] !== " "
390
+ && text[index - 1] !== text[index]) {
391
+ const delimiter = text[index];
392
+ const closeIndex = findClosingDelimiter(text, delimiter, index + 1);
393
+ if (closeIndex > index + 1) {
394
+ pushTextRun(runs, text.slice(index + 1, closeIndex), { underline: true });
395
+ index = closeIndex + 1;
396
+ continue;
397
+ }
398
+ }
399
+
400
+ let nextIndex = text.length;
401
+ for (const marker of ["**", "__", "`", "[", "*", "_"]) {
402
+ const candidate = text.indexOf(marker, index + 1);
403
+ if (candidate !== -1 && candidate < nextIndex) {
404
+ nextIndex = candidate;
405
+ }
406
+ }
407
+ pushTextRun(runs, text.slice(index, nextIndex));
408
+ index = nextIndex;
409
+ }
410
+
411
+ return runs.length > 0 ? runs : [{ text: "", color: null, bold: false, underline: false }];
412
+ }
413
+
414
+ function isMarkdownTableLine(line) {
415
+ return /^\s*\|.*\|\s*$/.test(line)
416
+ || /^\s*\|?[:\- ]+\|[:\-| ]+\|?\s*$/.test(line);
417
+ }
418
+
419
+ function splitMarkdownTableCells(line) {
420
+ const trimmed = String(line || "").trim().replace(/^\|/, "").replace(/\|$/, "");
421
+ const cells = [];
422
+ let current = "";
423
+ let escaping = false;
424
+
425
+ for (const ch of trimmed) {
426
+ if (escaping) {
427
+ current += ch;
428
+ escaping = false;
429
+ continue;
430
+ }
431
+ if (ch === "\\") {
432
+ escaping = true;
433
+ continue;
434
+ }
435
+ if (ch === "|") {
436
+ cells.push(current.trim());
437
+ current = "";
438
+ continue;
439
+ }
440
+ current += ch;
441
+ }
442
+
443
+ cells.push(current.trim());
444
+ return cells;
445
+ }
446
+
447
+ function isMarkdownTableSeparatorRow(cells) {
448
+ return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(String(cell || "").replace(/\s+/g, "")));
449
+ }
450
+
451
+ function displayWidth(value) {
452
+ return Array.from(String(value || "")).length;
453
+ }
454
+
455
+ function sliceTextToDisplayWidth(text, maxWidth) {
456
+ if (maxWidth <= 0) return "";
457
+ let output = "";
458
+ let used = 0;
459
+
460
+ for (const ch of Array.from(String(text || ""))) {
461
+ if (used + 1 > maxWidth) break;
462
+ output += ch;
463
+ used += 1;
464
+ }
465
+
466
+ return output;
467
+ }
468
+
469
+ function padToDisplayWidth(text, width) {
470
+ const rendered = String(text || "");
471
+ const padding = Math.max(0, width - displayWidth(rendered));
472
+ return rendered + " ".repeat(padding);
473
+ }
474
+
475
+ function flattenInlineMarkdownText(source) {
476
+ return flattenRunsText(parseInlineMarkdownRuns(source));
477
+ }
478
+
479
+ function wrapTableCellText(text, width) {
480
+ const normalized = flattenInlineMarkdownText(text).replace(/\s+/g, " ").trim();
481
+ if (!normalized) return [""];
482
+
483
+ const words = normalized.split(" ");
484
+ const lines = [];
485
+ let current = "";
486
+
487
+ const appendWord = (word) => {
488
+ let remaining = word;
489
+ while (displayWidth(remaining) > width) {
490
+ const chunk = sliceTextToDisplayWidth(remaining, width);
491
+ if (!chunk) break;
492
+ if (current) {
493
+ lines.push(current);
494
+ current = "";
495
+ }
496
+ lines.push(chunk);
497
+ remaining = remaining.slice(chunk.length);
498
+ }
499
+ return remaining;
500
+ };
501
+
502
+ for (const word of words) {
503
+ const fittedWord = appendWord(word);
504
+ if (!fittedWord) continue;
505
+ if (!current) {
506
+ current = fittedWord;
507
+ continue;
508
+ }
509
+
510
+ const candidate = `${current} ${fittedWord}`;
511
+ if (displayWidth(candidate) <= width) {
512
+ current = candidate;
513
+ } else {
514
+ lines.push(current);
515
+ current = fittedWord;
516
+ }
517
+ }
518
+
519
+ if (current) lines.push(current);
520
+ return lines.length > 0 ? lines : [""];
521
+ }
522
+
523
+ function computeMarkdownTableColumnWidths(rows, maxWidth) {
524
+ const columnCount = Math.max(1, ...rows.map((row) => row.length));
525
+ const availableWidth = Math.max(columnCount * 3, maxWidth - (columnCount * 3 + 1));
526
+ const preferred = Array.from({ length: columnCount }, (_, index) => Math.max(
527
+ 3,
528
+ ...rows.map((row) => displayWidth(row[index] || "")),
529
+ ));
530
+ const longestWord = Array.from({ length: columnCount }, (_, index) => Math.max(
531
+ 3,
532
+ ...rows.map((row) => Math.max(
533
+ 0,
534
+ ...String(row[index] || "")
535
+ .split(/\s+/)
536
+ .filter(Boolean)
537
+ .map((word) => displayWidth(word)),
538
+ )),
539
+ ));
540
+ const minimum = Array.from({ length: columnCount }, (_, index) => Math.max(
541
+ 3,
542
+ Math.min(Math.max(displayWidth(rows[0]?.[index] || "") || 3, longestWord[index]), 24),
543
+ ));
544
+
545
+ const totalPreferred = preferred.reduce((sum, width) => sum + width, 0);
546
+ if (totalPreferred <= availableWidth) {
547
+ return preferred;
548
+ }
549
+
550
+ const widths = minimum.slice();
551
+ let remaining = availableWidth - widths.reduce((sum, width) => sum + width, 0);
552
+
553
+ if (remaining < 0) {
554
+ const evenWidth = Math.max(3, Math.floor(availableWidth / columnCount));
555
+ return widths.map((_, index) => index === columnCount - 1
556
+ ? Math.max(3, availableWidth - evenWidth * (columnCount - 1))
557
+ : evenWidth);
558
+ }
559
+
560
+ while (remaining > 0) {
561
+ let bestIndex = 0;
562
+ let bestNeed = -Infinity;
563
+ for (let index = 0; index < columnCount; index++) {
564
+ const need = preferred[index] - widths[index];
565
+ if (need > bestNeed) {
566
+ bestNeed = need;
567
+ bestIndex = index;
568
+ }
569
+ }
570
+ if (bestNeed <= 0) {
571
+ widths[columnCount - 1] += remaining;
572
+ break;
573
+ }
574
+ widths[bestIndex] += 1;
575
+ remaining -= 1;
576
+ }
577
+
578
+ return widths;
579
+ }
580
+
581
+ function renderPlainMarkdownTable(rows, maxWidth) {
582
+ if (!rows || rows.length === 0) return "";
583
+ const columnCount = Math.max(1, ...rows.map((row) => row.length));
584
+ const normalizedRows = rows.map((row) => Array.from({ length: columnCount }, (_, index) => row[index] || ""));
585
+ const widths = computeMarkdownTableColumnWidths(normalizedRows, maxWidth);
586
+ const rendered = [];
587
+
588
+ const topBorder = `┌${widths.map((width) => "─".repeat(width + 2)).join("┬")}┐`;
589
+ const middleBorder = `├${widths.map((width) => "─".repeat(width + 2)).join("┼")}┤`;
590
+ const bottomBorder = `└${widths.map((width) => "─".repeat(width + 2)).join("┴")}┘`;
591
+
592
+ const renderRow = (cells) => {
593
+ const wrappedCells = cells.map((cell, index) => wrapTableCellText(cell, widths[index]));
594
+ const rowHeight = Math.max(...wrappedCells.map((cellLines) => cellLines.length));
595
+
596
+ for (let lineIndex = 0; lineIndex < rowHeight; lineIndex++) {
597
+ const parts = wrappedCells.map((cellLines, index) => padToDisplayWidth(cellLines[lineIndex] || "", widths[index]));
598
+ rendered.push(`│ ${parts.join(" │ ")} │`);
599
+ }
600
+ };
601
+
602
+ rendered.push(topBorder);
603
+ renderRow(normalizedRows[0]);
604
+ rendered.push(middleBorder);
605
+ const bodyRows = normalizedRows.slice(1);
606
+ for (const [index, row] of bodyRows.entries()) {
607
+ renderRow(row);
608
+ if (index < bodyRows.length - 1) {
609
+ rendered.push(middleBorder);
610
+ }
611
+ }
612
+ rendered.push(bottomBorder);
613
+
614
+ return rendered.join("\n");
615
+ }
616
+
617
+ function formatMarkdownTables(input, maxWidth) {
618
+ const lines = String(input || "").split("\n");
619
+ const output = [];
620
+ let inCodeFence = false;
621
+
622
+ for (let index = 0; index < lines.length; index++) {
623
+ const line = lines[index];
624
+ const trimmed = line.trim();
625
+
626
+ if (trimmed.startsWith("```")) {
627
+ inCodeFence = !inCodeFence;
628
+ output.push(line);
629
+ continue;
630
+ }
631
+
632
+ if (inCodeFence) {
633
+ output.push(line);
634
+ continue;
635
+ }
636
+
637
+ const nextLine = lines[index + 1];
638
+ if (!isMarkdownTableLine(line) || !isMarkdownTableLine(nextLine)) {
639
+ output.push(line);
640
+ continue;
641
+ }
642
+
643
+ const headerCells = splitMarkdownTableCells(line);
644
+ const separatorCells = splitMarkdownTableCells(nextLine);
645
+ if (!isMarkdownTableSeparatorRow(separatorCells) || headerCells.length !== separatorCells.length) {
646
+ output.push(line);
647
+ continue;
648
+ }
649
+
650
+ const rows = [headerCells];
651
+ index += 1;
652
+ while (index + 1 < lines.length && isMarkdownTableLine(lines[index + 1])) {
653
+ index += 1;
654
+ const rowCells = splitMarkdownTableCells(lines[index]);
655
+ if (isMarkdownTableSeparatorRow(rowCells)) continue;
656
+ rows.push(rowCells);
657
+ }
658
+
659
+ output.push(renderPlainMarkdownTable(rows, maxWidth));
660
+ }
661
+
662
+ return output.join("\n");
663
+ }
664
+
665
+ function wrapPlainText(text, width) {
666
+ const source = String(text || "");
667
+ if (!source) return [""];
668
+
669
+ const lines = [];
670
+ let remaining = source;
671
+ while (remaining.length > 0) {
672
+ const chunk = sliceTextToDisplayWidth(remaining, width);
673
+ if (!chunk) break;
674
+ lines.push(chunk);
675
+ remaining = remaining.slice(chunk.length);
676
+ }
677
+
678
+ return lines.length > 0 ? lines : [""];
679
+ }
680
+
681
+ function buildCodeFenceHeader(language, width) {
682
+ const safeWidth = Math.max(12, width);
683
+ const label = language ? ` ${String(language).trim()} ` : " code ";
684
+ const trimmedLabel = sliceTextToDisplayWidth(label, Math.max(1, safeWidth - 4));
685
+ const fill = Math.max(0, safeWidth - trimmedLabel.length - 2);
686
+
687
+ return [{
688
+ text: `┌${trimmedLabel}${"─".repeat(fill)}┐`,
689
+ color: "gray",
690
+ }];
691
+ }
692
+
693
+ function buildCodeFenceFooter(width) {
694
+ return [{
695
+ text: `└${"─".repeat(Math.max(1, width - 2))}┘`,
696
+ color: "gray",
697
+ }];
698
+ }
699
+
700
+ function buildCodeFenceBodyLines(codeLines, width) {
701
+ const safeWidth = Math.max(8, width);
702
+ const bodyWidth = Math.max(1, safeWidth - 2);
703
+ const rendered = [];
704
+
705
+ for (const rawLine of codeLines) {
706
+ const wrappedLines = wrapPlainText(rawLine, bodyWidth);
707
+ for (const wrappedLine of wrappedLines) {
708
+ rendered.push([
709
+ { text: "│", color: "gray" },
710
+ { text: padToDisplayWidth(wrappedLine, bodyWidth), color: "cyan" },
711
+ { text: "│", color: "gray" },
712
+ ]);
713
+ }
714
+ if (wrappedLines.length === 0) {
715
+ rendered.push([
716
+ { text: "│", color: "gray" },
717
+ { text: " ".repeat(bodyWidth), color: "cyan" },
718
+ { text: "│", color: "gray" },
719
+ ]);
720
+ }
721
+ }
722
+
723
+ if (rendered.length === 0) {
724
+ rendered.push([
725
+ { text: "│", color: "gray" },
726
+ { text: " ".repeat(bodyWidth), color: "cyan" },
727
+ { text: "│", color: "gray" },
728
+ ]);
729
+ }
730
+
731
+ return rendered;
732
+ }
733
+
734
+ export function parseMarkdownLines(input, options = {}) {
735
+ const lines = [];
736
+ const maxWidth = Math.max(20, Number(options?.width) || 80);
737
+ const source = formatMarkdownTables(
738
+ String(input || ""),
739
+ maxWidth,
740
+ );
741
+ const rawLines = source.split("\n");
742
+ let inCodeFence = false;
743
+ let codeFenceLanguage = "";
744
+ let codeFenceLines = [];
745
+
746
+ for (const rawLine of rawLines) {
747
+ const trimmed = rawLine.trim();
748
+ if (trimmed.startsWith("```")) {
749
+ if (inCodeFence) {
750
+ lines.push(buildCodeFenceHeader(codeFenceLanguage, Math.max(8, maxWidth)));
751
+ lines.push(...buildCodeFenceBodyLines(codeFenceLines, Math.max(8, maxWidth)));
752
+ lines.push(buildCodeFenceFooter(Math.max(8, maxWidth)));
753
+ inCodeFence = false;
754
+ codeFenceLanguage = "";
755
+ codeFenceLines = [];
756
+ } else {
757
+ inCodeFence = true;
758
+ codeFenceLanguage = trimmed.slice(3).trim();
759
+ codeFenceLines = [];
760
+ }
761
+ continue;
762
+ }
763
+
764
+ if (inCodeFence) {
765
+ codeFenceLines.push(rawLine);
766
+ continue;
767
+ }
768
+
769
+ if (!rawLine) {
770
+ lines.push([{ text: "", color: null }]);
771
+ continue;
772
+ }
773
+
774
+ const headingMatch = /^(#{1,6})\s+(.*)$/.exec(rawLine);
775
+ if (headingMatch) {
776
+ lines.push([{ text: headingMatch[2], color: "white", bold: true }]);
777
+ continue;
778
+ }
779
+
780
+ const quoteMatch = /^>\s?(.*)$/.exec(rawLine);
781
+ if (quoteMatch) {
782
+ lines.push([
783
+ { text: "> ", color: "gray" },
784
+ ...parseInlineMarkdownRuns(quoteMatch[1]).map((run) => ({
785
+ ...run,
786
+ color: run.color || "gray",
787
+ })),
788
+ ]);
789
+ continue;
790
+ }
791
+
792
+ const bulletMatch = /^\s*[-*]\s+(.*)$/.exec(rawLine);
793
+ if (bulletMatch) {
794
+ lines.push([
795
+ { text: "• ", color: "gray" },
796
+ ...parseInlineMarkdownRuns(bulletMatch[1]),
797
+ ]);
798
+ continue;
799
+ }
800
+
801
+ const numberedListMatch = /^\s*(\d+)\.\s+(.*)$/.exec(rawLine);
802
+ if (numberedListMatch) {
803
+ lines.push([
804
+ { text: `${numberedListMatch[1]}. `, color: "gray" },
805
+ ...parseInlineMarkdownRuns(numberedListMatch[2]),
806
+ ]);
807
+ continue;
808
+ }
809
+
810
+ lines.push(parseInlineMarkdownRuns(rawLine));
811
+ }
812
+
813
+ if (inCodeFence) {
814
+ lines.push(buildCodeFenceHeader(codeFenceLanguage, Math.max(8, maxWidth)));
815
+ lines.push(...buildCodeFenceBodyLines(codeFenceLines, Math.max(8, maxWidth)));
816
+ lines.push(buildCodeFenceFooter(Math.max(8, maxWidth)));
817
+ }
818
+
819
+ return lines;
820
+ }
821
+
822
+ export function buildMessageCardLines({
823
+ title = "SYSTEM",
824
+ timestamp = "",
825
+ body = "",
826
+ width = 80,
827
+ titleColor = "yellow",
828
+ borderColor = "gray",
829
+ bodyColor = null,
830
+ fitToContent = false,
831
+ } = {}) {
832
+ const maxWidth = Math.max(fitToContent ? 12 : 20, Number(width) || (fitToContent ? 12 : 20));
833
+ const maxContentWidth = Math.max(1, maxWidth - 4);
834
+ const titleRuns = [
835
+ { text: ` ${String(title || "SYSTEM").toUpperCase()} `, color: titleColor, bold: true },
836
+ ...(timestamp ? [{ text: ` ${String(timestamp)} `, color: "gray" }] : []),
837
+ ];
838
+ const bodyLines = parseMarkdownLines(String(body || ""), { width: maxContentWidth });
839
+ const normalizedBodyLines = bodyLines.length > 0
840
+ ? bodyLines.flatMap((lineRuns) => wrapRunsToDisplayWidth(lineRuns, maxContentWidth))
841
+ : [[{ text: "", color: null }]];
842
+ const tintedBodyLines = bodyColor
843
+ ? normalizedBodyLines.map((lineRuns) => recolorRuns(lineRuns, bodyColor))
844
+ : normalizedBodyLines;
845
+ const widestBodyLine = tintedBodyLines.reduce(
846
+ (max, lineRuns) => Math.max(max, measureRunsWidth(lineRuns)),
847
+ 0,
848
+ );
849
+ const safeWidth = fitToContent
850
+ ? Math.min(
851
+ maxWidth,
852
+ Math.max(12, measureRunsWidth(titleRuns) + 3, widestBodyLine + 4),
853
+ )
854
+ : maxWidth;
855
+ const contentWidth = Math.max(1, safeWidth - 4);
856
+ const topFill = Math.max(0, safeWidth - measureRunsWidth(titleRuns) - 3);
857
+
858
+ return [
859
+ [
860
+ { text: "┌─", color: borderColor },
861
+ ...titleRuns,
862
+ { text: `${"─".repeat(topFill)}┐`, color: borderColor },
863
+ ],
864
+ ...tintedBodyLines.map((lineRuns) => ([
865
+ { text: "│ ", color: borderColor },
866
+ ...padRunsToDisplayWidth(lineRuns, contentWidth),
867
+ { text: " │", color: borderColor },
868
+ ])),
869
+ [{ text: `└${"─".repeat(Math.max(1, safeWidth - 2))}┘`, color: borderColor }],
870
+ [{ text: "", color: null }],
871
+ ];
872
+ }