memd-cli 1.2.0 → 1.4.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/.claude/settings.local.json +10 -0
- package/README.md +10 -1
- package/main.js +160 -5
- package/package.json +6 -2
- package/test/memd.test.js +1 -1
package/README.md
CHANGED
|
@@ -22,10 +22,13 @@ Arguments:
|
|
|
22
22
|
Options:
|
|
23
23
|
-v, --version output the version number
|
|
24
24
|
--no-pager disable pager (less)
|
|
25
|
+
--no-mouse disable mouse scroll in pager
|
|
25
26
|
--no-color disable colored output
|
|
26
27
|
--no-highlight disable syntax highlighting
|
|
27
28
|
--width <number> terminal width override
|
|
28
29
|
--ascii use pure ASCII mode for diagrams (default: unicode)
|
|
30
|
+
--theme <name> syntax highlight theme (default, monokai, dracula,
|
|
31
|
+
github-dark, solarized, nord) (default: "default")
|
|
29
32
|
-h, --help display help for command
|
|
30
33
|
```
|
|
31
34
|
|
|
@@ -306,7 +309,13 @@ $ echo '# Hello\n\n```mermaid\nflowchart LR\n A --> B\n```' | memd
|
|
|
306
309
|
npm remove -g memd-cli
|
|
307
310
|
```
|
|
308
311
|
|
|
309
|
-
##
|
|
312
|
+
## Development
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
node main.js test/test1.md
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## Use specific version
|
|
310
319
|
|
|
311
320
|
```bash
|
|
312
321
|
# tag
|
package/main.js
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import { marked } from 'marked';
|
|
4
4
|
import { markedTerminal } from 'marked-terminal';
|
|
5
5
|
import { renderMermaidAscii } from 'beautiful-mermaid';
|
|
6
|
-
import { highlight, supportsLanguage } from 'cli-highlight';
|
|
6
|
+
import { highlight, supportsLanguage, plain } from 'cli-highlight';
|
|
7
|
+
import chalk from 'chalk';
|
|
7
8
|
import { program } from 'commander';
|
|
8
9
|
import { spawn } from 'child_process';
|
|
9
10
|
import * as fs from 'fs';
|
|
@@ -14,7 +15,147 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
14
15
|
const __dirname = path.dirname(__filename);
|
|
15
16
|
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
|
|
16
17
|
|
|
17
|
-
//
|
|
18
|
+
// HSL color utilities for deriving muted variants from base hex colors
|
|
19
|
+
function hexToHsl(hex) {
|
|
20
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
21
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
22
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
23
|
+
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
24
|
+
let h, s, l = (max + min) / 2;
|
|
25
|
+
if (max === min) {
|
|
26
|
+
h = s = 0;
|
|
27
|
+
} else {
|
|
28
|
+
const d = max - min;
|
|
29
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
30
|
+
switch (max) {
|
|
31
|
+
case r: h = ((g - b) / d + (g < b ? 6 : 0)) / 6; break;
|
|
32
|
+
case g: h = ((b - r) / d + 2) / 6; break;
|
|
33
|
+
case b: h = ((r - g) / d + 4) / 6; break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return [h * 360, s * 100, l * 100];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function hslToHex(h, s, l) {
|
|
40
|
+
h /= 360; s /= 100; l /= 100;
|
|
41
|
+
let r, g, b;
|
|
42
|
+
if (s === 0) {
|
|
43
|
+
r = g = b = l;
|
|
44
|
+
} else {
|
|
45
|
+
const hue2rgb = (p, q, t) => {
|
|
46
|
+
if (t < 0) t += 1;
|
|
47
|
+
if (t > 1) t -= 1;
|
|
48
|
+
if (t < 1/6) return p + (q - p) * 6 * t;
|
|
49
|
+
if (t < 1/2) return q;
|
|
50
|
+
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
|
|
51
|
+
return p;
|
|
52
|
+
};
|
|
53
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
54
|
+
const p = 2 * l - q;
|
|
55
|
+
r = hue2rgb(p, q, h + 1/3);
|
|
56
|
+
g = hue2rgb(p, q, h);
|
|
57
|
+
b = hue2rgb(p, q, h - 1/3);
|
|
58
|
+
}
|
|
59
|
+
const toHex = x => Math.round(x * 255).toString(16).padStart(2, '0');
|
|
60
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Mute a hex color: reduce saturation and shift lightness toward middle
|
|
64
|
+
function mute(hex, satMul = 0.55, lightShift = 0.85) {
|
|
65
|
+
const [h, s, l] = hexToHsl(hex);
|
|
66
|
+
const newS = s * satMul;
|
|
67
|
+
const newL = l * lightShift;
|
|
68
|
+
return hslToHex(h, Math.min(newS, 100), Math.max(Math.min(newL, 100), 0));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Build theme from base palette: markdown gets vivid colors, code blocks get muted variants
|
|
72
|
+
function buildTheme(colors) {
|
|
73
|
+
const m = (hex) => mute(hex); // muted variant for code block elements
|
|
74
|
+
return {
|
|
75
|
+
highlight: {
|
|
76
|
+
keyword: chalk.hex(m(colors.keyword)),
|
|
77
|
+
built_in: chalk.hex(m(colors.type)).italic,
|
|
78
|
+
type: chalk.hex(m(colors.type)),
|
|
79
|
+
literal: chalk.hex(m(colors.literal)),
|
|
80
|
+
number: chalk.hex(m(colors.number)),
|
|
81
|
+
regexp: chalk.hex(m(colors.string)),
|
|
82
|
+
string: chalk.hex(m(colors.string)),
|
|
83
|
+
subst: chalk.hex(m(colors.fg)),
|
|
84
|
+
symbol: chalk.hex(m(colors.literal)),
|
|
85
|
+
class: chalk.hex(m(colors.type)),
|
|
86
|
+
function: chalk.hex(m(colors.func)),
|
|
87
|
+
title: chalk.hex(m(colors.func)),
|
|
88
|
+
params: chalk.hex(m(colors.param)).italic,
|
|
89
|
+
comment: chalk.hex(m(colors.comment)).italic,
|
|
90
|
+
doctag: chalk.hex(m(colors.comment)).bold,
|
|
91
|
+
meta: chalk.hex(m(colors.comment)),
|
|
92
|
+
'meta-keyword': chalk.hex(m(colors.keyword)),
|
|
93
|
+
'meta-string': chalk.hex(m(colors.string)),
|
|
94
|
+
tag: chalk.hex(m(colors.keyword)),
|
|
95
|
+
name: chalk.hex(m(colors.keyword)),
|
|
96
|
+
attr: chalk.hex(m(colors.func)),
|
|
97
|
+
attribute: chalk.hex(m(colors.func)),
|
|
98
|
+
variable: chalk.hex(m(colors.fg)),
|
|
99
|
+
addition: chalk.hex(m(colors.added)),
|
|
100
|
+
deletion: chalk.hex(m(colors.deleted)),
|
|
101
|
+
default: plain,
|
|
102
|
+
},
|
|
103
|
+
markdown: {
|
|
104
|
+
firstHeading: chalk.hex(colors.heading1).underline.bold,
|
|
105
|
+
heading: chalk.hex(colors.heading2).bold,
|
|
106
|
+
code: chalk.hex(colors.string),
|
|
107
|
+
codespan: chalk.hex(colors.string),
|
|
108
|
+
blockquote: chalk.hex(colors.comment).italic,
|
|
109
|
+
strong: chalk.hex(colors.fg).bold,
|
|
110
|
+
em: chalk.hex(colors.param).italic,
|
|
111
|
+
del: chalk.hex(colors.comment).strikethrough,
|
|
112
|
+
link: chalk.hex(colors.type),
|
|
113
|
+
href: chalk.hex(colors.type).underline,
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Theme color palettes
|
|
119
|
+
const THEMES = {
|
|
120
|
+
default: { highlight: null, markdown: {} },
|
|
121
|
+
monokai: buildTheme({
|
|
122
|
+
keyword: '#F92672', func: '#A6E22E', string: '#E6DB74',
|
|
123
|
+
type: '#66D9EF', number: '#AE81FF', literal: '#AE81FF',
|
|
124
|
+
param: '#FD971F', comment: '#75715E', fg: '#F8F8F2',
|
|
125
|
+
heading1: '#F92672', heading2:'#A6E22E',
|
|
126
|
+
added: '#A6E22E', deleted: '#F92672',
|
|
127
|
+
}),
|
|
128
|
+
dracula: buildTheme({
|
|
129
|
+
keyword: '#FF79C6', func: '#50FA7B', string: '#F1FA8C',
|
|
130
|
+
type: '#8BE9FD', number: '#BD93F9', literal: '#BD93F9',
|
|
131
|
+
param: '#FFB86C', comment: '#6272A4', fg: '#F8F8F2',
|
|
132
|
+
heading1: '#BD93F9', heading2:'#FF79C6',
|
|
133
|
+
added: '#50FA7B', deleted: '#FF5555',
|
|
134
|
+
}),
|
|
135
|
+
'github-dark': buildTheme({
|
|
136
|
+
keyword: '#FF7B72', func: '#D2A8FF', string: '#A5D6FF',
|
|
137
|
+
type: '#FFA657', number: '#79C0FF', literal: '#79C0FF',
|
|
138
|
+
param: '#C9D1D9', comment: '#8B949E', fg: '#C9D1D9',
|
|
139
|
+
heading1: '#FFFFFF', heading2:'#79C0FF',
|
|
140
|
+
added: '#7EE787', deleted: '#FF7B72',
|
|
141
|
+
}),
|
|
142
|
+
solarized: buildTheme({
|
|
143
|
+
keyword: '#859900', func: '#268BD2', string: '#2AA198',
|
|
144
|
+
type: '#B58900', number: '#D33682', literal: '#2AA198',
|
|
145
|
+
param: '#93A1A1', comment: '#586E75', fg: '#93A1A1',
|
|
146
|
+
heading1: '#CB4B16', heading2:'#268BD2',
|
|
147
|
+
added: '#859900', deleted: '#DC322F',
|
|
148
|
+
}),
|
|
149
|
+
nord: buildTheme({
|
|
150
|
+
keyword: '#81A1C1', func: '#88C0D0', string: '#A3BE8C',
|
|
151
|
+
type: '#8FBCBB', number: '#B48EAD', literal: '#81A1C1',
|
|
152
|
+
param: '#D8DEE9', comment: '#616E88', fg: '#D8DEE9',
|
|
153
|
+
heading1: '#88C0D0', heading2:'#81A1C1',
|
|
154
|
+
added: '#A3BE8C', deleted: '#BF616A',
|
|
155
|
+
}),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const THEME_NAMES = Object.keys(THEMES);
|
|
18
159
|
|
|
19
160
|
function readMarkdownFile(filePath) {
|
|
20
161
|
const absolutePath = path.resolve(filePath);
|
|
@@ -135,22 +276,36 @@ async function main() {
|
|
|
135
276
|
.option('--no-pager', 'disable pager (less)')
|
|
136
277
|
.option('--no-mouse', 'disable mouse scroll in pager')
|
|
137
278
|
.option('--no-color', 'disable colored output')
|
|
138
|
-
.option('--no-highlight', 'disable syntax highlighting')
|
|
139
279
|
.option('--width <number>', 'terminal width override', Number)
|
|
140
280
|
.option('--ascii', 'use pure ASCII mode for diagrams (default: unicode)')
|
|
281
|
+
.option('--theme <name>', `syntax highlight theme (${THEME_NAMES.join(', ')})`, 'default')
|
|
141
282
|
.action(async (files, options) => {
|
|
283
|
+
// Validate theme option
|
|
284
|
+
const themeName = options.theme || 'default';
|
|
285
|
+
if (!(themeName in THEMES)) {
|
|
286
|
+
console.error(`Unknown theme: ${themeName}\nAvailable themes: ${THEME_NAMES.join(', ')}`);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
|
|
142
290
|
// Check if color should be disabled (--no-color or NO_COLOR env var)
|
|
143
291
|
const useColor = options.color !== false && !process.env.NO_COLOR;
|
|
144
292
|
|
|
145
293
|
// Configure syntax highlighting options (passed to cli-highlight)
|
|
146
|
-
const shouldHighlight =
|
|
294
|
+
const shouldHighlight = useColor;
|
|
147
295
|
|
|
148
296
|
// Configure marked with terminal renderer
|
|
149
|
-
const
|
|
297
|
+
const selectedTheme = THEMES[themeName];
|
|
298
|
+
const highlightOptions = shouldHighlight
|
|
299
|
+
? {
|
|
300
|
+
ignoreIllegals: true,
|
|
301
|
+
...(selectedTheme.highlight ? { theme: selectedTheme.highlight } : {}),
|
|
302
|
+
}
|
|
303
|
+
: undefined;
|
|
150
304
|
|
|
151
305
|
marked.use(markedTerminal({
|
|
152
306
|
reflowText: true,
|
|
153
307
|
width: options.width ?? process.stdout.columns ?? 80,
|
|
308
|
+
...selectedTheme.markdown,
|
|
154
309
|
}, highlightOptions));
|
|
155
310
|
|
|
156
311
|
// Override link renderer to avoid OSC 8 escape sequences (fixes tmux display issues)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memd-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "main.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"beautiful-mermaid": "^0.1.3",
|
|
14
|
+
"chalk": "^5.6.2",
|
|
14
15
|
"cli-highlight": "^2.1.11",
|
|
15
16
|
"commander": "^14.0.3",
|
|
16
17
|
"marked": "^17.0.3",
|
|
@@ -27,6 +28,9 @@
|
|
|
27
28
|
"vitest": "^4.0.18"
|
|
28
29
|
},
|
|
29
30
|
"pnpm": {
|
|
30
|
-
"onlyBuiltDependencies": [
|
|
31
|
+
"onlyBuiltDependencies": [
|
|
32
|
+
"esbuild",
|
|
33
|
+
"node-pty"
|
|
34
|
+
]
|
|
31
35
|
}
|
|
32
36
|
}
|
package/test/memd.test.js
CHANGED
|
@@ -22,7 +22,7 @@ async function run(args, { waitFor = null } = {}) {
|
|
|
22
22
|
describe('memd CLI', () => {
|
|
23
23
|
it('--version', async () => {
|
|
24
24
|
const output = await run(['-v'])
|
|
25
|
-
expect(output).toContain('1.
|
|
25
|
+
expect(output).toContain('1.3.0')
|
|
26
26
|
})
|
|
27
27
|
|
|
28
28
|
it('--help', async () => {
|