@bytechain.cn/colamd-themes 0.3.1 → 0.5.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.
Files changed (100) hide show
  1. package/.claude/skills/colamd-themes-preview.skill.md +333 -0
  2. package/.claude/skills/colamd-themes.skill.md +10 -0
  3. package/.dsh/skills/colamd-themes/SKILL.md +14 -0
  4. package/.github/supported-topics.yml +17 -0
  5. package/.hermes/skills/tools/colamd-themes/SKILL.md +14 -0
  6. package/.openclaw/skills/colamd-themes/SKILL.md +1 -146
  7. package/.opencode/skills/colamd-themes/SKILL.md +17 -0
  8. package/.trae/rules/colamd-themes.md +0 -145
  9. package/CHANGELOG.md +318 -240
  10. package/DSH_SUPPORT_SUMMARY.md +87 -0
  11. package/README.md +414 -384
  12. package/README_CN.md +412 -384
  13. package/RELEASENOTE.md +541 -365
  14. package/bytechain.cn-colamd-themes-0.5.0.tgz +0 -0
  15. package/dist/cli.js +272 -32
  16. package/dist/cli.js.map +1 -1
  17. package/dist/color-utils.d.ts +1 -0
  18. package/dist/color-utils.js +25 -6
  19. package/dist/color-utils.js.map +1 -1
  20. package/dist/extractors/pdf-extractor.js +6 -1
  21. package/dist/extractors/pdf-extractor.js.map +1 -1
  22. package/dist/extractors/url-extractor.d.ts +0 -4
  23. package/dist/extractors/url-extractor.js +13 -126
  24. package/dist/extractors/url-extractor.js.map +1 -1
  25. package/dist/generator.d.ts +1 -1
  26. package/dist/generator.js +32 -11
  27. package/dist/generator.js.map +1 -1
  28. package/dist/models.d.ts +5 -4
  29. package/dist/models.js +2 -3
  30. package/dist/models.js.map +1 -1
  31. package/dist/renderer.d.ts +4 -2
  32. package/dist/renderer.js +68 -25
  33. package/dist/renderer.js.map +1 -1
  34. package/dist/templates/template.css +220 -498
  35. package/dist/templates/theme-paradigm.md +530 -658
  36. package/dist/templates/theme-template.hbs +256 -370
  37. package/dist/theme-store.js +19 -3
  38. package/dist/theme-store.js.map +1 -1
  39. package/dist/utils/document-paradigm.d.ts +12 -0
  40. package/dist/utils/document-paradigm.js +109 -0
  41. package/dist/utils/document-paradigm.js.map +1 -0
  42. package/dist/validators.d.ts +4 -1
  43. package/dist/validators.js +115 -1
  44. package/dist/validators.js.map +1 -1
  45. package/mcp-server/dist/index.d.ts +13 -0
  46. package/mcp-server/dist/index.d.ts.map +1 -0
  47. package/mcp-server/dist/index.js +0 -0
  48. package/mcp-server/dist/index.js.map +1 -0
  49. package/mcp-server/package-lock.json +1759 -0
  50. package/mcp-server/package.json +24 -0
  51. package/mcp-server/src/index.ts +0 -0
  52. package/mcp-server/tsconfig.json +19 -0
  53. package/package.json +54 -36
  54. package/skills/build-skills.sh +30 -5
  55. package/skills/colamd-themes/SKILL.md +180 -148
  56. package/src/cli.ts +474 -217
  57. package/src/color-systems.ts +289 -289
  58. package/src/color-utils.ts +137 -120
  59. package/src/contrast.ts +66 -66
  60. package/src/docx-style-paradigm.ts +811 -811
  61. package/src/export-cli.ts +356 -356
  62. package/src/extractors/base.ts +8 -8
  63. package/src/extractors/docx-extractor.ts +417 -417
  64. package/src/extractors/pdf-extractor.ts +533 -527
  65. package/src/extractors/url-extractor.ts +846 -981
  66. package/src/generator.ts +197 -169
  67. package/src/index.ts +29 -29
  68. package/src/models.ts +212 -212
  69. package/src/renderer.ts +332 -288
  70. package/src/templates/template.css +220 -498
  71. package/src/templates/theme-paradigm.md +530 -658
  72. package/src/templates/theme-template.hbs +256 -370
  73. package/src/theme-store.ts +185 -170
  74. package/src/types/pdf-parse.d.ts +12 -12
  75. package/src/utils/cli-utils.ts +84 -84
  76. package/src/utils/document-paradigm.ts +118 -0
  77. package/src/validators.ts +298 -176
  78. package/themes/academic-paper-pt.css +353 -1805
  79. package/themes/academic-paper.css +350 -1099
  80. package/themes/academic-style.css +342 -2430
  81. package/themes/colly.css +256 -370
  82. package/themes/elegant.css +288 -671
  83. package/themes/forest-ink.css +240 -664
  84. package/themes/guizang.css +250 -732
  85. package/themes/hermes-security.css +279 -0
  86. package/themes/meeting-minutes.css +256 -370
  87. package/themes/pixso-design.css +278 -1261
  88. package/themes/salvator-blog.css +256 -370
  89. package/themes/shimo-theme.css +256 -0
  90. package/themes/swiss-design.css +285 -746
  91. package/themes/template.css +220 -498
  92. package/themes/trae-blue.css +280 -737
  93. package/themes/word-contract.css +249 -0
  94. package/themes-preview/demo-content.md +187 -187
  95. package/tsconfig.json +18 -18
  96. package/src/mermaid-presets.ts +0 -142
  97. package/themes/swiss-design copy.css +0 -746
  98. package/themes-preview/colly-preview.html +0 -623
  99. package/themes-preview/meeting-minutes-preview.html +0 -625
  100. package/themes-preview/salvator-blog-preview.html +0 -623
@@ -1,170 +1,185 @@
1
- /**
2
- * Theme configuration persistence.
3
- *
4
- * Stores default theme and registered custom themes in ~/.colamd-themes/config.json
5
- * Uses async file I/O to avoid blocking the event loop.
6
- */
7
-
8
- import { mkdir, readFile, writeFile, access } from "node:fs/promises";
9
- import { statSync } from "node:fs";
10
- import { join, basename } from "node:path";
11
- import { homedir } from "node:os";
12
-
13
- const CONFIG_DIR = join(homedir(), ".colamd-themes");
14
- const CONFIG_FILE = join(CONFIG_DIR, "config.json");
15
-
16
- /** Maximum allowed CSS file size (1MB) to prevent OOM attacks */
17
- const MAX_CSS_SIZE = 1024 * 1024;
18
-
19
- /** Built-in themes provided by @bytechain.cn/colamd */
20
- export const BUILTIN_THEMES = ["light", "dark", "elegant", "newsprint"] as const;
21
- export type BuiltinTheme = (typeof BUILTIN_THEMES)[number];
22
-
23
- export interface ThemeConfig {
24
- /** Default theme name (built-in or custom) */
25
- defaultTheme: string;
26
- /** Registered custom themes: name → CSS file path */
27
- themes: Record<string, string>;
28
- }
29
-
30
- async function readConfig(): Promise<ThemeConfig> {
31
- try {
32
- await access(CONFIG_FILE);
33
- const data = await readFile(CONFIG_FILE, "utf-8");
34
- return JSON.parse(data) as ThemeConfig;
35
- } catch {
36
- return { defaultTheme: "elegant", themes: {} };
37
- }
38
- }
39
-
40
- async function writeConfig(config: ThemeConfig): Promise<void> {
41
- try {
42
- await access(CONFIG_DIR);
43
- } catch {
44
- await mkdir(CONFIG_DIR, { recursive: true });
45
- }
46
- await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), "utf-8");
47
- }
48
-
49
- /** Get the full config. */
50
- export async function getConfig(): Promise<ThemeConfig> {
51
- return readConfig();
52
- }
53
-
54
- /** Set the default theme (built-in name or registered custom name). */
55
- export async function setDefaultTheme(name: string): Promise<void> {
56
- const config = await readConfig();
57
- config.defaultTheme = name;
58
- await writeConfig(config);
59
- }
60
-
61
- /**
62
- * Register a custom theme.
63
- * @param name Unique name (will be prefixed with "custom:" when applied)
64
- * @param cssPath Absolute path to the CSS file
65
- */
66
- export async function registerTheme(name: string, cssPath: string): Promise<void> {
67
- const config = await readConfig();
68
- config.themes[name] = cssPath;
69
- await writeConfig(config);
70
- }
71
-
72
- /** Remove a registered custom theme. */
73
- export async function unregisterTheme(name: string): Promise<boolean> {
74
- const config = await readConfig();
75
- if (!(name in config.themes)) return false;
76
- delete config.themes[name];
77
- await writeConfig(config);
78
- return true;
79
- }
80
-
81
- /**
82
- * Sanitize theme name from file path.
83
- * Removes special characters and limits length to prevent injection.
84
- */
85
- function sanitizeName(path: string): string {
86
- return basename(path, ".css")
87
- .replace(/[^a-zA-Z0-9_-]/g, "-")
88
- .replace(/-+/g, "-")
89
- .slice(0, 50) || "custom";
90
- }
91
-
92
- /**
93
- * Validate CSS file before reading.
94
- * Checks extension, size, and basic content validity.
95
- */
96
- async function validateCssFile(filePath: string): Promise<void> {
97
- // Check file extension
98
- if (!filePath.toLowerCase().endsWith(".css")) {
99
- throw new Error(`Theme file must have .css extension: ${filePath}`);
100
- }
101
-
102
- // Check file size (synchronous stat is acceptable for metadata)
103
- let stats;
104
- try {
105
- stats = statSync(filePath);
106
- } catch {
107
- throw new Error(`Cannot access theme file: ${filePath}`);
108
- }
109
-
110
- if (stats.size > MAX_CSS_SIZE) {
111
- throw new Error(
112
- `Theme file too large (${(stats.size / 1024).toFixed(1)}KB). ` +
113
- `Maximum size: ${(MAX_CSS_SIZE / 1024).toFixed(0)}MB`
114
- );
115
- }
116
-
117
- // Read and validate content
118
- const content = await readFile(filePath, "utf-8");
119
- if (content.length < 10) {
120
- throw new Error(`Theme file appears to be empty or invalid: ${filePath}`);
121
- }
122
- }
123
-
124
- /**
125
- * Resolve a theme name to the arguments needed by the ColaMD renderer.
126
- *
127
- * @param name Theme name. If omitted, uses the configured default.
128
- * @returns { themeName, customCSS? } — themeName is either a built-in name
129
- * or "custom:<name>"; customCSS is the CSS content for custom themes.
130
- */
131
- export async function resolveTheme(name?: string): Promise<{
132
- themeName: string;
133
- customCSS?: string;
134
- }> {
135
- const config = await readConfig();
136
- const resolved = name ?? config.defaultTheme;
137
-
138
- // Check if it's a registered custom theme
139
- if (config.themes[resolved]) {
140
- const cssPath = config.themes[resolved];
141
- await validateCssFile(cssPath);
142
- const customCSS = await readFile(cssPath, "utf-8");
143
- return { themeName: `custom:${resolved}`, customCSS };
144
- }
145
-
146
- // Check if it's a built-in theme
147
- if (BUILTIN_THEMES.includes(resolved as BuiltinTheme)) {
148
- return { themeName: resolved };
149
- }
150
-
151
- // Treat as a direct CSS file path
152
- try {
153
- await access(resolved);
154
- } catch {
155
- throw new Error(
156
- `Unknown theme: "${resolved}". Use a built-in theme (${BUILTIN_THEMES.join(", ")}), ` +
157
- `a registered custom theme, or a path to a .css file.`
158
- );
159
- }
160
-
161
- // Validate and read direct CSS file
162
- await validateCssFile(resolved);
163
- const nameFromPath = sanitizeName(resolved);
164
- const customCSS = await readFile(resolved, "utf-8");
165
-
166
- return {
167
- themeName: `custom:${nameFromPath}`,
168
- customCSS,
169
- };
170
- }
1
+ /**
2
+ * Theme configuration persistence.
3
+ *
4
+ * Stores default theme and registered custom themes in ~/.colamd-themes/config.json
5
+ * Uses async file I/O to avoid blocking the event loop.
6
+ */
7
+
8
+ import { mkdir, readFile, writeFile, access, rename } from "node:fs/promises";
9
+ import { statSync, unlink } from "node:fs";
10
+ import { join, basename, dirname } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import { homedir } from "node:os";
13
+
14
+ const CONFIG_DIR = join(homedir(), ".colamd-themes");
15
+ const CONFIG_FILE = join(CONFIG_DIR, "config.json");
16
+
17
+ /** Maximum allowed CSS file size (1MB) to prevent OOM attacks */
18
+ const MAX_CSS_SIZE = 1024 * 1024;
19
+
20
+ /** Built-in themes provided by @bytechain.cn/colamd */
21
+ export const BUILTIN_THEMES = ["light", "dark", "elegant", "newsprint"] as const;
22
+ export type BuiltinTheme = (typeof BUILTIN_THEMES)[number];
23
+
24
+ export interface ThemeConfig {
25
+ /** Default theme name (built-in or custom) */
26
+ defaultTheme: string;
27
+ /** Registered custom themes: name → CSS file path */
28
+ themes: Record<string, string>;
29
+ }
30
+
31
+ async function readConfig(): Promise<ThemeConfig> {
32
+ try {
33
+ await access(CONFIG_FILE);
34
+ const data = await readFile(CONFIG_FILE, "utf-8");
35
+ return JSON.parse(data) as ThemeConfig;
36
+ } catch {
37
+ return { defaultTheme: "elegant", themes: {} };
38
+ }
39
+ }
40
+
41
+ async function writeConfig(config: ThemeConfig): Promise<void> {
42
+ try {
43
+ await access(CONFIG_DIR);
44
+ } catch {
45
+ await mkdir(CONFIG_DIR, { recursive: true });
46
+ }
47
+ const tmpPath = `${CONFIG_FILE}.${process.pid}.${Date.now()}.tmp`;
48
+ await writeFile(tmpPath, JSON.stringify(config, null, 2), "utf-8");
49
+ await rename(tmpPath, CONFIG_FILE);
50
+ }
51
+
52
+ /** Get the full config. */
53
+ export async function getConfig(): Promise<ThemeConfig> {
54
+ return readConfig();
55
+ }
56
+
57
+ /** Set the default theme (built-in name or registered custom name). */
58
+ export async function setDefaultTheme(name: string): Promise<void> {
59
+ const config = await readConfig();
60
+ config.defaultTheme = name;
61
+ await writeConfig(config);
62
+ }
63
+
64
+ /**
65
+ * Register a custom theme.
66
+ * @param name Unique name (will be prefixed with "custom:" when applied)
67
+ * @param cssPath Absolute path to the CSS file
68
+ */
69
+ export async function registerTheme(name: string, cssPath: string): Promise<void> {
70
+ const config = await readConfig();
71
+ config.themes[name] = cssPath;
72
+ await writeConfig(config);
73
+ }
74
+
75
+ /** Remove a registered custom theme. */
76
+ export async function unregisterTheme(name: string): Promise<boolean> {
77
+ const config = await readConfig();
78
+ if (!(name in config.themes)) return false;
79
+ delete config.themes[name];
80
+ await writeConfig(config);
81
+ return true;
82
+ }
83
+
84
+ /**
85
+ * Sanitize theme name from file path.
86
+ * Removes special characters and limits length to prevent injection.
87
+ */
88
+ function sanitizeName(path: string): string {
89
+ return basename(path, ".css")
90
+ .replace(/[^a-zA-Z0-9_-]/g, "-")
91
+ .replace(/-+/g, "-")
92
+ .slice(0, 50) || "custom";
93
+ }
94
+
95
+ /**
96
+ * Validate CSS file before reading.
97
+ * Checks extension, size, and basic content validity.
98
+ */
99
+ async function validateCssFile(filePath: string): Promise<void> {
100
+ // Check file extension
101
+ if (!filePath.toLowerCase().endsWith(".css")) {
102
+ throw new Error(`Theme file must have .css extension: ${filePath}`);
103
+ }
104
+
105
+ // Check file size (synchronous stat is acceptable for metadata)
106
+ let stats;
107
+ try {
108
+ stats = statSync(filePath);
109
+ } catch {
110
+ throw new Error(`Cannot access theme file: ${filePath}`);
111
+ }
112
+
113
+ if (stats.size > MAX_CSS_SIZE) {
114
+ throw new Error(
115
+ `Theme file too large (${(stats.size / 1024).toFixed(1)}KB). ` +
116
+ `Maximum size: ${(MAX_CSS_SIZE / 1024).toFixed(0)}MB`
117
+ );
118
+ }
119
+
120
+ // Read and validate content
121
+ const content = await readFile(filePath, "utf-8");
122
+ if (content.length < 10) {
123
+ throw new Error(`Theme file appears to be empty or invalid: ${filePath}`);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Resolve a theme name to the arguments needed by the ColaMD renderer.
129
+ *
130
+ * @param name Theme name. If omitted, uses the configured default.
131
+ * @returns { themeName, customCSS? } — themeName is either a built-in name
132
+ * or "custom:<name>"; customCSS is the CSS content for custom themes.
133
+ */
134
+ export async function resolveTheme(name?: string): Promise<{
135
+ themeName: string;
136
+ customCSS?: string;
137
+ }> {
138
+ const config = await readConfig();
139
+ const resolved = name ?? config.defaultTheme;
140
+
141
+ // Check if it's a registered custom theme
142
+ if (config.themes[resolved]) {
143
+ const cssPath = config.themes[resolved];
144
+ await validateCssFile(cssPath);
145
+ const customCSS = await readFile(cssPath, "utf-8");
146
+ return { themeName: `custom:${resolved}`, customCSS };
147
+ }
148
+
149
+ // Check if it's a built-in theme
150
+ if (BUILTIN_THEMES.includes(resolved as BuiltinTheme)) {
151
+ // Prefer our 2.7.7 contract CSS when a local theme file exists: passing
152
+ // only the name would resolve the 1.5.x renderer's own (pre-2.7) built-in
153
+ // palette in exports. applyTheme(name, css) takes over completely.
154
+ for (const base of [process.cwd(), join(dirname(fileURLToPath(import.meta.url)), "..")]) {
155
+ const cssPath = join(base, "themes", `${resolved}.css`);
156
+ try {
157
+ await validateCssFile(cssPath);
158
+ return { themeName: resolved, customCSS: await readFile(cssPath, "utf-8") };
159
+ } catch {
160
+ // No local 2.7.7 file for this built-in — fall back to the renderer's own.
161
+ }
162
+ }
163
+ return { themeName: resolved };
164
+ }
165
+
166
+ // Treat as a direct CSS file path
167
+ try {
168
+ await access(resolved);
169
+ } catch {
170
+ throw new Error(
171
+ `Unknown theme: "${resolved}". Use a built-in theme (${BUILTIN_THEMES.join(", ")}), ` +
172
+ `a registered custom theme, or a path to a .css file.`
173
+ );
174
+ }
175
+
176
+ // Validate and read direct CSS file
177
+ await validateCssFile(resolved);
178
+ const nameFromPath = sanitizeName(resolved);
179
+ const customCSS = await readFile(resolved, "utf-8");
180
+
181
+ return {
182
+ themeName: `custom:${nameFromPath}`,
183
+ customCSS,
184
+ };
185
+ }
@@ -1,12 +1,12 @@
1
- declare module "pdf-parse" {
2
- interface PDFData {
3
- text: string;
4
- numpages: number;
5
- info: Record<string, unknown>;
6
- metadata: Record<string, unknown>;
7
- version: string;
8
- }
9
-
10
- function pdfParse(buffer: Buffer): Promise<PDFData>;
11
- export default pdfParse;
12
- }
1
+ declare module "pdf-parse" {
2
+ interface PDFData {
3
+ text: string;
4
+ numpages: number;
5
+ info: Record<string, unknown>;
6
+ metadata: Record<string, unknown>;
7
+ version: string;
8
+ }
9
+
10
+ function pdfParse(buffer: Buffer): Promise<PDFData>;
11
+ export default pdfParse;
12
+ }
@@ -1,84 +1,84 @@
1
- /**
2
- * Shared utility functions for CLI commands.
3
- * Provides consistent file path resolution and error handling.
4
- */
5
-
6
- import { existsSync } from "node:fs";
7
- import { resolve, isAbsolute, basename } from "node:path";
8
-
9
- /**
10
- * Resolve input file path with validation.
11
- * Converts relative paths to absolute and checks existence.
12
- *
13
- * @param input File path provided by user
14
- * @returns Resolved absolute path
15
- * @throws Error if file does not exist
16
- */
17
- export function resolveInputPath(input: string): string {
18
- const path = isAbsolute(input) ? input : resolve(process.cwd(), input);
19
- if (!existsSync(path)) {
20
- throw new Error(`File not found: ${path}`);
21
- }
22
- return path;
23
- }
24
-
25
- /**
26
- * Resolve output file path with sensible defaults.
27
- * If output path is not specified, derives it from input path by replacing extension.
28
- *
29
- * @param inputPath Resolved absolute input path
30
- * @param output Optional explicit output path
31
- * @param ext Target extension (default: ".html")
32
- * @returns Resolved absolute output path
33
- */
34
- export function resolveOutputPath(inputPath: string, output?: string, ext = ".html"): string {
35
- if (output) {
36
- return isAbsolute(output) ? output : resolve(process.cwd(), output);
37
- }
38
- return inputPath.replace(/\.md$/i, ext);
39
- }
40
-
41
- /**
42
- * Get base filename without extension for use as document title.
43
- *
44
- * @param filePath Absolute or relative file path
45
- * @returns Filename without extension
46
- */
47
- export function getDocumentTitle(filePath: string): string {
48
- return basename(filePath, ".md");
49
- }
50
-
51
- /**
52
- * Standard error codes for CLI operations.
53
- * Enables consistent error handling across all commands.
54
- */
55
- export enum ErrorCode {
56
- /** Input file not found */
57
- FILE_NOT_FOUND = 1,
58
- /** Theme resolution failed */
59
- THEME_ERROR = 2,
60
- /** Rendering/export failed */
61
- RENDER_ERROR = 3,
62
- /** Invalid arguments or configuration */
63
- INVALID_INPUT = 4,
64
- /** Unknown/unexpected error */
65
- UNKNOWN = 99,
66
- }
67
-
68
- /**
69
- * Custom error class for CLI operations with structured error codes.
70
- * Replaces direct process.exit() calls for better error propagation.
71
- */
72
- export class CLIError extends Error {
73
- constructor(
74
- message: string,
75
- public code: ErrorCode = ErrorCode.UNKNOWN,
76
- public cause?: Error
77
- ) {
78
- super(message);
79
- this.name = "CLIError";
80
- if (cause) {
81
- this.stack = `${this.message}\nCaused by: ${cause.stack}`;
82
- }
83
- }
84
- }
1
+ /**
2
+ * Shared utility functions for CLI commands.
3
+ * Provides consistent file path resolution and error handling.
4
+ */
5
+
6
+ import { existsSync } from "node:fs";
7
+ import { resolve, isAbsolute, basename } from "node:path";
8
+
9
+ /**
10
+ * Resolve input file path with validation.
11
+ * Converts relative paths to absolute and checks existence.
12
+ *
13
+ * @param input File path provided by user
14
+ * @returns Resolved absolute path
15
+ * @throws Error if file does not exist
16
+ */
17
+ export function resolveInputPath(input: string): string {
18
+ const path = isAbsolute(input) ? input : resolve(process.cwd(), input);
19
+ if (!existsSync(path)) {
20
+ throw new Error(`File not found: ${path}`);
21
+ }
22
+ return path;
23
+ }
24
+
25
+ /**
26
+ * Resolve output file path with sensible defaults.
27
+ * If output path is not specified, derives it from input path by replacing extension.
28
+ *
29
+ * @param inputPath Resolved absolute input path
30
+ * @param output Optional explicit output path
31
+ * @param ext Target extension (default: ".html")
32
+ * @returns Resolved absolute output path
33
+ */
34
+ export function resolveOutputPath(inputPath: string, output?: string, ext = ".html"): string {
35
+ if (output) {
36
+ return isAbsolute(output) ? output : resolve(process.cwd(), output);
37
+ }
38
+ return inputPath.replace(/\.md$/i, ext);
39
+ }
40
+
41
+ /**
42
+ * Get base filename without extension for use as document title.
43
+ *
44
+ * @param filePath Absolute or relative file path
45
+ * @returns Filename without extension
46
+ */
47
+ export function getDocumentTitle(filePath: string): string {
48
+ return basename(filePath, ".md");
49
+ }
50
+
51
+ /**
52
+ * Standard error codes for CLI operations.
53
+ * Enables consistent error handling across all commands.
54
+ */
55
+ export enum ErrorCode {
56
+ /** Input file not found */
57
+ FILE_NOT_FOUND = 1,
58
+ /** Theme resolution failed */
59
+ THEME_ERROR = 2,
60
+ /** Rendering/export failed */
61
+ RENDER_ERROR = 3,
62
+ /** Invalid arguments or configuration */
63
+ INVALID_INPUT = 4,
64
+ /** Unknown/unexpected error */
65
+ UNKNOWN = 99,
66
+ }
67
+
68
+ /**
69
+ * Custom error class for CLI operations with structured error codes.
70
+ * Replaces direct process.exit() calls for better error propagation.
71
+ */
72
+ export class CLIError extends Error {
73
+ constructor(
74
+ message: string,
75
+ public code: ErrorCode = ErrorCode.UNKNOWN,
76
+ public cause?: Error
77
+ ) {
78
+ super(message);
79
+ this.name = "CLIError";
80
+ if (cause) {
81
+ this.stack = `${this.message}\nCaused by: ${cause.stack}`;
82
+ }
83
+ }
84
+ }