memd-cli 1.2.0 → 1.3.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 +159 -3
- 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);
|
|
@@ -138,7 +279,15 @@ async function main() {
|
|
|
138
279
|
.option('--no-highlight', 'disable syntax highlighting')
|
|
139
280
|
.option('--width <number>', 'terminal width override', Number)
|
|
140
281
|
.option('--ascii', 'use pure ASCII mode for diagrams (default: unicode)')
|
|
282
|
+
.option('--theme <name>', `syntax highlight theme (${THEME_NAMES.join(', ')})`, 'default')
|
|
141
283
|
.action(async (files, options) => {
|
|
284
|
+
// Validate theme option
|
|
285
|
+
const themeName = options.theme || 'default';
|
|
286
|
+
if (!(themeName in THEMES)) {
|
|
287
|
+
console.error(`Unknown theme: ${themeName}\nAvailable themes: ${THEME_NAMES.join(', ')}`);
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
|
|
142
291
|
// Check if color should be disabled (--no-color or NO_COLOR env var)
|
|
143
292
|
const useColor = options.color !== false && !process.env.NO_COLOR;
|
|
144
293
|
|
|
@@ -146,11 +295,18 @@ async function main() {
|
|
|
146
295
|
const shouldHighlight = options.highlight !== false && useColor;
|
|
147
296
|
|
|
148
297
|
// Configure marked with terminal renderer
|
|
149
|
-
const
|
|
298
|
+
const selectedTheme = THEMES[themeName];
|
|
299
|
+
const highlightOptions = shouldHighlight
|
|
300
|
+
? {
|
|
301
|
+
ignoreIllegals: true,
|
|
302
|
+
...(selectedTheme.highlight ? { theme: selectedTheme.highlight } : {}),
|
|
303
|
+
}
|
|
304
|
+
: undefined;
|
|
150
305
|
|
|
151
306
|
marked.use(markedTerminal({
|
|
152
307
|
reflowText: true,
|
|
153
308
|
width: options.width ?? process.stdout.columns ?? 80,
|
|
309
|
+
...selectedTheme.markdown,
|
|
154
310
|
}, highlightOptions));
|
|
155
311
|
|
|
156
312
|
// 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.3.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 () => {
|