vigthoria-cli 1.11.10 → 1.11.12
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/commands/chat.d.ts +4 -0
- package/dist/commands/chat.js +98 -24
- package/dist/utils/api.js +1 -1
- package/dist/utils/deckEvents.d.ts +17 -0
- package/dist/utils/deckEvents.js +35 -0
- package/dist/utils/terminalMarkdown.d.ts +15 -0
- package/dist/utils/terminalMarkdown.js +91 -0
- package/package.json +1 -1
package/dist/commands/chat.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export declare class ChatCommand {
|
|
|
48
48
|
private retryPromptStreak;
|
|
49
49
|
private v3SuppressThinkingStream;
|
|
50
50
|
private v3StreamedAnswerBuffer;
|
|
51
|
+
private v3StreamedAnswerDisplayed;
|
|
51
52
|
private v3SeenToolCalls;
|
|
52
53
|
private v3SeenToolResults;
|
|
53
54
|
private lastAgentRunOutcome;
|
|
@@ -123,6 +124,9 @@ export declare class ChatCommand {
|
|
|
123
124
|
private isRawV3StreamPayload;
|
|
124
125
|
private consumeV3StreamPayload;
|
|
125
126
|
private writeV3StreamText;
|
|
127
|
+
private displayV3StreamedAnswer;
|
|
128
|
+
private renderAgentAnswerForTerminal;
|
|
129
|
+
private getTerminalContentWidth;
|
|
126
130
|
private sanitizeV3VisibleStreamText;
|
|
127
131
|
private writeAgentActivityLine;
|
|
128
132
|
private updateV3AgentSpinner;
|
package/dist/commands/chat.js
CHANGED
|
@@ -14,6 +14,8 @@ import { ProjectMemoryService } from '../utils/project-memory.js';
|
|
|
14
14
|
import { buildPersonaOverlay, normalizePersonaMode } from '../utils/persona.js';
|
|
15
15
|
import { runAgentSessionMenu, shouldShowAgentSessionMenu } from './agent-session-menu.js';
|
|
16
16
|
import { createLiveOutcome, evaluateExecutorSuccess, handleTaskEvent, isSubstantiveAgentAnswer, isToolEvidenceStubAnswer, normalizeAgentAnswerContent, noteAnalysisToolUse, } from '../utils/agentRunOutcome.js';
|
|
17
|
+
import { looksLikeMarkdownReport, renderMarkdownToTerminal, summarizeMarkdownReport, } from '../utils/terminalMarkdown.js';
|
|
18
|
+
import { emitDeckEvent, isDeckModeEnabled } from '../utils/deckEvents.js';
|
|
17
19
|
const DEFAULT_V3_AGENT_TIMEOUT_MS = (() => {
|
|
18
20
|
const rawValue = process.env.VIGTHORIA_AGENT_TIMEOUT_MS || process.env.V3_AGENT_TIMEOUT_MS;
|
|
19
21
|
if (!rawValue) {
|
|
@@ -67,6 +69,7 @@ export class ChatCommand {
|
|
|
67
69
|
retryPromptStreak = 0;
|
|
68
70
|
v3SuppressThinkingStream = false;
|
|
69
71
|
v3StreamedAnswerBuffer = '';
|
|
72
|
+
v3StreamedAnswerDisplayed = false;
|
|
70
73
|
v3SeenToolCalls = new Set();
|
|
71
74
|
v3SeenToolResults = new Set();
|
|
72
75
|
// Last completed Agent run — used by /retry, /continue, and the final summary block.
|
|
@@ -788,8 +791,37 @@ export class ChatCommand {
|
|
|
788
791
|
else {
|
|
789
792
|
spinner.stop();
|
|
790
793
|
}
|
|
791
|
-
process.stdout.write(safeText);
|
|
792
794
|
this.v3StreamedAnswerBuffer += safeText;
|
|
795
|
+
spinner.text = looksLikeMarkdownReport(this.v3StreamedAnswerBuffer)
|
|
796
|
+
? 'Writing analysis report...'
|
|
797
|
+
: 'Writing response...';
|
|
798
|
+
}
|
|
799
|
+
displayV3StreamedAnswer() {
|
|
800
|
+
if (this.jsonOutput || this.v3StreamedAnswerDisplayed) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
const content = this.v3StreamedAnswerBuffer.trim();
|
|
804
|
+
if (!isSubstantiveAgentAnswer(content)) {
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
emitDeckEvent({ type: 'report_ready', markdown: content });
|
|
808
|
+
if (isDeckModeEnabled()) {
|
|
809
|
+
this.v3StreamedAnswerDisplayed = true;
|
|
810
|
+
return;
|
|
811
|
+
}
|
|
812
|
+
console.log('');
|
|
813
|
+
console.log(renderMarkdownToTerminal(content, this.getTerminalContentWidth()));
|
|
814
|
+
this.v3StreamedAnswerDisplayed = true;
|
|
815
|
+
}
|
|
816
|
+
renderAgentAnswerForTerminal(content) {
|
|
817
|
+
const normalized = String(content || '').trim();
|
|
818
|
+
if (!normalized)
|
|
819
|
+
return '';
|
|
820
|
+
return renderMarkdownToTerminal(normalized, this.getTerminalContentWidth());
|
|
821
|
+
}
|
|
822
|
+
getTerminalContentWidth() {
|
|
823
|
+
const cols = process.stdout.columns || 80;
|
|
824
|
+
return Math.max(60, Math.min(cols - 2, 100));
|
|
793
825
|
}
|
|
794
826
|
sanitizeV3VisibleStreamText(text) {
|
|
795
827
|
let output = String(text || '');
|
|
@@ -849,6 +881,13 @@ export class ChatCommand {
|
|
|
849
881
|
}
|
|
850
882
|
this.v3SeenToolCalls.add(eventKey);
|
|
851
883
|
this.v3ToolCallCount += 1;
|
|
884
|
+
const toolName = event.name || event.tool || event.tool_name || '';
|
|
885
|
+
emitDeckEvent({
|
|
886
|
+
type: 'tool_start',
|
|
887
|
+
tool: toolName,
|
|
888
|
+
index: this.v3ToolCallCount,
|
|
889
|
+
target: event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '',
|
|
890
|
+
});
|
|
852
891
|
const toolDesc = this.describeV3AgentTool(event.tool || event.name || event.tool_name);
|
|
853
892
|
const toolTarget = event.arguments?.path || event.arguments?.file_path || event.arguments?.pattern || '';
|
|
854
893
|
const sanitizedTarget = this.sanitizeServerPath(String(toolTarget));
|
|
@@ -859,16 +898,16 @@ export class ChatCommand {
|
|
|
859
898
|
this.writeAgentActivityLine(this.fitTerminalText(stepLabel) + '\n');
|
|
860
899
|
// Show extra detail for key tools
|
|
861
900
|
const args = event.arguments || {};
|
|
862
|
-
const
|
|
863
|
-
if ((
|
|
901
|
+
const toolNameForDetail = event.name || event.tool || '';
|
|
902
|
+
if ((toolNameForDetail === 'write_file' || toolNameForDetail === 'edit_file') && typeof args.content === 'string') {
|
|
864
903
|
const len = args.content.length;
|
|
865
904
|
this.writeAgentActivityLine(chalk.gray(` ${len > 1000 ? Math.round(len / 1024) + ' KB' : len + ' bytes'} content\n`));
|
|
866
905
|
}
|
|
867
|
-
else if (
|
|
906
|
+
else if (toolNameForDetail === 'bash' && typeof args.command === 'string') {
|
|
868
907
|
const command = this.sanitizeServerPath(args.command);
|
|
869
908
|
this.writeAgentActivityLine(chalk.gray(` ${this.fitTerminalText(`$ ${command}`, 6)}\n`));
|
|
870
909
|
}
|
|
871
|
-
else if (
|
|
910
|
+
else if (toolNameForDetail === 'read_file') {
|
|
872
911
|
this.writeAgentActivityLine(chalk.gray(` ${this.fitTerminalText(`reading ${sanitizedTarget || 'file'}`, 6)}\n`));
|
|
873
912
|
}
|
|
874
913
|
spinner.text = `Running ${toolDesc}...`;
|
|
@@ -883,6 +922,12 @@ export class ChatCommand {
|
|
|
883
922
|
this.v3SeenToolResults.add(eventKey);
|
|
884
923
|
const success = event.success !== false;
|
|
885
924
|
const toolName = event.name || event.tool || '';
|
|
925
|
+
emitDeckEvent({
|
|
926
|
+
type: 'tool_end',
|
|
927
|
+
tool: toolName,
|
|
928
|
+
success,
|
|
929
|
+
index: this.v3ToolCallCount,
|
|
930
|
+
});
|
|
886
931
|
const indicator = success ? chalk.green(' ✓') : chalk.red(' ✗');
|
|
887
932
|
if (spinner.isSpinning)
|
|
888
933
|
spinner.stop();
|
|
@@ -923,12 +968,15 @@ export class ChatCommand {
|
|
|
923
968
|
if (event.type === 'thinking') {
|
|
924
969
|
this.v3IterationCount += 1;
|
|
925
970
|
const iterText = this.sanitizeServerPath(event.content || '');
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
971
|
+
const isGenericStep = !iterText
|
|
972
|
+
|| /read-only analysis turn|run at least one discovery tool/i.test(iterText)
|
|
973
|
+
|| /^Step \d+:/i.test(iterText)
|
|
974
|
+
|| /reviewing attached files|inspecting workspace/i.test(iterText);
|
|
975
|
+
if (!isGenericStep) {
|
|
976
|
+
if (spinner.isSpinning)
|
|
977
|
+
spinner.stop();
|
|
978
|
+
this.writeAgentActivityLine(chalk.cyan(`\n── ${this.fitTerminalText(iterText, 6)} ──\n`));
|
|
979
|
+
}
|
|
932
980
|
spinner.text = 'Analyzing...';
|
|
933
981
|
return;
|
|
934
982
|
}
|
|
@@ -951,6 +999,14 @@ export class ChatCommand {
|
|
|
951
999
|
const tools = event.tool_calls || this.v3ToolCallCount;
|
|
952
1000
|
if (spinner.isSpinning)
|
|
953
1001
|
spinner.stop();
|
|
1002
|
+
this.displayV3StreamedAnswer();
|
|
1003
|
+
emitDeckEvent({
|
|
1004
|
+
type: 'run_complete',
|
|
1005
|
+
success: true,
|
|
1006
|
+
tools: event.tool_calls || this.v3ToolCallCount,
|
|
1007
|
+
iterations: event.iterations || this.v3IterationCount,
|
|
1008
|
+
elapsed: event.elapsed || '',
|
|
1009
|
+
});
|
|
954
1010
|
let statLine = `${iters} iterations, ${tools} tool calls`;
|
|
955
1011
|
if (elapsed)
|
|
956
1012
|
statLine += `, ${elapsed}`;
|
|
@@ -2545,8 +2601,10 @@ export class ChatCommand {
|
|
|
2545
2601
|
this.v3LastActivity = Date.now();
|
|
2546
2602
|
this.v3StreamingStarted = false;
|
|
2547
2603
|
this.v3StreamedAnswerBuffer = '';
|
|
2604
|
+
this.v3StreamedAnswerDisplayed = false;
|
|
2548
2605
|
this.v3SeenToolCalls.clear();
|
|
2549
2606
|
this.v3SeenToolResults.clear();
|
|
2607
|
+
emitDeckEvent({ type: 'agent_start' });
|
|
2550
2608
|
const taskDisplay = new TaskDisplay(['Analyse workspace', 'Execute tasks', 'Validate output', 'Self-heal'], false);
|
|
2551
2609
|
taskDisplay.start(0);
|
|
2552
2610
|
const spinner = this.jsonOutput ? null : createSpinner({
|
|
@@ -2731,9 +2789,8 @@ export class ChatCommand {
|
|
|
2731
2789
|
if (spinner) {
|
|
2732
2790
|
spinner.stop();
|
|
2733
2791
|
}
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
process.stdout.write('\n');
|
|
2792
|
+
if (this.v3StreamingStarted && !this.v3StreamedAnswerDisplayed) {
|
|
2793
|
+
this.displayV3StreamedAnswer();
|
|
2737
2794
|
}
|
|
2738
2795
|
const previewGate = (response.metadata?.previewGate || null);
|
|
2739
2796
|
const workspaceHasOutput = this.api.hasAgentWorkspaceOutput(workspaceContext);
|
|
@@ -2828,15 +2885,13 @@ export class ChatCommand {
|
|
|
2828
2885
|
executorSucceeded = false;
|
|
2829
2886
|
}
|
|
2830
2887
|
if (!this.jsonOutput && !this.directPromptMode) {
|
|
2831
|
-
if (this.
|
|
2832
|
-
|
|
2833
|
-
console.log(chalk.bold('Answer:'));
|
|
2834
|
-
console.log(this.v3StreamedAnswerBuffer.trim());
|
|
2888
|
+
if (this.v3StreamedAnswerDisplayed) {
|
|
2889
|
+
// Highlighted report already printed once above.
|
|
2835
2890
|
}
|
|
2836
2891
|
else if (isSubstantiveAgentAnswer(finalAnswer)) {
|
|
2837
2892
|
console.log('');
|
|
2838
2893
|
console.log(chalk.bold('Answer:'));
|
|
2839
|
-
console.log(finalAnswer);
|
|
2894
|
+
console.log(this.renderAgentAnswerForTerminal(finalAnswer));
|
|
2840
2895
|
}
|
|
2841
2896
|
else if (!requiresWorkspaceChanges) {
|
|
2842
2897
|
console.log('');
|
|
@@ -2845,7 +2900,7 @@ export class ChatCommand {
|
|
|
2845
2900
|
}
|
|
2846
2901
|
else if (finalAnswer) {
|
|
2847
2902
|
console.log('');
|
|
2848
|
-
console.log(finalAnswer);
|
|
2903
|
+
console.log(this.renderAgentAnswerForTerminal(finalAnswer));
|
|
2849
2904
|
}
|
|
2850
2905
|
}
|
|
2851
2906
|
if (!executorSucceeded && requiresWorkspaceChanges && changedFileCount === 0 && !liveOutcome.executorError) {
|
|
@@ -3369,6 +3424,15 @@ export class ChatCommand {
|
|
|
3369
3424
|
*/
|
|
3370
3425
|
printAgentRunSummary(outcome, evaluation, changedFileCount) {
|
|
3371
3426
|
const executorSucceeded = evaluation.executorSucceeded;
|
|
3427
|
+
if (outcome.answerContent && isSubstantiveAgentAnswer(outcome.answerContent)) {
|
|
3428
|
+
const reportSummary = summarizeMarkdownReport(outcome.answerContent);
|
|
3429
|
+
emitDeckEvent({
|
|
3430
|
+
type: 'run_summary',
|
|
3431
|
+
success: executorSucceeded,
|
|
3432
|
+
title: reportSummary?.title || 'Agent run complete',
|
|
3433
|
+
sections: reportSummary?.sections || [],
|
|
3434
|
+
});
|
|
3435
|
+
}
|
|
3372
3436
|
const bar = chalk.gray('─'.repeat(63));
|
|
3373
3437
|
const ok = chalk.green('✓');
|
|
3374
3438
|
const warn = chalk.yellow('⚠');
|
|
@@ -3456,10 +3520,20 @@ export class ChatCommand {
|
|
|
3456
3520
|
console.log(' ' + chalk.white(taskLine + '.'));
|
|
3457
3521
|
}
|
|
3458
3522
|
else if (outcome.answerContent && isSubstantiveAgentAnswer(outcome.answerContent)) {
|
|
3459
|
-
const
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3523
|
+
const reportSummary = summarizeMarkdownReport(outcome.answerContent);
|
|
3524
|
+
if (reportSummary) {
|
|
3525
|
+
const sectionPreview = reportSummary.sections.length > 0
|
|
3526
|
+
? reportSummary.sections.slice(0, 4).join(', ')
|
|
3527
|
+
: `${Math.round(reportSummary.charCount / 1000)}k chars`;
|
|
3528
|
+
console.log(' ' + chalk.white(reportSummary.title));
|
|
3529
|
+
console.log(' ' + chalk.gray(`Sections: ${sectionPreview}`));
|
|
3530
|
+
}
|
|
3531
|
+
else {
|
|
3532
|
+
const preview = outcome.answerContent.length > 160
|
|
3533
|
+
? `${outcome.answerContent.slice(0, 160)}…`
|
|
3534
|
+
: outcome.answerContent;
|
|
3535
|
+
console.log(' ' + chalk.white(preview));
|
|
3536
|
+
}
|
|
3463
3537
|
}
|
|
3464
3538
|
else if (changedFileCount > 0) {
|
|
3465
3539
|
console.log(' ' + chalk.white('Workspace changes were produced and are ready for review.'));
|
package/dist/utils/api.js
CHANGED
|
@@ -1524,7 +1524,7 @@ export class APIClient {
|
|
|
1524
1524
|
if (json.length <= LIMIT)
|
|
1525
1525
|
return json;
|
|
1526
1526
|
if (process.env.DEBUG || process.env.VIGTHORIA_DEBUG) {
|
|
1527
|
-
|
|
1527
|
+
process.stderr.write(`[context] Payload ${json.length} chars exceeds ${LIMIT} limit, compacting...\n`);
|
|
1528
1528
|
}
|
|
1529
1529
|
// Phase 1 — shrink workspaceFiles to fit
|
|
1530
1530
|
const summary = payload.localWorkspaceSummary;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured events for Vigthoria Workbench (VIGTHORIA_DECK_MODE=1).
|
|
3
|
+
* Emitted as single lines: @vigthoria-deck:{json}
|
|
4
|
+
* Workbench strips these from terminal display and updates UI panels.
|
|
5
|
+
*/
|
|
6
|
+
export type DeckEventType = 'agent_start' | 'tool_start' | 'tool_end' | 'report_ready' | 'run_complete' | 'run_summary';
|
|
7
|
+
export interface DeckEvent {
|
|
8
|
+
type: DeckEventType;
|
|
9
|
+
ts: number;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
export declare function isDeckModeEnabled(): boolean;
|
|
13
|
+
export declare function emitDeckEvent(event: Omit<DeckEvent, 'ts'> & {
|
|
14
|
+
ts?: number;
|
|
15
|
+
}): void;
|
|
16
|
+
export declare function parseDeckLine(line: string): DeckEvent | null;
|
|
17
|
+
export declare function stripDeckLines(text: string): string;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured events for Vigthoria Workbench (VIGTHORIA_DECK_MODE=1).
|
|
3
|
+
* Emitted as single lines: @vigthoria-deck:{json}
|
|
4
|
+
* Workbench strips these from terminal display and updates UI panels.
|
|
5
|
+
*/
|
|
6
|
+
const DECK_PREFIX = '@vigthoria-deck:';
|
|
7
|
+
export function isDeckModeEnabled() {
|
|
8
|
+
return /^(1|true|yes)$/i.test(String(process.env.VIGTHORIA_DECK_MODE || ''));
|
|
9
|
+
}
|
|
10
|
+
export function emitDeckEvent(event) {
|
|
11
|
+
if (!isDeckModeEnabled())
|
|
12
|
+
return;
|
|
13
|
+
const payload = {
|
|
14
|
+
...event,
|
|
15
|
+
ts: event.ts ?? Date.now(),
|
|
16
|
+
};
|
|
17
|
+
process.stdout.write(`${DECK_PREFIX}${JSON.stringify(payload)}\n`);
|
|
18
|
+
}
|
|
19
|
+
export function parseDeckLine(line) {
|
|
20
|
+
const trimmed = line.trim();
|
|
21
|
+
if (!trimmed.startsWith(DECK_PREFIX))
|
|
22
|
+
return null;
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(trimmed.slice(DECK_PREFIX.length));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function stripDeckLines(text) {
|
|
31
|
+
return text
|
|
32
|
+
.split('\n')
|
|
33
|
+
.filter((line) => !line.trim().startsWith(DECK_PREFIX))
|
|
34
|
+
.join('\n');
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared terminal markdown rendering for CLI commands.
|
|
3
|
+
*/
|
|
4
|
+
export interface MarkdownReportSummary {
|
|
5
|
+
title: string;
|
|
6
|
+
sections: string[];
|
|
7
|
+
charCount: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Clean marked-terminal output for release-grade CLI presentation.
|
|
11
|
+
*/
|
|
12
|
+
export declare function cleanTerminalMarkdownOutput(raw: string): string;
|
|
13
|
+
export declare function looksLikeMarkdownReport(text: string): boolean;
|
|
14
|
+
export declare function renderMarkdownToTerminal(markdown: string, width?: number): string;
|
|
15
|
+
export declare function summarizeMarkdownReport(markdown: string): MarkdownReportSummary | null;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared terminal markdown rendering for CLI commands.
|
|
3
|
+
*/
|
|
4
|
+
import { Marked } from 'marked';
|
|
5
|
+
import { markedTerminal } from 'marked-terminal';
|
|
6
|
+
let cachedRenderer = null;
|
|
7
|
+
function getTerminalWidth() {
|
|
8
|
+
const cols = process.stdout.columns || 80;
|
|
9
|
+
return Math.max(60, Math.min(cols - 2, 100));
|
|
10
|
+
}
|
|
11
|
+
function getRenderer(width) {
|
|
12
|
+
const resolvedWidth = width ?? getTerminalWidth();
|
|
13
|
+
if (cachedRenderer && cachedRenderer.width === resolvedWidth) {
|
|
14
|
+
return cachedRenderer.marked;
|
|
15
|
+
}
|
|
16
|
+
const marked = new Marked();
|
|
17
|
+
marked.use(markedTerminal({
|
|
18
|
+
showSectionPrefix: false,
|
|
19
|
+
tab: 2,
|
|
20
|
+
width: resolvedWidth,
|
|
21
|
+
reflowText: true,
|
|
22
|
+
}));
|
|
23
|
+
cachedRenderer = { width: resolvedWidth, marked };
|
|
24
|
+
return marked;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Clean marked-terminal output for release-grade CLI presentation.
|
|
28
|
+
*/
|
|
29
|
+
export function cleanTerminalMarkdownOutput(raw) {
|
|
30
|
+
let out = raw;
|
|
31
|
+
out = out.replace(/^ \* /gm, '- ');
|
|
32
|
+
out = out.replace(/^ \* /gm, ' - ');
|
|
33
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, '');
|
|
34
|
+
out = out.split('\n').filter((line) => {
|
|
35
|
+
if (line === '')
|
|
36
|
+
return true;
|
|
37
|
+
return stripAnsi(line).trim().length > 0;
|
|
38
|
+
}).join('\n');
|
|
39
|
+
out = out.replace(/\n{3,}/g, '\n\n');
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
export function looksLikeMarkdownReport(text) {
|
|
43
|
+
const trimmed = String(text || '').trim();
|
|
44
|
+
if (!trimmed)
|
|
45
|
+
return false;
|
|
46
|
+
return /^#{1,3}\s+\S/m.test(trimmed)
|
|
47
|
+
|| /^\|.+\|/m.test(trimmed)
|
|
48
|
+
|| /^[-*]\s+\*\*/m.test(trimmed);
|
|
49
|
+
}
|
|
50
|
+
export function renderMarkdownToTerminal(markdown, width) {
|
|
51
|
+
const input = String(markdown || '').trim();
|
|
52
|
+
if (!input)
|
|
53
|
+
return '';
|
|
54
|
+
if (!looksLikeMarkdownReport(input)) {
|
|
55
|
+
return input;
|
|
56
|
+
}
|
|
57
|
+
const rendered = getRenderer(width).parse(input);
|
|
58
|
+
return cleanTerminalMarkdownOutput(rendered);
|
|
59
|
+
}
|
|
60
|
+
export function summarizeMarkdownReport(markdown) {
|
|
61
|
+
const text = String(markdown || '').trim();
|
|
62
|
+
if (!text || !looksLikeMarkdownReport(text)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const lines = text.split('\n');
|
|
66
|
+
let title = '';
|
|
67
|
+
const sections = [];
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
const h1 = line.match(/^#\s+(.+)$/);
|
|
70
|
+
if (h1 && !title) {
|
|
71
|
+
title = h1[1].replace(/[*_`]/g, '').trim();
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const h2 = line.match(/^##\s+(.+)$/);
|
|
75
|
+
if (h2) {
|
|
76
|
+
const section = h2[1].replace(/[*_`]/g, '').trim();
|
|
77
|
+
if (section && sections.length < 8) {
|
|
78
|
+
sections.push(section);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!title) {
|
|
83
|
+
const firstLine = lines.find((line) => line.trim().length > 0)?.trim() || 'Analysis report';
|
|
84
|
+
title = firstLine.replace(/^#+\s*/, '').replace(/[*_`]/g, '').slice(0, 120);
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
title,
|
|
88
|
+
sections,
|
|
89
|
+
charCount: text.length,
|
|
90
|
+
};
|
|
91
|
+
}
|