@compilr-dev/cli 0.6.6 → 0.7.1
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-v2/handlers/core.js +10 -1
- package/dist/commands-v2/handlers/index.d.ts +1 -0
- package/dist/commands-v2/handlers/index.js +3 -0
- package/dist/commands-v2/handlers/settings.js +21 -5
- package/dist/commands-v2/handlers/skill.d.ts +10 -0
- package/dist/commands-v2/handlers/skill.js +63 -0
- package/dist/commands-v2/index.d.ts +1 -1
- package/dist/commands-v2/index.js +1 -1
- package/dist/commands-v2/registry.d.ts +4 -0
- package/dist/commands-v2/registry.js +19 -0
- package/dist/compilr-diff-companion.vsix +0 -0
- package/dist/index.js +8 -12
- package/dist/repl-helpers.d.ts +29 -1
- package/dist/repl-helpers.js +77 -7
- package/dist/repl-v2.js +29 -3
- package/dist/tools/delegation-status.d.ts +3 -12
- package/dist/tools/delegation-status.js +4 -123
- package/dist/tools/handoff.d.ts +9 -17
- package/dist/tools/handoff.js +28 -48
- package/dist/ui/conversation.js +1 -2
- package/dist/ui/markdown-renderer.d.ts +43 -0
- package/dist/ui/markdown-renderer.js +474 -0
- package/dist/ui/overlay/impl/artifact-detail-overlay-v2.js +1 -2
- package/dist/ui/overlay/impl/document-detail-overlay-v2.js +1 -2
- package/dist/ui/overlay/impl/help-overlay-v2.d.ts +7 -1
- package/dist/ui/overlay/impl/help-overlay-v2.js +19 -2
- package/dist/ui/overlay/impl/skill-detail-overlay-v2.d.ts +91 -0
- package/dist/ui/overlay/impl/skill-detail-overlay-v2.js +863 -0
- package/dist/ui/overlay/impl/skill-editor-overlay.d.ts +56 -0
- package/dist/ui/overlay/impl/skill-editor-overlay.js +493 -0
- package/dist/ui/overlay/impl/skills-overlay-v2.d.ts +83 -0
- package/dist/ui/overlay/impl/skills-overlay-v2.js +1095 -0
- package/dist/utils/skill-paths.d.ts +21 -0
- package/dist/utils/skill-paths.js +44 -0
- package/package.json +9 -6
package/dist/tools/handoff.d.ts
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Handoff Tool - Multi-Agent Task Handoff
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*
|
|
9
|
-
* 2. CLI shows approval prompt (unless auto-approve is enabled)
|
|
10
|
-
* 3. If approved: auto-switch to target agent, inject task
|
|
11
|
-
* 4. If denied: return "User declined" to current agent
|
|
4
|
+
* Uses SDK's createHandoffTool factory with CLI-specific handler.
|
|
5
|
+
* Exported as a static tool that lazily resolves dependencies.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Handoff tool — delegates to the CLI's handoff handler.
|
|
12
9
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* The handler (registered by delegation-handlers.ts) manages team validation,
|
|
11
|
+
* one-hop enforcement, user approval, and agent switching.
|
|
15
12
|
*/
|
|
16
|
-
|
|
17
|
-
/** Target agent ID (e.g., 'arch', 'dev', 'qa', 'default') */
|
|
13
|
+
export declare const handoffTool: import("@compilr-dev/sdk").Tool<{
|
|
18
14
|
agentId: string;
|
|
19
|
-
/** Task description for the target agent */
|
|
20
15
|
task: string;
|
|
21
|
-
/** Optional reason for handoff (shown to user for context) */
|
|
22
16
|
reason?: string;
|
|
23
|
-
}
|
|
24
|
-
export declare const handoffTool: import("@compilr-dev/sdk").Tool<HandoffInput>;
|
|
25
|
-
export {};
|
|
17
|
+
}>;
|
package/dist/tools/handoff.js
CHANGED
|
@@ -1,23 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Handoff Tool - Multi-Agent Task Handoff
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Flow:
|
|
8
|
-
* 1. Agent calls handoff(agentId, task, reason?)
|
|
9
|
-
* 2. CLI shows approval prompt (unless auto-approve is enabled)
|
|
10
|
-
* 3. If approved: auto-switch to target agent, inject task
|
|
11
|
-
* 4. If denied: return "User declined" to current agent
|
|
12
|
-
*
|
|
13
|
-
* One-hop rule: If this agent was itself handed a task, it cannot
|
|
14
|
-
* re-hand it off (except back to the coordinator/default).
|
|
4
|
+
* Uses SDK's createHandoffTool factory with CLI-specific handler.
|
|
5
|
+
* Exported as a static tool that lazily resolves dependencies.
|
|
15
6
|
*/
|
|
16
7
|
import { defineTool } from '@compilr-dev/sdk';
|
|
17
8
|
import { getHandoffHandler } from '../shared-handlers.js';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Handoff tool — delegates to the CLI's handoff handler.
|
|
11
|
+
*
|
|
12
|
+
* The handler (registered by delegation-handlers.ts) manages team validation,
|
|
13
|
+
* one-hop enforcement, user approval, and agent switching.
|
|
14
|
+
*/
|
|
21
15
|
export const handoffTool = defineTool({
|
|
22
16
|
name: 'handoff',
|
|
23
17
|
description: 'Hand off the current task to another team agent. ' +
|
|
@@ -43,50 +37,36 @@ export const handoffTool = defineTool({
|
|
|
43
37
|
required: ['agentId', 'task'],
|
|
44
38
|
},
|
|
45
39
|
execute: async (input) => {
|
|
40
|
+
if (!input.agentId || input.agentId.trim().length === 0) {
|
|
41
|
+
return { success: false, error: 'Agent ID is required' };
|
|
42
|
+
}
|
|
43
|
+
if (!input.task || input.task.trim().length === 0) {
|
|
44
|
+
return { success: false, error: 'Task description is required' };
|
|
45
|
+
}
|
|
46
|
+
const handoffHandler = getHandoffHandler();
|
|
47
|
+
if (!handoffHandler) {
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
error: 'Handoff handler not initialized. This tool requires multi-agent mode with a team.',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
46
53
|
try {
|
|
47
|
-
// Validate input
|
|
48
|
-
if (!input.agentId || input.agentId.trim().length === 0) {
|
|
49
|
-
return {
|
|
50
|
-
success: false,
|
|
51
|
-
error: 'Agent ID is required',
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
if (!input.task || input.task.trim().length === 0) {
|
|
55
|
-
return {
|
|
56
|
-
success: false,
|
|
57
|
-
error: 'Task description is required',
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
// Get the handoff handler from shared-handlers
|
|
61
|
-
const handoffHandler = getHandoffHandler();
|
|
62
|
-
if (!handoffHandler) {
|
|
63
|
-
return {
|
|
64
|
-
success: false,
|
|
65
|
-
error: 'Handoff handler not initialized. ' +
|
|
66
|
-
'This tool requires multi-agent mode with a team.',
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
// Execute handoff (shows approval prompt, switches if approved)
|
|
70
54
|
const result = await handoffHandler({
|
|
71
55
|
agentId: input.agentId.trim(),
|
|
72
56
|
task: input.task.trim(),
|
|
73
57
|
reason: input.reason?.trim(),
|
|
74
58
|
});
|
|
75
59
|
if (result.error) {
|
|
76
|
-
return {
|
|
77
|
-
success: false,
|
|
78
|
-
error: result.error,
|
|
79
|
-
};
|
|
60
|
+
return { success: false, error: result.error };
|
|
80
61
|
}
|
|
81
|
-
const output = {
|
|
82
|
-
handedOff: result.success,
|
|
83
|
-
message: result.declined
|
|
84
|
-
? 'User declined the handoff. Continue handling the task yourself.'
|
|
85
|
-
: `Task handed off to $${input.agentId}. They will handle it from here.`,
|
|
86
|
-
};
|
|
87
62
|
return {
|
|
88
63
|
success: true,
|
|
89
|
-
result:
|
|
64
|
+
result: {
|
|
65
|
+
handedOff: !result.declined,
|
|
66
|
+
message: result.declined
|
|
67
|
+
? 'User declined the handoff. Continue handling the task yourself.'
|
|
68
|
+
: `Task handed off to $${input.agentId}. They will handle it from here.`,
|
|
69
|
+
},
|
|
90
70
|
};
|
|
91
71
|
}
|
|
92
72
|
catch (err) {
|
package/dist/ui/conversation.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Chalk } from 'chalk';
|
|
9
9
|
import { marked } from 'marked';
|
|
10
|
-
import { markedTerminal } from '
|
|
10
|
+
import { markedTerminal } from './markdown-renderer.js';
|
|
11
11
|
import { highlight } from 'cli-highlight';
|
|
12
12
|
import { getStyles } from '../themes/index.js';
|
|
13
13
|
import { getThemeColors } from '../themes/index.js';
|
|
@@ -153,7 +153,6 @@ export function renderMarkdown(text) {
|
|
|
153
153
|
const preprocessed = preprocessMarkdown(text);
|
|
154
154
|
// Configure marked with theme-aware options
|
|
155
155
|
const options = getThemedMarkedOptions();
|
|
156
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
|
157
156
|
marked.use(markedTerminal(options));
|
|
158
157
|
// Parse
|
|
159
158
|
const rendered = marked.parse(preprocessed, { async: false });
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal markdown renderer for marked.
|
|
3
|
+
* Drop-in replacement for `marked-terminal` — returns a marked extension.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { markedTerminal } from './markdown-renderer.js';
|
|
7
|
+
* marked.use(markedTerminal(options));
|
|
8
|
+
*/
|
|
9
|
+
type ChalkFn = (text: string) => string;
|
|
10
|
+
export interface TerminalRendererOptions {
|
|
11
|
+
heading?: ChalkFn;
|
|
12
|
+
firstHeading?: ChalkFn;
|
|
13
|
+
strong?: ChalkFn;
|
|
14
|
+
em?: ChalkFn;
|
|
15
|
+
codespan?: ChalkFn;
|
|
16
|
+
code?: ChalkFn;
|
|
17
|
+
blockquote?: ChalkFn;
|
|
18
|
+
del?: ChalkFn;
|
|
19
|
+
link?: ChalkFn;
|
|
20
|
+
href?: ChalkFn;
|
|
21
|
+
html?: ChalkFn;
|
|
22
|
+
hr?: ChalkFn;
|
|
23
|
+
table?: ChalkFn;
|
|
24
|
+
listitem?: ChalkFn;
|
|
25
|
+
paragraph?: ChalkFn;
|
|
26
|
+
text?: ChalkFn;
|
|
27
|
+
width?: number;
|
|
28
|
+
tab?: number;
|
|
29
|
+
reflowText?: boolean;
|
|
30
|
+
showSectionPrefix?: boolean;
|
|
31
|
+
unescape?: boolean;
|
|
32
|
+
emoji?: boolean;
|
|
33
|
+
tableOptions?: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Returns a marked extension that renders markdown to ANSI terminal output.
|
|
37
|
+
* Drop-in replacement for `markedTerminal` from the `marked-terminal` package.
|
|
38
|
+
*/
|
|
39
|
+
export declare function markedTerminal(options?: TerminalRendererOptions): {
|
|
40
|
+
renderer: Record<string, unknown>;
|
|
41
|
+
useNewRenderer: boolean;
|
|
42
|
+
};
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Terminal markdown renderer for marked.
|
|
3
|
+
* Drop-in replacement for `marked-terminal` — returns a marked extension.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* import { markedTerminal } from './markdown-renderer.js';
|
|
7
|
+
* marked.use(markedTerminal(options));
|
|
8
|
+
*/
|
|
9
|
+
import { Chalk } from 'chalk';
|
|
10
|
+
import { highlight as cliHighlight } from 'cli-highlight';
|
|
11
|
+
import Table from 'cli-table3';
|
|
12
|
+
import ansiEscapes from 'ansi-escapes';
|
|
13
|
+
import supportsHyperlinks from 'supports-hyperlinks';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Constants
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const TABLE_CELL_SPLIT = '^*||*^';
|
|
18
|
+
const TABLE_ROW_WRAP = '*|*|*|*';
|
|
19
|
+
const TABLE_ROW_WRAP_RE = new RegExp(TABLE_ROW_WRAP.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
|
|
20
|
+
const COLON_REPLACER = '*#COLON|*';
|
|
21
|
+
const COLON_REPLACER_RE = new RegExp(COLON_REPLACER.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g');
|
|
22
|
+
const HARD_RETURN = '\r';
|
|
23
|
+
const HARD_RETURN_RE = new RegExp(HARD_RETURN);
|
|
24
|
+
const HARD_RETURN_GFM_RE = new RegExp(HARD_RETURN + '|<br />');
|
|
25
|
+
const BULLET_POINT = '* ';
|
|
26
|
+
// eslint-disable-next-line no-control-regex
|
|
27
|
+
const ANSI_RE = /\u001b\[(?:\d{1,3})(?:;\d{1,3})*m/g;
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Defaults
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
const chalk = new Chalk({ level: 3 });
|
|
32
|
+
const defaultOptions = {
|
|
33
|
+
heading: chalk.green.bold,
|
|
34
|
+
firstHeading: chalk.magenta.underline.bold,
|
|
35
|
+
strong: chalk.bold,
|
|
36
|
+
em: chalk.italic,
|
|
37
|
+
codespan: chalk.yellow,
|
|
38
|
+
code: chalk.yellow,
|
|
39
|
+
blockquote: chalk.gray.italic,
|
|
40
|
+
del: chalk.dim.gray.strikethrough,
|
|
41
|
+
link: chalk.blue,
|
|
42
|
+
href: chalk.blue.underline,
|
|
43
|
+
html: chalk.gray,
|
|
44
|
+
hr: chalk.reset,
|
|
45
|
+
table: chalk.reset,
|
|
46
|
+
listitem: chalk.reset,
|
|
47
|
+
paragraph: chalk.reset,
|
|
48
|
+
text: (s) => s,
|
|
49
|
+
width: 80,
|
|
50
|
+
tab: 2,
|
|
51
|
+
reflowText: true,
|
|
52
|
+
showSectionPrefix: false,
|
|
53
|
+
unescape: true,
|
|
54
|
+
emoji: false,
|
|
55
|
+
tableOptions: {},
|
|
56
|
+
};
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Utilities
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
function identity(s) { return s; }
|
|
61
|
+
function textLength(str) {
|
|
62
|
+
return str.replace(ANSI_RE, '').length;
|
|
63
|
+
}
|
|
64
|
+
function section(text) {
|
|
65
|
+
return text + '\n\n';
|
|
66
|
+
}
|
|
67
|
+
function indentify(indent, text) {
|
|
68
|
+
if (!text)
|
|
69
|
+
return text;
|
|
70
|
+
return indent + text.split('\n').join('\n' + indent);
|
|
71
|
+
}
|
|
72
|
+
function indentLines(indent, text) {
|
|
73
|
+
return text.replace(/(^|\n)(.+)/g, '$1' + indent + '$2');
|
|
74
|
+
}
|
|
75
|
+
function unescapeEntities(html) {
|
|
76
|
+
return html
|
|
77
|
+
.replace(/&/g, '&')
|
|
78
|
+
.replace(/</g, '<')
|
|
79
|
+
.replace(/>/g, '>')
|
|
80
|
+
.replace(/"/g, '"')
|
|
81
|
+
.replace(/'/g, "'");
|
|
82
|
+
}
|
|
83
|
+
function undoColon(str) {
|
|
84
|
+
return str.replace(COLON_REPLACER_RE, ':');
|
|
85
|
+
}
|
|
86
|
+
function fixHardReturn(text, reflow) {
|
|
87
|
+
return reflow ? text.replace(HARD_RETURN_RE, '\n') : text;
|
|
88
|
+
}
|
|
89
|
+
function hr(char, length) {
|
|
90
|
+
const width = length || process.stdout.columns || 80;
|
|
91
|
+
return char.repeat(width);
|
|
92
|
+
}
|
|
93
|
+
function highlightCode(code, language, fallbackStyle) {
|
|
94
|
+
if (chalk.level === 0)
|
|
95
|
+
return code;
|
|
96
|
+
try {
|
|
97
|
+
return cliHighlight(code, { language: language || 'plaintext' });
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return fallbackStyle(code);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Reflow
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
function reflowText(text, width, gfm) {
|
|
107
|
+
const splitRe = gfm ? HARD_RETURN_GFM_RE : HARD_RETURN_RE;
|
|
108
|
+
const sections = text.split(splitRe);
|
|
109
|
+
const reflowed = [];
|
|
110
|
+
for (const sec of sections) {
|
|
111
|
+
// eslint-disable-next-line no-control-regex
|
|
112
|
+
const fragments = sec.split(/(\u001b\[(?:\d{1,3})(?:;\d{1,3})*m)/g);
|
|
113
|
+
let column = 0;
|
|
114
|
+
let currentLine = '';
|
|
115
|
+
let lastWasEscape = false;
|
|
116
|
+
for (const fragment of fragments) {
|
|
117
|
+
if (fragment === '') {
|
|
118
|
+
lastWasEscape = false;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
if (!textLength(fragment)) {
|
|
122
|
+
currentLine += fragment;
|
|
123
|
+
lastWasEscape = true;
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const words = fragment.split(/[ \t\n]+/);
|
|
127
|
+
for (const word of words) {
|
|
128
|
+
if (!word)
|
|
129
|
+
continue;
|
|
130
|
+
const addSpace = column !== 0 && !lastWasEscape;
|
|
131
|
+
const neededWidth = word.length + (addSpace ? 1 : 0);
|
|
132
|
+
if (column + neededWidth > width && word.length <= width) {
|
|
133
|
+
reflowed.push(currentLine);
|
|
134
|
+
currentLine = word;
|
|
135
|
+
column = word.length;
|
|
136
|
+
}
|
|
137
|
+
else if (column + neededWidth > width) {
|
|
138
|
+
// Word longer than width — split it
|
|
139
|
+
let remaining = word;
|
|
140
|
+
const space = width - column - (addSpace ? 1 : 0);
|
|
141
|
+
if (space > 0) {
|
|
142
|
+
if (addSpace)
|
|
143
|
+
currentLine += ' ';
|
|
144
|
+
currentLine += remaining.substring(0, space);
|
|
145
|
+
reflowed.push(currentLine);
|
|
146
|
+
remaining = remaining.substring(space);
|
|
147
|
+
}
|
|
148
|
+
while (remaining.length > width) {
|
|
149
|
+
reflowed.push(remaining.substring(0, width));
|
|
150
|
+
remaining = remaining.substring(width);
|
|
151
|
+
}
|
|
152
|
+
currentLine = remaining;
|
|
153
|
+
column = remaining.length;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
if (addSpace) {
|
|
157
|
+
currentLine += ' ';
|
|
158
|
+
column++;
|
|
159
|
+
}
|
|
160
|
+
currentLine += word;
|
|
161
|
+
column += word.length;
|
|
162
|
+
}
|
|
163
|
+
lastWasEscape = false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (textLength(currentLine))
|
|
167
|
+
reflowed.push(currentLine);
|
|
168
|
+
}
|
|
169
|
+
return reflowed.join('\n');
|
|
170
|
+
}
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// List helpers
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
function isPointedLine(line, indent) {
|
|
175
|
+
const pointRe = new RegExp('^(?:' + indent.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')*(?:\\*|\\d+\\.)');
|
|
176
|
+
return pointRe.test(line);
|
|
177
|
+
}
|
|
178
|
+
function bulletPointLines(lines, indent) {
|
|
179
|
+
return lines.split('\n').filter(Boolean).map(line => isPointedLine(line, indent) ? line : ' '.repeat(BULLET_POINT.length) + line).join('\n');
|
|
180
|
+
}
|
|
181
|
+
function numberedLines(lines, indent) {
|
|
182
|
+
let num = 0;
|
|
183
|
+
return lines.split('\n').filter(Boolean).map(line => {
|
|
184
|
+
if (isPointedLine(line, indent)) {
|
|
185
|
+
num++;
|
|
186
|
+
return line.replace(BULLET_POINT, `${String(num)}. `);
|
|
187
|
+
}
|
|
188
|
+
return ' '.repeat(`${String(num)}. `.length) + line;
|
|
189
|
+
}).join('\n');
|
|
190
|
+
}
|
|
191
|
+
function fixNestedLists(body, indent) {
|
|
192
|
+
const pointRe = '(?:\\*|\\d+\\.)';
|
|
193
|
+
const regex = new RegExp('(\\S(?: | )?)' +
|
|
194
|
+
'((?:' + indent.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + ')+)' +
|
|
195
|
+
'(' + pointRe + '(?:.*)+)$', 'gm');
|
|
196
|
+
return body.replace(regex, '$1\n' + indent + '$2$3');
|
|
197
|
+
}
|
|
198
|
+
// ---------------------------------------------------------------------------
|
|
199
|
+
// Table helpers
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
function generateTableRow(text, escape) {
|
|
202
|
+
if (!text)
|
|
203
|
+
return [];
|
|
204
|
+
const esc = escape || identity;
|
|
205
|
+
const lines = esc(text).split('\n');
|
|
206
|
+
const data = [];
|
|
207
|
+
for (const line of lines) {
|
|
208
|
+
if (!line)
|
|
209
|
+
continue;
|
|
210
|
+
const parsed = line.replace(TABLE_ROW_WRAP_RE, '').split(TABLE_CELL_SPLIT);
|
|
211
|
+
data.push(parsed.slice(0, -1));
|
|
212
|
+
}
|
|
213
|
+
return data;
|
|
214
|
+
}
|
|
215
|
+
// ---------------------------------------------------------------------------
|
|
216
|
+
// Renderer
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
class TerminalMarkdownRenderer {
|
|
219
|
+
o;
|
|
220
|
+
tab;
|
|
221
|
+
tableSettings;
|
|
222
|
+
transform;
|
|
223
|
+
// Set by marked extension wrapper before each call
|
|
224
|
+
parser = { parse: () => '', parseInline: () => '' };
|
|
225
|
+
options = {};
|
|
226
|
+
constructor(opts) {
|
|
227
|
+
this.o = { ...defaultOptions, ...opts };
|
|
228
|
+
this.tab = typeof this.o.tab === 'number' ? ' '.repeat(this.o.tab) : ' ';
|
|
229
|
+
this.tableSettings = this.o.tableOptions;
|
|
230
|
+
const unescape = this.o.unescape ? unescapeEntities : identity;
|
|
231
|
+
this.transform = (s) => undoColon(unescape(s));
|
|
232
|
+
}
|
|
233
|
+
space() { return ''; }
|
|
234
|
+
text(token) {
|
|
235
|
+
const t = typeof token === 'object' ? (token.text ?? '') : token;
|
|
236
|
+
return this.o.text(t);
|
|
237
|
+
}
|
|
238
|
+
code(token, lang) {
|
|
239
|
+
let code;
|
|
240
|
+
if (typeof token === 'object') {
|
|
241
|
+
lang = token.lang;
|
|
242
|
+
code = token.text;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
code = token;
|
|
246
|
+
}
|
|
247
|
+
code = fixHardReturn(code, this.o.reflowText);
|
|
248
|
+
return section(indentify(this.tab, highlightCode(code, lang, this.o.code)));
|
|
249
|
+
}
|
|
250
|
+
blockquote(token) {
|
|
251
|
+
let quote;
|
|
252
|
+
if (typeof token === 'object' && token.tokens) {
|
|
253
|
+
quote = this.parser.parse(token.tokens);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
quote = typeof token === 'string' ? token : '';
|
|
257
|
+
}
|
|
258
|
+
return section(this.o.blockquote(indentify(this.tab, quote.trim())));
|
|
259
|
+
}
|
|
260
|
+
html(token) {
|
|
261
|
+
const t = typeof token === 'object' ? (token.text ?? '') : token;
|
|
262
|
+
return this.o.html(t);
|
|
263
|
+
}
|
|
264
|
+
heading(token, level) {
|
|
265
|
+
let text;
|
|
266
|
+
if (typeof token === 'object' && token.tokens) {
|
|
267
|
+
level = token.depth;
|
|
268
|
+
text = this.parser.parseInline(token.tokens);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
text = typeof token === 'string' ? token : '';
|
|
272
|
+
}
|
|
273
|
+
text = this.transform(text);
|
|
274
|
+
const prefix = this.o.showSectionPrefix ? '#'.repeat(level ?? 1) + ' ' : '';
|
|
275
|
+
text = prefix + text;
|
|
276
|
+
if (this.o.reflowText)
|
|
277
|
+
text = reflowText(text, this.o.width, !!this.options.gfm);
|
|
278
|
+
return section(level === 1 ? this.o.firstHeading(text) : this.o.heading(text));
|
|
279
|
+
}
|
|
280
|
+
hr() {
|
|
281
|
+
return section(this.o.hr(hr('─', this.o.reflowText ? this.o.width : undefined)));
|
|
282
|
+
}
|
|
283
|
+
list(token, ordered) {
|
|
284
|
+
let body;
|
|
285
|
+
if (typeof token === 'object' && token.items) {
|
|
286
|
+
ordered = token.ordered;
|
|
287
|
+
body = '';
|
|
288
|
+
for (const item of token.items) {
|
|
289
|
+
body += this.listitem(item);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
body = typeof token === 'string' ? token : '';
|
|
294
|
+
}
|
|
295
|
+
body = body.trim();
|
|
296
|
+
body = ordered ? numberedLines(body, this.tab) : bulletPointLines(body, this.tab);
|
|
297
|
+
return section(fixNestedLists(indentLines(this.tab, body), this.tab));
|
|
298
|
+
}
|
|
299
|
+
listitem(token) {
|
|
300
|
+
let text;
|
|
301
|
+
if (typeof token === 'object' && token.tokens) {
|
|
302
|
+
const item = token;
|
|
303
|
+
text = '';
|
|
304
|
+
if (item.task) {
|
|
305
|
+
const cb = item.checked ? '[X] ' : '[ ] ';
|
|
306
|
+
text += cb;
|
|
307
|
+
}
|
|
308
|
+
text += this.parser.parse(item.tokens ?? [], !!item.loose);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
text = typeof token === 'string' ? token : '';
|
|
312
|
+
}
|
|
313
|
+
const transform = (s) => this.o.listitem(this.transform(s));
|
|
314
|
+
const isNested = text.includes('\n');
|
|
315
|
+
if (isNested)
|
|
316
|
+
text = text.trim();
|
|
317
|
+
return '\n' + BULLET_POINT + transform(text);
|
|
318
|
+
}
|
|
319
|
+
checkbox(token) {
|
|
320
|
+
const checked = typeof token === 'object' ? token.checked : token;
|
|
321
|
+
return '[' + (checked ? 'X' : ' ') + '] ';
|
|
322
|
+
}
|
|
323
|
+
paragraph(token) {
|
|
324
|
+
let text;
|
|
325
|
+
if (typeof token === 'object' && token.tokens) {
|
|
326
|
+
text = this.parser.parseInline(token.tokens);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
text = typeof token === 'string' ? token : '';
|
|
330
|
+
}
|
|
331
|
+
text = this.o.paragraph(this.transform(text));
|
|
332
|
+
if (this.o.reflowText)
|
|
333
|
+
text = reflowText(text, this.o.width, !!this.options.gfm);
|
|
334
|
+
return section(text);
|
|
335
|
+
}
|
|
336
|
+
table(token, body) {
|
|
337
|
+
let header;
|
|
338
|
+
if (typeof token === 'object' && token.header) {
|
|
339
|
+
header = '';
|
|
340
|
+
for (const cell of token.header) {
|
|
341
|
+
header += this.parser.parseInline(cell.tokens) + TABLE_CELL_SPLIT;
|
|
342
|
+
}
|
|
343
|
+
header = TABLE_ROW_WRAP + header + TABLE_ROW_WRAP + '\n';
|
|
344
|
+
body = '';
|
|
345
|
+
for (const row of token.rows ?? []) {
|
|
346
|
+
let rowStr = '';
|
|
347
|
+
for (const cell of row) {
|
|
348
|
+
rowStr += this.parser.parseInline(cell.tokens) + TABLE_CELL_SPLIT;
|
|
349
|
+
}
|
|
350
|
+
body += TABLE_ROW_WRAP + rowStr + TABLE_ROW_WRAP + '\n';
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else {
|
|
354
|
+
header = typeof token === 'string' ? token : '';
|
|
355
|
+
}
|
|
356
|
+
const table = new Table({
|
|
357
|
+
head: generateTableRow(header)[0] ?? [],
|
|
358
|
+
...this.tableSettings,
|
|
359
|
+
});
|
|
360
|
+
for (const row of generateTableRow(body ?? '', this.transform)) {
|
|
361
|
+
table.push(row);
|
|
362
|
+
}
|
|
363
|
+
return section(this.o.table(table.toString()));
|
|
364
|
+
}
|
|
365
|
+
tablerow(token) {
|
|
366
|
+
const content = typeof token === 'object' ? (token.text ?? '') : token;
|
|
367
|
+
return TABLE_ROW_WRAP + content + TABLE_ROW_WRAP + '\n';
|
|
368
|
+
}
|
|
369
|
+
tablecell(token) {
|
|
370
|
+
if (typeof token === 'object' && token.tokens) {
|
|
371
|
+
return this.parser.parseInline(token.tokens) + TABLE_CELL_SPLIT;
|
|
372
|
+
}
|
|
373
|
+
return (typeof token === 'string' ? token : '') + TABLE_CELL_SPLIT;
|
|
374
|
+
}
|
|
375
|
+
strong(token) {
|
|
376
|
+
let text;
|
|
377
|
+
if (typeof token === 'object' && token.tokens) {
|
|
378
|
+
text = this.parser.parseInline(token.tokens);
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
text = typeof token === 'string' ? token : '';
|
|
382
|
+
}
|
|
383
|
+
return this.o.strong(text);
|
|
384
|
+
}
|
|
385
|
+
em(token) {
|
|
386
|
+
let text;
|
|
387
|
+
if (typeof token === 'object' && token.tokens) {
|
|
388
|
+
text = this.parser.parseInline(token.tokens);
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
text = typeof token === 'string' ? token : '';
|
|
392
|
+
}
|
|
393
|
+
return this.o.em(fixHardReturn(text, this.o.reflowText));
|
|
394
|
+
}
|
|
395
|
+
codespan(token) {
|
|
396
|
+
let text = typeof token === 'object' ? (token.text ?? '') : token;
|
|
397
|
+
text = fixHardReturn(text, this.o.reflowText);
|
|
398
|
+
return this.o.codespan(text.replace(/:/g, COLON_REPLACER));
|
|
399
|
+
}
|
|
400
|
+
br() {
|
|
401
|
+
return this.o.reflowText ? HARD_RETURN : '\n';
|
|
402
|
+
}
|
|
403
|
+
del(token) {
|
|
404
|
+
let text;
|
|
405
|
+
if (typeof token === 'object' && token.tokens) {
|
|
406
|
+
text = this.parser.parseInline(token.tokens);
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
text = typeof token === 'string' ? token : '';
|
|
410
|
+
}
|
|
411
|
+
return this.o.del(text);
|
|
412
|
+
}
|
|
413
|
+
link(token, _title, _text) {
|
|
414
|
+
let href;
|
|
415
|
+
let text;
|
|
416
|
+
if (typeof token === 'object' && token.tokens) {
|
|
417
|
+
href = token.href ?? '';
|
|
418
|
+
text = this.parser.parseInline(token.tokens);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
href = typeof token === 'string' ? token : '';
|
|
422
|
+
text = typeof _text === 'string' ? _text : '';
|
|
423
|
+
}
|
|
424
|
+
const hasText = text && text !== href;
|
|
425
|
+
let out = '';
|
|
426
|
+
if (supportsHyperlinks.stdout) {
|
|
427
|
+
const linkText = this.o.href(text || href);
|
|
428
|
+
out = ansiEscapes.link(linkText, href);
|
|
429
|
+
}
|
|
430
|
+
else {
|
|
431
|
+
if (hasText)
|
|
432
|
+
out += text + ' (';
|
|
433
|
+
out += this.o.href(href);
|
|
434
|
+
if (hasText)
|
|
435
|
+
out += ')';
|
|
436
|
+
}
|
|
437
|
+
return this.o.link(out);
|
|
438
|
+
}
|
|
439
|
+
image(token, title, text) {
|
|
440
|
+
if (typeof token === 'object') {
|
|
441
|
+
title = token.title;
|
|
442
|
+
text = token.text;
|
|
443
|
+
token = token.href ?? '';
|
|
444
|
+
}
|
|
445
|
+
let out = '\n';
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
// ---------------------------------------------------------------------------
|
|
452
|
+
// Public API — same interface as `marked-terminal`
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
/**
|
|
455
|
+
* Returns a marked extension that renders markdown to ANSI terminal output.
|
|
456
|
+
* Drop-in replacement for `markedTerminal` from the `marked-terminal` package.
|
|
457
|
+
*/
|
|
458
|
+
export function markedTerminal(options) {
|
|
459
|
+
const r = new TerminalMarkdownRenderer(options ?? {});
|
|
460
|
+
const methods = [
|
|
461
|
+
'space', 'text', 'code', 'blockquote', 'html', 'heading', 'hr',
|
|
462
|
+
'list', 'listitem', 'checkbox', 'paragraph', 'table', 'tablerow',
|
|
463
|
+
'tablecell', 'strong', 'em', 'codespan', 'br', 'del', 'link', 'image',
|
|
464
|
+
];
|
|
465
|
+
const renderer = {};
|
|
466
|
+
for (const method of methods) {
|
|
467
|
+
renderer[method] = function (...args) {
|
|
468
|
+
r.options = this.options;
|
|
469
|
+
r.parser = this.parser;
|
|
470
|
+
return r[method](...args);
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
return { renderer, useNewRenderer: true };
|
|
474
|
+
}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import chalk from 'chalk';
|
|
12
12
|
import { marked } from 'marked';
|
|
13
|
-
import { markedTerminal } from '
|
|
13
|
+
import { markedTerminal } from '../../markdown-renderer.js';
|
|
14
14
|
import { BaseOverlayV2 } from '../../base/overlay-base-v2.js';
|
|
15
15
|
import { getCurrentTheme } from '../../../themes/index.js';
|
|
16
16
|
import * as terminal from '../../terminal.js';
|
|
@@ -92,7 +92,6 @@ function getThemedMarkedOptions(termWidth) {
|
|
|
92
92
|
}
|
|
93
93
|
function renderMarkdownSync(content, termWidth) {
|
|
94
94
|
const options = getThemedMarkedOptions(termWidth);
|
|
95
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
|
|
96
95
|
marked.use(markedTerminal(options));
|
|
97
96
|
const rendered = marked.parse(content, { async: false });
|
|
98
97
|
return rendered.split('\n');
|