tycono 0.1.96-beta.37 → 0.1.96-beta.38
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/package.json
CHANGED
|
@@ -10,6 +10,7 @@ import { Box, Text, Static } from 'ink';
|
|
|
10
10
|
import TextInput from 'ink-text-input';
|
|
11
11
|
import type { SSEEvent } from '../api';
|
|
12
12
|
import { getRoleColor } from '../theme';
|
|
13
|
+
import { renderMarkdownLine } from '../utils/markdown';
|
|
13
14
|
|
|
14
15
|
const SUPERVISOR_ROLE = 'ceo';
|
|
15
16
|
|
|
@@ -20,6 +21,7 @@ export interface StreamLine {
|
|
|
20
21
|
prefix?: string;
|
|
21
22
|
prefixColor?: string;
|
|
22
23
|
indent?: boolean;
|
|
24
|
+
markdown?: boolean; // render text as markdown
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
interface CommandModeProps {
|
|
@@ -56,6 +58,7 @@ export function summarizeEvent(event: SSEEvent, allRoleIds: string[]): StreamLin
|
|
|
56
58
|
id: ++lineCounter,
|
|
57
59
|
text,
|
|
58
60
|
color: 'white',
|
|
61
|
+
markdown: true,
|
|
59
62
|
};
|
|
60
63
|
} else {
|
|
61
64
|
return {
|
|
@@ -65,6 +68,7 @@ export function summarizeEvent(event: SSEEvent, allRoleIds: string[]): StreamLin
|
|
|
65
68
|
text,
|
|
66
69
|
color: 'white',
|
|
67
70
|
indent: true,
|
|
71
|
+
markdown: true,
|
|
68
72
|
};
|
|
69
73
|
}
|
|
70
74
|
}
|
|
@@ -232,6 +236,43 @@ export function summarizeEvent(event: SSEEvent, allRoleIds: string[]): StreamLin
|
|
|
232
236
|
|
|
233
237
|
/** Render a single StreamLine */
|
|
234
238
|
function StreamLineRow({ line }: { line: StreamLine }) {
|
|
239
|
+
// Multi-line markdown: split and render each line
|
|
240
|
+
if (line.markdown && line.text.includes('\n')) {
|
|
241
|
+
const lines = line.text.split('\n');
|
|
242
|
+
return (
|
|
243
|
+
<Box flexDirection="column">
|
|
244
|
+
{lines.map((l, i) => (
|
|
245
|
+
<Box key={i}>
|
|
246
|
+
{line.indent && <Text> </Text>}
|
|
247
|
+
{line.prefix && i === 0 && (
|
|
248
|
+
<Text color={line.prefixColor} bold>
|
|
249
|
+
{(line.prefix).padEnd(12)}
|
|
250
|
+
</Text>
|
|
251
|
+
)}
|
|
252
|
+
{line.prefix && i > 0 && <Text>{' '.repeat(12)}</Text>}
|
|
253
|
+
{renderMarkdownLine(l, `${line.id}-${i}`)}
|
|
254
|
+
</Box>
|
|
255
|
+
))}
|
|
256
|
+
</Box>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Single line with markdown
|
|
261
|
+
if (line.markdown) {
|
|
262
|
+
return (
|
|
263
|
+
<Box>
|
|
264
|
+
{line.indent && <Text> </Text>}
|
|
265
|
+
{line.prefix && (
|
|
266
|
+
<Text color={line.prefixColor} bold>
|
|
267
|
+
{(line.prefix).padEnd(12)}
|
|
268
|
+
</Text>
|
|
269
|
+
)}
|
|
270
|
+
{renderMarkdownLine(line.text, line.id)}
|
|
271
|
+
</Box>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Plain text (tools, system messages)
|
|
235
276
|
return (
|
|
236
277
|
<Box>
|
|
237
278
|
{line.indent && <Text> </Text>}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal Markdown Renderer
|
|
3
|
+
*
|
|
4
|
+
* Converts markdown text to Ink <Text> elements with basic formatting:
|
|
5
|
+
* - **bold** → bold text
|
|
6
|
+
* - `code` → dimmed text
|
|
7
|
+
* - ## heading → bold colored text
|
|
8
|
+
* - --- → horizontal line
|
|
9
|
+
* - | table | → kept as-is (monospace already works)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { Text } from 'ink';
|
|
14
|
+
|
|
15
|
+
interface Segment {
|
|
16
|
+
text: string;
|
|
17
|
+
bold?: boolean;
|
|
18
|
+
dim?: boolean;
|
|
19
|
+
color?: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Parse inline markdown (bold, code) into segments */
|
|
23
|
+
function parseInline(text: string): Segment[] {
|
|
24
|
+
const segments: Segment[] = [];
|
|
25
|
+
let remaining = text;
|
|
26
|
+
|
|
27
|
+
while (remaining.length > 0) {
|
|
28
|
+
// Bold: **text**
|
|
29
|
+
const boldMatch = remaining.match(/^(.*?)\*\*(.+?)\*\*(.*)/s);
|
|
30
|
+
if (boldMatch) {
|
|
31
|
+
if (boldMatch[1]) segments.push({ text: boldMatch[1] });
|
|
32
|
+
segments.push({ text: boldMatch[2], bold: true });
|
|
33
|
+
remaining = boldMatch[3];
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Inline code: `text`
|
|
38
|
+
const codeMatch = remaining.match(/^(.*?)`(.+?)`(.*)/s);
|
|
39
|
+
if (codeMatch) {
|
|
40
|
+
if (codeMatch[1]) segments.push({ text: codeMatch[1] });
|
|
41
|
+
segments.push({ text: codeMatch[2], dim: true, color: 'yellow' });
|
|
42
|
+
remaining = codeMatch[3];
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// No more matches — push rest
|
|
47
|
+
segments.push({ text: remaining });
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return segments;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Render a single line of markdown as Ink elements */
|
|
55
|
+
export function renderMarkdownLine(line: string, key: string | number): React.ReactElement {
|
|
56
|
+
// Horizontal rule
|
|
57
|
+
if (/^---+$/.test(line.trim())) {
|
|
58
|
+
return <Text key={key} color="gray">{'\u2500'.repeat(Math.min(60, process.stdout.columns || 60))}</Text>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Heading: ## text
|
|
62
|
+
const headingMatch = line.match(/^(#{1,4})\s+(.+)/);
|
|
63
|
+
if (headingMatch) {
|
|
64
|
+
const level = headingMatch[1].length;
|
|
65
|
+
const content = headingMatch[2].replace(/\*\*/g, ''); // Strip bold in headings
|
|
66
|
+
const color = level <= 2 ? 'cyan' : 'white';
|
|
67
|
+
return <Text key={key} color={color} bold>{content}</Text>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Empty line
|
|
71
|
+
if (!line.trim()) {
|
|
72
|
+
return <Text key={key}> </Text>;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Regular line with inline formatting
|
|
76
|
+
const segments = parseInline(line);
|
|
77
|
+
|
|
78
|
+
if (segments.length === 1 && !segments[0].bold && !segments[0].dim) {
|
|
79
|
+
// Simple text — no formatting needed
|
|
80
|
+
return <Text key={key} color="white">{segments[0].text}</Text>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<Text key={key}>
|
|
85
|
+
{segments.map((seg, i) => (
|
|
86
|
+
<Text
|
|
87
|
+
key={i}
|
|
88
|
+
bold={seg.bold}
|
|
89
|
+
dimColor={seg.dim}
|
|
90
|
+
color={seg.color ?? 'white'}
|
|
91
|
+
>
|
|
92
|
+
{seg.text}
|
|
93
|
+
</Text>
|
|
94
|
+
))}
|
|
95
|
+
</Text>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Render multi-line markdown text as array of Ink elements */
|
|
100
|
+
export function renderMarkdown(text: string, baseKey: string | number = 0): React.ReactElement[] {
|
|
101
|
+
return text.split('\n').map((line, i) => renderMarkdownLine(line, `${baseKey}-${i}`));
|
|
102
|
+
}
|