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.
@@ -0,0 +1,10 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(echo:*)",
5
+ "Bash(FORCE_COLOR=1 echo:*)",
6
+ "Bash(script:*)",
7
+ "Bash(FORCE_COLOR=1 node:*)"
8
+ ]
9
+ }
10
+ }
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
- ## Debug
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
- // Initialize marked configuration (will be configured in main function with color options)
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 = options.highlight !== false && useColor;
294
+ const shouldHighlight = useColor;
147
295
 
148
296
  // Configure marked with terminal renderer
149
- const highlightOptions = shouldHighlight ? { ignoreIllegals: true } : undefined;
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.2.0",
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": ["esbuild", "node-pty"]
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.1.1')
25
+ expect(output).toContain('1.3.0')
26
26
  })
27
27
 
28
28
  it('--help', async () => {