resuml 2.0.0 → 3.1.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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/validate.ts","../src/utils/loadResume.ts","../src/utils/fileUtils.ts","../src/utils/errorHandler.ts","../src/commands/tojson.ts","../src/commands/render.ts","../src/commands/dev.ts","../src/commands/init.ts","../src/commands/pdf.ts","../src/commands/themes.ts","../src/commands/mcp.ts","../src/utils/themeRender.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { Command } from 'commander';\nimport path from 'path';\nimport fs from 'fs';\nimport { fileURLToPath } from 'url';\nimport { validateAction } from './commands/validate';\nimport { toJsonAction } from './commands/tojson';\nimport { renderAction } from './commands/render';\nimport { devAction } from './commands/dev';\nimport { initAction } from './commands/init';\nimport { pdfAction } from './commands/pdf';\nimport { themesAction } from './commands/themes';\nimport { mcpAction } from './commands/mcp';\n\n// Get the directory name equivalent to __dirname in CommonJS\nconst currentDir = path.dirname(fileURLToPath(import.meta.url));\n\nfunction getCliVersion(): string {\n const packageJsonPath = path.resolve(currentDir, '../package.json');\n if (fs.existsSync(packageJsonPath)) {\n try {\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')) as { version?: string };\n return packageJson.version ?? '0.0.0';\n } catch {\n return '0.0.0';\n }\n }\n return '0.0.0';\n}\n\nexport const program = new Command();\n\nprogram\n .name('resuml')\n .description('CLI tool for managing resuml resume files.')\n .version(getCliVersion());\n\n// Validate Command\nprogram\n .command('validate')\n .description('Validates resume data against the schema.')\n .option('-r, --resume <path>', 'Input YAML file, directory, or glob pattern.')\n .option('--debug', 'Show detailed validation errors.')\n .option('--ats', 'Run ATS (Applicant Tracking System) compatibility analysis.')\n .option('--jd <path>', 'Path to a job description file for keyword matching (requires --ats).')\n .option('--ats-threshold <score>', 'Minimum ATS score (0-100). Exit with code 1 if below threshold.')\n .option('--format <type>', 'Output format for ATS results (text or json).', 'text')\n .action(validateAction);\n\n// ToJSON Command\nprogram\n .command('tojson')\n .description('Converts YAML resume data to JSON format.')\n .option('-r, --resume <path>', 'Input YAML file, directory, or glob pattern.')\n .option('-o, --output <file>', 'Output JSON file path.', 'resume.json')\n .option('--debug', 'Show detailed validation and processing information.')\n .action(toJsonAction);\n\n// Render Command\nprogram\n .command('render')\n .description('Renders the resume data using a specified theme.')\n .option('-r, --resume <path>', 'Input YAML file, directory, or glob pattern.')\n .option('-t, --theme <name>', 'Theme name (e.g., stackoverflow, react).')\n .option('-o, --output <file>', 'Output file path.')\n .option('--format <type>', 'Output format (html or pdf).', 'html')\n .option('--language <code>', 'Language code for localization.', 'en')\n .option('--debug', 'Show detailed validation and processing information.')\n .action(renderAction);\n\n// Dev Command\nprogram\n .command('dev')\n .description('Start development server with hot-reload.')\n .option('-r, --resume <path>', 'Input YAML file, directory, or glob pattern.')\n .option('-t, --theme <name>', 'Theme name (e.g., stackoverflow, react).')\n .option('--port <number>', 'Port for development server.', '3000')\n .option('--language <code>', 'Language code for localization.', 'en')\n .option('--debug', 'Show detailed validation and processing information.')\n .action(devAction);\n\n// Init Command\nprogram\n .command('init')\n .description('Scaffold a starter resume.yaml file with all sections.')\n .option('-o, --output <file>', 'Output YAML file path.', 'resume.yaml')\n .action(initAction);\n\n// PDF Command\nprogram\n .command('pdf')\n .description('Export resume as PDF using Playwright.')\n .option('-r, --resume <path>', 'Input YAML file, directory, or glob pattern.')\n .option('-t, --theme <name>', 'Theme name (e.g., stackoverflow, react).')\n .option('-o, --output <file>', 'Output PDF file path.', 'resume.pdf')\n .option('--language <code>', 'Language code for localization.', 'en')\n .option('--format <size>', 'Page format: A4 or Letter.', 'A4')\n .option('--margin <values>', 'Page margins (e.g., \"10mm\" or \"10mm,15mm,10mm,15mm\").')\n .option('--debug', 'Show detailed validation and processing information.')\n .action(pdfAction);\n\n// Themes Command\nprogram\n .command('themes')\n .description('List available JSON Resume themes and install them.')\n .option('--install <name>', 'Install a theme by name (e.g., stackoverflow, elegant).')\n .action(themesAction);\n\n// MCP Server Command\nprogram\n .command('mcp')\n .description('Start MCP server for AI agent integration (stdio transport).')\n .action(mcpAction);\n\n// Parse Arguments - only execute when not in test environment\nif (process.env['NODE_ENV'] !== 'test') {\n void (async () => {\n try {\n await program.parseAsync(process.argv);\n } catch (e: unknown) {\n console.error('Command line error:', (e as Error).message);\n process.exit(1);\n }\n })();\n}\n\nexport { processResumeData } from './core';\nexport { loadResumeFiles } from './utils/loadResume';\nexport { loadTheme } from './utils/themeLoader';\nexport * as themeRender from './utils/themeRender';\nexport { analyzeAts } from './ats/index';\nexport type { AtsResult, AtsOptions } from './ats/index';\n","import fs from 'fs';\nimport { processResumeData } from '../core';\nimport { loadResumeFiles } from '../utils/loadResume';\nimport { handleCommandError } from '../utils/errorHandler';\nimport { analyzeAts } from '../ats/index';\nimport type { AtsResult, AtsCheck } from '../ats/index';\n\ninterface ValidateCommandOptions {\n resume?: string;\n debug?: boolean;\n ats?: boolean;\n jd?: string;\n atsThreshold?: string;\n format?: string;\n}\n\nfunction formatAtsReport(result: AtsResult, debug: boolean, chalk: typeof import('chalk').default): void {\n const scoreColor = result.score >= 75 ? chalk.green : result.score >= 60 ? chalk.yellow : chalk.red;\n console.log('');\n console.log(chalk.bold('═══ ATS Analysis Report ═══'));\n console.log('');\n console.log(` Score: ${scoreColor(chalk.bold(`${result.score}/100`))} (${result.rating.replace('-', ' ')})`);\n console.log(` ${result.summary}`);\n console.log('');\n\n // Group checks by category\n const categories: Record<string, AtsCheck[]> = {};\n for (const check of result.checks) {\n const list = categories[check.category];\n if (!list) {\n categories[check.category] = [check];\n } else {\n list.push(check);\n }\n }\n\n const categoryLabels: Record<string, string> = {\n contact: 'Contact Information',\n content: 'Content Quality',\n structure: 'Resume Structure',\n keywords: 'Keywords',\n };\n\n for (const [cat, checks] of Object.entries(categories)) {\n const label = categoryLabels[cat] || cat;\n console.log(chalk.bold(` ${label}`));\n\n for (const check of checks) {\n if (!debug && check.passed) continue; // In normal mode, only show failures\n const icon = check.passed ? chalk.green('✓') : chalk.red('✗');\n const scoreText = chalk.dim(`[${check.score}]`);\n console.log(` ${icon} ${check.message} ${scoreText}`);\n if (!check.passed && check.suggestion) {\n console.log(chalk.dim(` → ${check.suggestion}`));\n }\n }\n console.log('');\n }\n\n // JD keyword section\n if (result.keywords) {\n console.log(chalk.bold(' Job Description Match'));\n const kw = result.keywords;\n const matchColor = kw.matchPercentage >= 70 ? chalk.green : kw.matchPercentage >= 50 ? chalk.yellow : chalk.red;\n console.log(` Match: ${matchColor(`${kw.matchPercentage}%`)} (${kw.matched.length}/${kw.matched.length + kw.missing.length} keywords)`);\n if (kw.matched.length > 0) {\n console.log(chalk.green(` ✓ Matched: ${kw.matched.join(', ')}`));\n }\n if (kw.missing.length > 0) {\n console.log(chalk.red(` ✗ Missing: ${kw.missing.join(', ')}`));\n console.log(chalk.dim(' → Consider incorporating these keywords into your resume where relevant.'));\n }\n console.log('');\n }\n\n console.log(chalk.dim('═══════════════════════════'));\n}\n\nexport async function validateAction(options: ValidateCommandOptions): Promise<void> {\n const chalk = (await import('chalk')).default;\n console.log(chalk.blue('Starting resuml validate...'));\n\n try {\n const inputPath = options.resume;\n const { yamlContents } = await loadResumeFiles(inputPath);\n\n console.log(chalk.blue('Validating resume data...'));\n\n let resumeData;\n try {\n resumeData = await processResumeData(yamlContents);\n console.log(chalk.green('✓ Resume data is valid against the schema!'));\n } catch (error: unknown) {\n handleCommandError(error, 'validate', options.debug);\n return;\n }\n\n // Run ATS analysis if requested\n if (options.ats) {\n console.log(chalk.blue('Running ATS analysis...'));\n\n let jobDescription: string | undefined;\n if (options.jd) {\n try {\n jobDescription = fs.readFileSync(options.jd, 'utf8');\n } catch {\n console.error(chalk.red(`Failed to read job description file: ${options.jd}`));\n return;\n }\n }\n\n const result = analyzeAts(resumeData, {\n language: 'en',\n jobDescription,\n });\n\n if (options.format === 'json') {\n console.log(JSON.stringify(result, null, 2));\n } else {\n formatAtsReport(result, !!options.debug, chalk);\n }\n\n // Check threshold\n const threshold = options.atsThreshold ? parseInt(options.atsThreshold, 10) : undefined;\n if (threshold !== undefined && result.score < threshold) {\n console.error(chalk.red(`\\nATS score ${result.score} is below threshold ${threshold}.`));\n process.exit(1);\n }\n }\n } catch (error: unknown) {\n handleCommandError(error, 'validate', options.debug);\n }\n}\n","import fs from 'fs/promises';\nimport YAML from 'yaml';\nimport { findInputFiles } from './fileUtils';\n\n/**\n * Load and parse resume files\n */\nexport async function loadResumeFiles(\n inputPath?: string\n): Promise<{ files: string[]; yamlContents: string[] }> {\n const files = await findInputFiles(inputPath);\n if (files.length === 0) {\n throw new Error('No resume files found');\n }\n\n const yamlContents: string[] = [];\n\n for (const file of files) {\n try {\n const content = await fs.readFile(file, 'utf-8');\n const parsed: unknown = YAML.parse(content);\n if (parsed && typeof parsed === 'object') {\n yamlContents.push(content);\n }\n } catch (error) {\n throw new Error(`Failed to parse ${file}: ${(error as Error).message}`);\n }\n }\n\n if (yamlContents.length === 0) {\n throw new Error('No valid data found in any of the input files');\n }\n\n return { files, yamlContents };\n}\n","import fs from 'fs/promises';\nimport path from 'path';\nimport { glob } from 'glob';\n\n/**\n * Find input files based on provided path\n * @param inputPath File, directory, or glob pattern\n * @returns Array of matched file paths\n */\nexport async function findInputFiles(inputPath?: string): Promise<string[]> {\n if (!inputPath) {\n return [];\n }\n\n if (inputPath.includes('*')) {\n try {\n const matchedFiles = await glob(inputPath);\n if (matchedFiles.length === 0) {\n throw new Error(`No files found matching pattern: ${inputPath}`);\n }\n return matchedFiles;\n } catch (err) {\n throw new Error(`Error matching files: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n try {\n const stat = await fs.stat(inputPath);\n if (stat.isFile()) {\n return [inputPath];\n } else if (stat.isDirectory()) {\n const pattern = path.join(inputPath, '*.{yaml,yml}');\n try {\n const yamlFiles = await glob(pattern);\n if (yamlFiles.length === 0) {\n throw new Error(`No YAML files found in directory: ${inputPath}`);\n }\n return yamlFiles;\n } catch (_) {\n throw new Error(`No YAML files found in directory: ${inputPath}`);\n }\n }\n } catch (e) {\n if (e instanceof Error && e.message.includes('ENOENT')) {\n throw new Error('Input path not found');\n }\n throw e;\n }\n\n return [];\n}\n","interface ValidationErrorDetail {\n instancePath?: string;\n message?: string;\n params?: Record<string, unknown>;\n // Add other properties from Ajv error objects if needed\n}\n\ninterface SchemaValidationError extends Error {\n errors?: ValidationErrorDetail[];\n}\n\n/**\n * Handle command errors, displaying useful information to the user\n */\nexport function handleCommandError(error: unknown, command: string, debug: boolean = false): void {\n const errorMessage = error instanceof Error ? error.message : String(error);\n console.error(`❌ Error during ${command} command: ${errorMessage}`);\n\n // Handle validation errors from core\n const potentialValidationError = error as SchemaValidationError;\n if (\n potentialValidationError instanceof Error &&\n potentialValidationError.name === 'SchemaValidationError' &&\n Array.isArray(potentialValidationError.errors)\n ) {\n const errors: ValidationErrorDetail[] = potentialValidationError.errors;\n\n if (debug) {\n // Show detailed errors with all information in debug mode\n console.error('\\nValidation failed with the following errors:');\n errors.forEach((err: ValidationErrorDetail, index: number) => {\n const path = err.instancePath || 'root';\n console.error(`${index + 1}. Path: ${path}`);\n console.error(` Error: ${err.message || 'Unknown validation error'}`);\n if (err.params) {\n console.error(` Params: ${JSON.stringify(err.params)}`);\n }\n });\n } else {\n // Show a more concise error message in normal mode\n console.error('\\nSome validation errors were found:');\n // Show a limited number of errors to avoid overwhelming output\n const maxErrors = 5;\n errors.slice(0, maxErrors).forEach((err: ValidationErrorDetail, index: number) => {\n const path = err.instancePath || 'root';\n console.error(`${index + 1}. Field: ${path}`);\n console.error(` Error: ${err.message || 'Unknown validation error'}`);\n });\n\n if (errors.length > maxErrors) {\n console.error(`\\n...and ${errors.length - maxErrors} more errors.`);\n console.error('Use the --debug flag for complete error details.');\n }\n }\n }\n\n // Only exit if not in a test environment where exit might disrupt the test runner\n if (process.env['NODE_ENV'] !== 'test') {\n process.exit(1);\n }\n}\n","import fs from 'fs';\nimport { processResumeData } from '../core';\nimport { loadResumeFiles } from '../utils/loadResume';\nimport { handleCommandError } from '../utils/errorHandler';\n\ninterface ToJsonOptions {\n resume?: string;\n output: string;\n debug?: boolean;\n}\n\nexport async function toJsonAction(options: ToJsonOptions): Promise<void> {\n const chalk = (await import('chalk')).default;\n console.log(chalk.blue('Starting resuml tojson...'));\n\n try {\n const inputPath = options.resume;\n const { yamlContents } = await loadResumeFiles(inputPath);\n\n console.log(chalk.blue('Processing and validating data...'));\n const resumeData = await processResumeData(yamlContents);\n console.log(chalk.green('Processing and validation successful!'));\n\n // Generate JSON output\n const jsonOutput = JSON.stringify(resumeData, null, 2);\n fs.writeFileSync(options.output, jsonOutput, 'utf8');\n console.log(chalk.green(`Successfully wrote output to ${options.output}`));\n } catch (error: unknown) {\n handleCommandError(error, 'tojson', options.debug);\n }\n}\n","import fs from 'fs';\nimport path from 'node:path';\nimport { processResumeData } from '../core';\nimport { loadResumeFiles } from '../utils/loadResume';\nimport { loadTheme } from '../utils/themeLoader';\nimport { handleCommandError } from '../utils/errorHandler';\nimport chalk from 'chalk';\n\ninterface RenderCommandOptions {\n resume?: string;\n theme?: string;\n output?: string;\n format: 'html' | 'pdf';\n language: string;\n debug?: boolean;\n}\n\nexport async function renderAction(options: RenderCommandOptions): Promise<void> {\n if (!options.theme) {\n throw new Error(\n '--theme option is required. Please specify a theme name (e.g., stackoverflow, react).'\n );\n }\n\n console.log(chalk.blue('Starting resuml render...'));\n\n try {\n const inputPath = options.resume;\n const { yamlContents } = await loadResumeFiles(inputPath);\n\n console.log(chalk.blue('Processing and validating resume data...'));\n const resumeData = await processResumeData(yamlContents);\n console.log(chalk.green('Resume data processing and validation successful!'));\n\n const theme = loadTheme(options.theme);\n\n const htmlOutput = await theme.render(resumeData, {\n locale: options.language,\n });\n\n const defaultExtension = options.format;\n const defaultFilename = `resume.${defaultExtension}`;\n const outputPath = options.output || defaultFilename;\n\n if (options.format === 'pdf') { // eslint-disable-line @typescript-eslint/no-unnecessary-condition\n console.log(chalk.blue(`Generating PDF output at ${outputPath}...`));\n const { chromium } = await import('playwright');\n const browser = await chromium.launch();\n const page = await browser.newPage();\n await page.setContent(htmlOutput, { waitUntil: 'networkidle' });\n const pdfBuffer = await page.pdf({\n path: outputPath,\n format: 'A4',\n printBackground: true,\n margin: {\n top: '1cm',\n right: '1cm',\n bottom: '1cm',\n left: '1cm',\n },\n });\n await browser.close();\n fs.writeFileSync(outputPath, pdfBuffer);\n console.log(chalk.green(`Successfully wrote PDF output to ${outputPath}`));\n } else {\n console.log(chalk.blue(`Writing HTML output to ${outputPath}...`));\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n fs.writeFileSync(outputPath, htmlOutput, 'utf8');\n console.log(chalk.green(`Successfully wrote HTML output to ${outputPath}`));\n }\n } catch (error: unknown) {\n handleCommandError(error, 'render', options.debug);\n }\n}\n","import fs from 'fs';\nimport path from 'node:path';\nimport { processResumeData } from '../core';\nimport { loadResumeFiles } from '../utils/loadResume';\nimport { loadTheme } from '../utils/themeLoader';\nimport { handleCommandError } from '../utils/errorHandler';\nimport chalk from 'chalk';\n\ninterface DevCommandOptions {\n resume?: string;\n theme?: string;\n port?: number;\n language: string;\n debug?: boolean;\n}\n\nexport async function devAction(options: DevCommandOptions): Promise<void> {\n if (!options.theme) {\n throw new Error(\n '--theme option is required. Please specify a theme name (e.g., stackoverflow, react).'\n );\n }\n\n console.log(chalk.blue('Starting resuml development server...'));\n\n const port = options.port || 3000;\n const inputPath = options.resume;\n\n if (!inputPath) {\n throw new Error('Resume path is required. Use -r or --resume option.');\n }\n\n try {\n // Initial render\n await renderResume(options);\n\n console.log(chalk.green(`🚀 Development server running at http://localhost:${port}`));\n console.log(chalk.blue('Watching for file changes...'));\n\n // Watch for file changes if inputPath is a directory\n if (fs.existsSync(inputPath) && fs.statSync(inputPath).isDirectory()) {\n watchDirectory(inputPath, () => { void renderResume(options); });\n } else if (fs.existsSync(inputPath)) {\n watchFile(inputPath, () => { void renderResume(options); });\n }\n\n // Simple HTTP server\n await startDevServer(port);\n } catch (error: unknown) {\n handleCommandError(error, 'dev', options.debug);\n }\n}\n\nasync function renderResume(options: DevCommandOptions): Promise<void> {\n try {\n const inputPath = options.resume;\n if (!inputPath) {\n throw new Error('Resume path is required');\n }\n\n const { yamlContents } = await loadResumeFiles(inputPath);\n\n console.log(chalk.blue('🔄 Processing resume data...'));\n const resumeData = await processResumeData(yamlContents);\n\n const theme = loadTheme(options.theme ?? 'stackoverflow');\n\n const htmlOutput = await theme.render(resumeData, {\n locale: options.language,\n });\n\n // Write to a temp directory for the dev server\n const outputPath = path.join(process.cwd(), '.resuml-dev', 'index.html');\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n fs.writeFileSync(outputPath, htmlOutput, 'utf8');\n\n console.log(chalk.green('✅ Resume updated!'));\n } catch (error: unknown) {\n console.error(chalk.red('❌ Error rendering resume:'), (error as Error).message);\n }\n}\n\nfunction watchDirectory(dirPath: string, callback: () => void): void {\n fs.watch(dirPath, { recursive: true }, (_eventType, filename) => {\n if (filename && (filename.endsWith('.yaml') || filename.endsWith('.yml'))) {\n console.log(chalk.blue(`📁 File changed: ${filename}`));\n callback();\n }\n });\n}\n\nfunction watchFile(filePath: string, callback: () => void): void {\n fs.watch(filePath, (eventType) => {\n if (eventType === 'change') {\n console.log(chalk.blue(`📄 File changed: ${path.basename(filePath)}`));\n callback();\n }\n });\n}\n\nasync function startDevServer(port: number): Promise<void> {\n // Simple HTTP server implementation\n const http = await import('http');\n const url = await import('url');\n\n const server = http.createServer((req, res) => {\n const parsedUrl = url.parse(req.url || '', true);\n const pathname = parsedUrl.pathname || '/';\n\n if (pathname === '/' || pathname === '/index.html') {\n const htmlPath = path.join(process.cwd(), '.resuml-dev', 'index.html');\n\n if (fs.existsSync(htmlPath)) {\n const html = fs.readFileSync(htmlPath, 'utf8');\n\n // Inject live reload script\n const liveReloadScript = `\n <script>\n setInterval(() => {\n fetch('/health').then(() => {\n location.reload();\n }).catch(() => {\n // Server might be restarting\n });\n }, 1000);\n </script>\n `;\n\n const modifiedHtml = html.replace('</body>', `${liveReloadScript}</body>`);\n\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(modifiedHtml);\n } else {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Resume not found. Make sure to provide a valid resume path.');\n }\n } else if (pathname === '/health') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end('{\"status\":\"ok\"}');\n } else {\n res.writeHead(404, { 'Content-Type': 'text/plain' });\n res.end('Not found');\n }\n });\n\n server.listen(port, () => {\n console.log(chalk.green(`🌐 Server listening on port ${port}`));\n });\n}\n","import fs from 'fs';\nimport path from 'path';\nimport readline from 'readline';\nimport chalk from 'chalk';\nimport { generateResumeYaml } from '../utils/resumeTemplate';\n\ninterface InitCommandOptions {\n output?: string;\n}\n\nfunction createReadlineInterface(): readline.Interface {\n return readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n}\n\nfunction ask(rl: readline.Interface, question: string, defaultValue?: string): Promise<string> {\n const prompt = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;\n return new Promise((resolve) => {\n rl.question(prompt, (answer) => {\n resolve(answer.trim() || defaultValue || '');\n });\n });\n}\n\nexport async function initAction(options: InitCommandOptions): Promise<void> {\n const outputPath = options.output || 'resume.yaml';\n const fullPath = path.resolve(outputPath);\n\n const rl = createReadlineInterface();\n\n try {\n // Check if file already exists\n if (fs.existsSync(fullPath)) {\n const overwrite = await ask(\n rl,\n `${chalk.yellow('⚠')} ${outputPath} already exists. Overwrite? (y/N)`,\n 'N'\n );\n if (overwrite.toLowerCase() !== 'y') {\n console.log(chalk.blue('Aborted. No files were changed.'));\n return;\n }\n }\n\n console.log(chalk.blue('\\n📝 Let\\'s set up your resume!\\n'));\n\n const name = await ask(rl, 'Your full name', 'John Doe');\n const email = await ask(rl, 'Email address', 'john@example.com');\n const label = await ask(rl, 'Professional title/label', 'Software Engineer');\n\n const yaml = generateResumeYaml(name, email, label);\n\n fs.mkdirSync(path.dirname(fullPath), { recursive: true });\n fs.writeFileSync(fullPath, yaml, 'utf8');\n\n console.log(chalk.green(`\\n✅ Created ${outputPath}`));\n console.log(chalk.blue('\\nNext steps:'));\n console.log(` 1. Edit ${outputPath} to fill in your details`);\n console.log(' 2. Run ' + chalk.cyan('resuml validate --resume ' + outputPath));\n console.log(' 3. Run ' + chalk.cyan('resuml render --resume ' + outputPath + ' --theme stackoverflow'));\n } finally {\n rl.close();\n }\n}\n","import fs from 'fs';\nimport path from 'node:path';\nimport { processResumeData } from '../core';\nimport { loadResumeFiles } from '../utils/loadResume';\nimport { loadTheme } from '../utils/themeLoader';\nimport { handleCommandError } from '../utils/errorHandler';\nimport chalk from 'chalk';\n\ninterface PdfCommandOptions {\n resume?: string;\n theme?: string;\n output?: string;\n language: string;\n format: string;\n margin?: string;\n debug?: boolean;\n}\n\ninterface PlaywrightBrowser {\n newPage(): Promise<PlaywrightPage>;\n close(): Promise<void>;\n}\n\ninterface PlaywrightPage {\n setContent(html: string, options?: { waitUntil?: string }): Promise<void>;\n pdf(options?: Record<string, unknown>): Promise<Buffer>;\n}\n\ninterface PlaywrightBrowserType {\n launch(options?: Record<string, unknown>): Promise<PlaywrightBrowser>;\n}\n\nasync function loadPlaywright(): Promise<PlaywrightBrowserType> {\n try {\n const { chromium } = await import('playwright');\n return chromium;\n } catch {\n throw new Error(\n `Playwright is required for PDF export but is not installed.\\n` +\n `Install it with: ${chalk.cyan('npm install playwright')}`\n );\n }\n}\n\nfunction parseMargin(margin?: string): Record<string, string> {\n const defaultMargin = { top: '10mm', right: '10mm', bottom: '10mm', left: '10mm' };\n if (!margin) return defaultMargin;\n\n const parts = margin.split(',').map((s) => s.trim());\n if (parts.length === 1 && parts[0]) {\n return { top: parts[0], right: parts[0], bottom: parts[0], left: parts[0] };\n }\n if (parts.length === 2 && parts[0] && parts[1]) {\n return { top: parts[0], right: parts[1], bottom: parts[0], left: parts[1] };\n }\n if (parts.length === 4 && parts[0] && parts[1] && parts[2] && parts[3]) {\n return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[3] };\n }\n return defaultMargin;\n}\n\nexport async function pdfAction(options: PdfCommandOptions): Promise<void> {\n if (!options.theme) {\n throw new Error(\n '--theme option is required. Please specify a theme name (e.g., stackoverflow, react).'\n );\n }\n\n console.log(chalk.blue('Starting resuml PDF export...'));\n\n try {\n // Load and process resume data\n const inputPath = options.resume;\n const { yamlContents } = await loadResumeFiles(inputPath);\n\n console.log(chalk.blue('Processing and validating resume data...'));\n const resumeData = await processResumeData(yamlContents);\n console.log(chalk.green('Resume data processing and validation successful!'));\n\n // Render HTML using theme\n const theme = loadTheme(options.theme);\n const htmlOutput = await theme.render(resumeData, {\n locale: options.language,\n });\n\n // Load playwright\n console.log(chalk.blue('Loading Playwright...'));\n const chromium = await loadPlaywright();\n\n // Convert HTML to PDF\n const outputPath = options.output || 'resume.pdf';\n const format = options.format === 'Letter' ? 'Letter' : 'A4';\n const margin = parseMargin(options.margin);\n\n console.log(chalk.blue(`Generating PDF (${format} format)...`));\n\n const browser = await chromium.launch({ headless: true });\n try {\n const page = await browser.newPage();\n await page.setContent(htmlOutput, { waitUntil: 'networkidle' });\n\n const pdfBuffer = await page.pdf({\n format,\n margin,\n printBackground: true,\n preferCSSPageSize: true,\n });\n\n fs.mkdirSync(path.dirname(path.resolve(outputPath)), { recursive: true });\n fs.writeFileSync(outputPath, pdfBuffer);\n\n console.log(chalk.green(`✅ Successfully generated ${outputPath}`));\n } finally {\n await browser.close();\n }\n } catch (error: unknown) {\n handleCommandError(error, 'pdf', options.debug);\n }\n}\n","import chalk from 'chalk';\nimport { execSync } from 'child_process';\nimport { KNOWN_THEMES, isThemeInstalled, getInstalledVersion } from '../utils/themeInfo';\n\ninterface ThemesCommandOptions {\n install?: string;\n}\n\nfunction listThemes(): void {\n console.log(chalk.blue('\\n📦 Compatible JSON Resume Themes\\n'));\n\n const nameWidth = 16;\n const pkgWidth = 38;\n\n // Header\n console.log(\n ` ${'Status'.padEnd(10)}${'Name'.padEnd(nameWidth)}${'Package'.padEnd(pkgWidth)}Description`\n );\n console.log(` ${'─'.repeat(10)}${'─'.repeat(nameWidth)}${'─'.repeat(pkgWidth)}${'─'.repeat(30)}`);\n\n for (const theme of KNOWN_THEMES) {\n const installed = isThemeInstalled(theme.pkg);\n const version = installed ? getInstalledVersion(theme.pkg) : null;\n const status = installed\n ? chalk.green(`✓ ${version || 'yes'}`.padEnd(10))\n : chalk.yellow('not installed'.substring(0, 10).padEnd(10));\n\n console.log(\n ` ${status}${theme.name.padEnd(nameWidth)}${chalk.blue(theme.pkg.padEnd(pkgWidth))}${theme.description}`\n );\n }\n\n console.log(chalk.blue('\\nInstall a theme:'));\n console.log(` ${chalk.cyan('resuml themes --install <name>')}`);\n console.log(` ${chalk.cyan('resuml themes --install stackoverflow')}\\n`);\n console.log(\n chalk.blue('Browse all themes: ') +\n 'https://www.npmjs.com/search?q=jsonresume-theme\\n'\n );\n}\n\nfunction installTheme(name: string): void {\n // Check if it's a known short name\n const known = KNOWN_THEMES.find((t) => t.name === name);\n const pkg = known ? known.pkg : name.startsWith('jsonresume-theme-') ? name : `jsonresume-theme-${name}`;\n\n console.log(chalk.blue(`\\n📦 Installing ${pkg}...\\n`));\n\n try {\n execSync(`npm install ${pkg}`, { stdio: 'inherit' });\n console.log(chalk.green(`\\n✅ Successfully installed ${pkg}`));\n console.log(chalk.blue(`\\nUse it with: ${chalk.cyan(`resuml render --theme ${known?.name || name}`)}\\n`));\n } catch {\n console.error(chalk.red(`\\n❌ Failed to install ${pkg}`));\n console.error(chalk.yellow(`Make sure the package exists: https://www.npmjs.com/package/${pkg}\\n`));\n }\n}\n\nexport function themesAction(options: ThemesCommandOptions): void {\n if (options.install) {\n installTheme(options.install);\n } else {\n listThemes();\n }\n}\n","export async function mcpAction(): Promise<void> {\n const { startMcpServer } = await import('../mcp/server');\n await startMcpServer();\n}\n","import type { Resume } from '../core';\n\ninterface ThemeConfig {\n sections?: {\n order?: string[];\n exclude?: string[];\n };\n layout?: {\n style?: string;\n };\n styling?: Record<string, string | number>;\n labels?: Record<string, string>;\n}\n\n/**\n * Render a resume using the specified theme\n * @param themeName Name of the theme to use\n * @param resumeData Resume data to render\n * @param themeConfig Theme configuration\n * @param inlineCss Optional CSS to include in the HTML\n * @param language Language code for localization\n * @returns Object containing the rendered HTML\n */\nexport async function renderTheme(\n themeName: string,\n resumeData: Resume,\n themeConfig: ThemeConfig = {},\n inlineCss?: string,\n language: string = 'en'\n): Promise<{ htmlOutput: string }> {\n try {\n if (themeName.startsWith('jsonresume-')) {\n return await renderJsonResumeTheme(themeName, resumeData, inlineCss);\n } else {\n return await renderRyamlTheme(themeName, resumeData, themeConfig, inlineCss, language);\n }\n } catch (error) {\n if (error instanceof Error) {\n throw new Error(`Error rendering theme: ${error.message}`);\n }\n throw new Error('Unknown error rendering theme');\n }\n}\n\n/**\n * Renders a JSON Resume theme\n */\nasync function renderJsonResumeTheme(\n themeName: string,\n resumeData: Resume,\n inlineCss?: string\n): Promise<{ htmlOutput: string }> {\n // Handle both formats: with and without 'jsonresume-' prefix\n let themePackageName;\n if (themeName.startsWith('jsonresume-theme-')) {\n // User provided full name (jsonresume-theme-stackoverflow)\n themePackageName = themeName;\n } else if (themeName.startsWith('jsonresume-')) {\n // User provided short name (jsonresume-stackoverflow)\n themePackageName = `jsonresume-theme-${themeName.replace('jsonresume-', '')}`;\n } else {\n // User provided just the name (stackoverflow)\n themePackageName = `jsonresume-theme-${themeName}`;\n }\n\n // Convert resuml data to jsonresume format if needed\n const jsonResumeData = resumeData; // In real implementation, convert if necessary\n\n // Dynamically import the theme\n const themePackage = await import(themePackageName) as { default: { render: (d: unknown) => string } };\n const renderedHTML = themePackage.default.render(jsonResumeData);\n\n return {\n htmlOutput: injectCss(renderedHTML, inlineCss),\n };\n}\n\n/**\n * Renders a resuml theme\n */\nasync function renderRyamlTheme(\n themeName: string,\n resumeData: Resume,\n themeConfig: ThemeConfig,\n inlineCss?: string,\n language: string = 'en'\n): Promise<{ htmlOutput: string }> {\n if (themeName === 'default') {\n throw new Error('No default theme available. Please specify a specific theme name.');\n }\n\n const themePackageName = `@resuml/theme-${themeName}`;\n\n // Dynamically import the theme (better than require)\n const themePackage = await import(themePackageName) as { default: { render: (d: unknown, c: unknown, l: string) => string } };\n const renderedHTML = themePackage.default.render(resumeData, themeConfig, language);\n\n return {\n htmlOutput: injectCss(renderedHTML, inlineCss),\n };\n}\n\n/**\n * Injects CSS into HTML content\n * @param html HTML content\n * @param css CSS to inject\n * @returns HTML with injected CSS\n */\nexport function injectCss(html: string, css?: string): string {\n if (!css) return html;\n return html.replace('</head>', `<style>${css}</style></head>`);\n}\n"],"mappings":";;;;;;;;;;;;;AAEA,SAAS,eAAe;AACxB,OAAOA,WAAU;AACjB,OAAOC,SAAQ;AACf,SAAS,qBAAqB;;;ACL9B,OAAOC,SAAQ;;;ACAf,OAAOC,SAAQ;AACf,OAAO,UAAU;;;ACDjB,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,SAAS,YAAY;AAOrB,eAAsB,eAAe,WAAuC;AAC1E,MAAI,CAAC,WAAW;AACd,WAAO,CAAC;AAAA,EACV;AAEA,MAAI,UAAU,SAAS,GAAG,GAAG;AAC3B,QAAI;AACF,YAAM,eAAe,MAAM,KAAK,SAAS;AACzC,UAAI,aAAa,WAAW,GAAG;AAC7B,cAAM,IAAI,MAAM,oCAAoC,SAAS,EAAE;AAAA,MACjE;AACA,aAAO;AAAA,IACT,SAAS,KAAK;AACZ,YAAM,IAAI,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,IAC7F;AAAA,EACF;AAEA,MAAI;AACF,UAAM,OAAO,MAAM,GAAG,KAAK,SAAS;AACpC,QAAI,KAAK,OAAO,GAAG;AACjB,aAAO,CAAC,SAAS;AAAA,IACnB,WAAW,KAAK,YAAY,GAAG;AAC7B,YAAM,UAAU,KAAK,KAAK,WAAW,cAAc;AACnD,UAAI;AACF,cAAM,YAAY,MAAM,KAAK,OAAO;AACpC,YAAI,UAAU,WAAW,GAAG;AAC1B,gBAAM,IAAI,MAAM,qCAAqC,SAAS,EAAE;AAAA,QAClE;AACA,eAAO;AAAA,MACT,SAAS,GAAG;AACV,cAAM,IAAI,MAAM,qCAAqC,SAAS,EAAE;AAAA,MAClE;AAAA,IACF;AAAA,EACF,SAAS,GAAG;AACV,QAAI,aAAa,SAAS,EAAE,QAAQ,SAAS,QAAQ,GAAG;AACtD,YAAM,IAAI,MAAM,sBAAsB;AAAA,IACxC;AACA,UAAM;AAAA,EACR;AAEA,SAAO,CAAC;AACV;;;AD3CA,eAAsB,gBACpB,WACsD;AACtD,QAAM,QAAQ,MAAM,eAAe,SAAS;AAC5C,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,uBAAuB;AAAA,EACzC;AAEA,QAAM,eAAyB,CAAC;AAEhC,aAAW,QAAQ,OAAO;AACxB,QAAI;AACF,YAAM,UAAU,MAAMC,IAAG,SAAS,MAAM,OAAO;AAC/C,YAAM,SAAkB,KAAK,MAAM,OAAO;AAC1C,UAAI,UAAU,OAAO,WAAW,UAAU;AACxC,qBAAa,KAAK,OAAO;AAAA,MAC3B;AAAA,IACF,SAAS,OAAO;AACd,YAAM,IAAI,MAAM,mBAAmB,IAAI,KAAM,MAAgB,OAAO,EAAE;AAAA,IACxE;AAAA,EACF;AAEA,MAAI,aAAa,WAAW,GAAG;AAC7B,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AAEA,SAAO,EAAE,OAAO,aAAa;AAC/B;;;AEpBO,SAAS,mBAAmB,OAAgB,SAAiB,QAAiB,OAAa;AAChG,QAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC1E,UAAQ,MAAM,uBAAkB,OAAO,aAAa,YAAY,EAAE;AAGlE,QAAM,2BAA2B;AACjC,MACE,oCAAoC,SACpC,yBAAyB,SAAS,2BAClC,MAAM,QAAQ,yBAAyB,MAAM,GAC7C;AACA,UAAM,SAAkC,yBAAyB;AAEjE,QAAI,OAAO;AAET,cAAQ,MAAM,gDAAgD;AAC9D,aAAO,QAAQ,CAAC,KAA4B,UAAkB;AAC5D,cAAMC,QAAO,IAAI,gBAAgB;AACjC,gBAAQ,MAAM,GAAG,QAAQ,CAAC,WAAWA,KAAI,EAAE;AAC3C,gBAAQ,MAAM,aAAa,IAAI,WAAW,0BAA0B,EAAE;AACtE,YAAI,IAAI,QAAQ;AACd,kBAAQ,MAAM,cAAc,KAAK,UAAU,IAAI,MAAM,CAAC,EAAE;AAAA,QAC1D;AAAA,MACF,CAAC;AAAA,IACH,OAAO;AAEL,cAAQ,MAAM,sCAAsC;AAEpD,YAAM,YAAY;AAClB,aAAO,MAAM,GAAG,SAAS,EAAE,QAAQ,CAAC,KAA4B,UAAkB;AAChF,cAAMA,QAAO,IAAI,gBAAgB;AACjC,gBAAQ,MAAM,GAAG,QAAQ,CAAC,YAAYA,KAAI,EAAE;AAC5C,gBAAQ,MAAM,aAAa,IAAI,WAAW,0BAA0B,EAAE;AAAA,MACxE,CAAC;AAED,UAAI,OAAO,SAAS,WAAW;AAC7B,gBAAQ,MAAM;AAAA,SAAY,OAAO,SAAS,SAAS,eAAe;AAClE,gBAAQ,MAAM,kDAAkD;AAAA,MAClE;AAAA,IACF;AAAA,EACF;AAGE,MAAI,QAAQ,IAAI,UAAU,MAAM,QAAQ;AACxC,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;;;AH5CA,SAAS,gBAAgB,QAAmB,OAAgBC,QAA6C;AACvG,QAAM,aAAa,OAAO,SAAS,KAAKA,OAAM,QAAQ,OAAO,SAAS,KAAKA,OAAM,SAASA,OAAM;AAChG,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAIA,OAAM,KAAK,2DAA6B,CAAC;AACrD,UAAQ,IAAI,EAAE;AACd,UAAQ,IAAI,YAAY,WAAWA,OAAM,KAAK,GAAG,OAAO,KAAK,MAAM,CAAC,CAAC,KAAK,OAAO,OAAO,QAAQ,KAAK,GAAG,CAAC,GAAG;AAC5G,UAAQ,IAAI,KAAK,OAAO,OAAO,EAAE;AACjC,UAAQ,IAAI,EAAE;AAGd,QAAM,aAAyC,CAAC;AAChD,aAAW,SAAS,OAAO,QAAQ;AACjC,UAAM,OAAO,WAAW,MAAM,QAAQ;AACtC,QAAI,CAAC,MAAM;AACT,iBAAW,MAAM,QAAQ,IAAI,CAAC,KAAK;AAAA,IACrC,OAAO;AACL,WAAK,KAAK,KAAK;AAAA,IACjB;AAAA,EACF;AAEA,QAAM,iBAAyC;AAAA,IAC7C,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAW;AAAA,IACX,UAAU;AAAA,EACZ;AAEA,aAAW,CAAC,KAAK,MAAM,KAAK,OAAO,QAAQ,UAAU,GAAG;AACtD,UAAM,QAAQ,eAAe,GAAG,KAAK;AACrC,YAAQ,IAAIA,OAAM,KAAK,KAAK,KAAK,EAAE,CAAC;AAEpC,eAAW,SAAS,QAAQ;AAC1B,UAAI,CAAC,SAAS,MAAM,OAAQ;AAC5B,YAAM,OAAO,MAAM,SAASA,OAAM,MAAM,QAAG,IAAIA,OAAM,IAAI,QAAG;AAC5D,YAAM,YAAYA,OAAM,IAAI,IAAI,MAAM,KAAK,GAAG;AAC9C,cAAQ,IAAI,OAAO,IAAI,IAAI,MAAM,OAAO,IAAI,SAAS,EAAE;AACvD,UAAI,CAAC,MAAM,UAAU,MAAM,YAAY;AACrC,gBAAQ,IAAIA,OAAM,IAAI,gBAAW,MAAM,UAAU,EAAE,CAAC;AAAA,MACtD;AAAA,IACF;AACA,YAAQ,IAAI,EAAE;AAAA,EAChB;AAGA,MAAI,OAAO,UAAU;AACnB,YAAQ,IAAIA,OAAM,KAAK,yBAAyB,CAAC;AACjD,UAAM,KAAK,OAAO;AAClB,UAAM,aAAa,GAAG,mBAAmB,KAAKA,OAAM,QAAQ,GAAG,mBAAmB,KAAKA,OAAM,SAASA,OAAM;AAC5G,YAAQ,IAAI,cAAc,WAAW,GAAG,GAAG,eAAe,GAAG,CAAC,KAAK,GAAG,QAAQ,MAAM,IAAI,GAAG,QAAQ,SAAS,GAAG,QAAQ,MAAM,YAAY;AACzI,QAAI,GAAG,QAAQ,SAAS,GAAG;AACzB,cAAQ,IAAIA,OAAM,MAAM,uBAAkB,GAAG,QAAQ,KAAK,IAAI,CAAC,EAAE,CAAC;AAAA,IACpE;AACA,QAAI,GAAG,QAAQ,SAAS,GAAG;AACzB,cAAQ,IAAIA,OAAM,IAAI,uBAAkB,GAAG,QAAQ,KAAK,IAAI,CAAC,EAAE,CAAC;AAChE,cAAQ,IAAIA,OAAM,IAAI,qFAAgF,CAAC;AAAA,IACzG;AACA,YAAQ,IAAI,EAAE;AAAA,EAChB;AAEA,UAAQ,IAAIA,OAAM,IAAI,oKAA6B,CAAC;AACtD;AAEA,eAAsB,eAAe,SAAgD;AACnF,QAAMA,UAAS,MAAM,OAAO,OAAO,GAAG;AACtC,UAAQ,IAAIA,OAAM,KAAK,6BAA6B,CAAC;AAErD,MAAI;AACF,UAAM,YAAY,QAAQ;AAC1B,UAAM,EAAE,aAAa,IAAI,MAAM,gBAAgB,SAAS;AAExD,YAAQ,IAAIA,OAAM,KAAK,2BAA2B,CAAC;AAEnD,QAAI;AACJ,QAAI;AACF,mBAAa,MAAM,kBAAkB,YAAY;AACjD,cAAQ,IAAIA,OAAM,MAAM,iDAA4C,CAAC;AAAA,IACvE,SAAS,OAAgB;AACvB,yBAAmB,OAAO,YAAY,QAAQ,KAAK;AACnD;AAAA,IACF;AAGA,QAAI,QAAQ,KAAK;AACf,cAAQ,IAAIA,OAAM,KAAK,yBAAyB,CAAC;AAEjD,UAAI;AACJ,UAAI,QAAQ,IAAI;AACd,YAAI;AACF,2BAAiBC,IAAG,aAAa,QAAQ,IAAI,MAAM;AAAA,QACrD,QAAQ;AACN,kBAAQ,MAAMD,OAAM,IAAI,wCAAwC,QAAQ,EAAE,EAAE,CAAC;AAC7E;AAAA,QACF;AAAA,MACF;AAEA,YAAM,SAAS,WAAW,YAAY;AAAA,QACpC,UAAU;AAAA,QACV;AAAA,MACF,CAAC;AAED,UAAI,QAAQ,WAAW,QAAQ;AAC7B,gBAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,MAC7C,OAAO;AACL,wBAAgB,QAAQ,CAAC,CAAC,QAAQ,OAAOA,MAAK;AAAA,MAChD;AAGA,YAAM,YAAY,QAAQ,eAAe,SAAS,QAAQ,cAAc,EAAE,IAAI;AAC9E,UAAI,cAAc,UAAa,OAAO,QAAQ,WAAW;AACvD,gBAAQ,MAAMA,OAAM,IAAI;AAAA,YAAe,OAAO,KAAK,uBAAuB,SAAS,GAAG,CAAC;AACvF,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AAAA,EACF,SAAS,OAAgB;AACvB,uBAAmB,OAAO,YAAY,QAAQ,KAAK;AAAA,EACrD;AACF;;;AIpIA,OAAOE,SAAQ;AAWf,eAAsB,aAAa,SAAuC;AACxE,QAAMC,UAAS,MAAM,OAAO,OAAO,GAAG;AACtC,UAAQ,IAAIA,OAAM,KAAK,2BAA2B,CAAC;AAEnD,MAAI;AACF,UAAM,YAAY,QAAQ;AAC1B,UAAM,EAAE,aAAa,IAAI,MAAM,gBAAgB,SAAS;AAExD,YAAQ,IAAIA,OAAM,KAAK,mCAAmC,CAAC;AAC3D,UAAM,aAAa,MAAM,kBAAkB,YAAY;AACvD,YAAQ,IAAIA,OAAM,MAAM,uCAAuC,CAAC;AAGhE,UAAM,aAAa,KAAK,UAAU,YAAY,MAAM,CAAC;AACrD,IAAAC,IAAG,cAAc,QAAQ,QAAQ,YAAY,MAAM;AACnD,YAAQ,IAAID,OAAM,MAAM,gCAAgC,QAAQ,MAAM,EAAE,CAAC;AAAA,EAC3E,SAAS,OAAgB;AACvB,uBAAmB,OAAO,UAAU,QAAQ,KAAK;AAAA,EACnD;AACF;;;AC9BA,OAAOE,SAAQ;AACf,OAAOC,WAAU;AAKjB,OAAO,WAAW;AAWlB,eAAsB,aAAa,SAA8C;AAC/E,MAAI,CAAC,QAAQ,OAAO;AAClB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,IAAI,MAAM,KAAK,2BAA2B,CAAC;AAEnD,MAAI;AACF,UAAM,YAAY,QAAQ;AAC1B,UAAM,EAAE,aAAa,IAAI,MAAM,gBAAgB,SAAS;AAExD,YAAQ,IAAI,MAAM,KAAK,0CAA0C,CAAC;AAClE,UAAM,aAAa,MAAM,kBAAkB,YAAY;AACvD,YAAQ,IAAI,MAAM,MAAM,mDAAmD,CAAC;AAE5E,UAAM,QAAQ,UAAU,QAAQ,KAAK;AAErC,UAAM,aAAa,MAAM,MAAM,OAAO,YAAY;AAAA,MAChD,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAED,UAAM,mBAAmB,QAAQ;AACjC,UAAM,kBAAkB,UAAU,gBAAgB;AAClD,UAAM,aAAa,QAAQ,UAAU;AAErC,QAAI,QAAQ,WAAW,OAAO;AAC5B,cAAQ,IAAI,MAAM,KAAK,4BAA4B,UAAU,KAAK,CAAC;AACnE,YAAM,EAAE,SAAS,IAAI,MAAM,OAAO,YAAY;AAC9C,YAAM,UAAU,MAAM,SAAS,OAAO;AACtC,YAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,YAAM,KAAK,WAAW,YAAY,EAAE,WAAW,cAAc,CAAC;AAC9D,YAAM,YAAY,MAAM,KAAK,IAAI;AAAA,QAC/B,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,iBAAiB;AAAA,QACjB,QAAQ;AAAA,UACN,KAAK;AAAA,UACL,OAAO;AAAA,UACP,QAAQ;AAAA,UACR,MAAM;AAAA,QACR;AAAA,MACF,CAAC;AACD,YAAM,QAAQ,MAAM;AACpB,MAAAC,IAAG,cAAc,YAAY,SAAS;AACtC,cAAQ,IAAI,MAAM,MAAM,oCAAoC,UAAU,EAAE,CAAC;AAAA,IAC3E,OAAO;AACL,cAAQ,IAAI,MAAM,KAAK,0BAA0B,UAAU,KAAK,CAAC;AACjE,MAAAA,IAAG,UAAUC,MAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,MAAAD,IAAG,cAAc,YAAY,YAAY,MAAM;AAC/C,cAAQ,IAAI,MAAM,MAAM,qCAAqC,UAAU,EAAE,CAAC;AAAA,IAC5E;AAAA,EACF,SAAS,OAAgB;AACvB,uBAAmB,OAAO,UAAU,QAAQ,KAAK;AAAA,EACnD;AACF;;;ACzEA,OAAOE,SAAQ;AACf,OAAOC,WAAU;AAKjB,OAAOC,YAAW;AAUlB,eAAsB,UAAU,SAA2C;AACzE,MAAI,CAAC,QAAQ,OAAO;AAClB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,IAAIA,OAAM,KAAK,uCAAuC,CAAC;AAE/D,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,YAAY,QAAQ;AAE1B,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AAEA,MAAI;AAEF,UAAM,aAAa,OAAO;AAE1B,YAAQ,IAAIA,OAAM,MAAM,4DAAqD,IAAI,EAAE,CAAC;AACpF,YAAQ,IAAIA,OAAM,KAAK,8BAA8B,CAAC;AAGtD,QAAIC,IAAG,WAAW,SAAS,KAAKA,IAAG,SAAS,SAAS,EAAE,YAAY,GAAG;AACpE,qBAAe,WAAW,MAAM;AAAE,aAAK,aAAa,OAAO;AAAA,MAAG,CAAC;AAAA,IACjE,WAAWA,IAAG,WAAW,SAAS,GAAG;AACnC,gBAAU,WAAW,MAAM;AAAE,aAAK,aAAa,OAAO;AAAA,MAAG,CAAC;AAAA,IAC5D;AAGA,UAAM,eAAe,IAAI;AAAA,EAC3B,SAAS,OAAgB;AACvB,uBAAmB,OAAO,OAAO,QAAQ,KAAK;AAAA,EAChD;AACF;AAEA,eAAe,aAAa,SAA2C;AACrE,MAAI;AACF,UAAM,YAAY,QAAQ;AAC1B,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC3C;AAEA,UAAM,EAAE,aAAa,IAAI,MAAM,gBAAgB,SAAS;AAExD,YAAQ,IAAID,OAAM,KAAK,qCAA8B,CAAC;AACtD,UAAM,aAAa,MAAM,kBAAkB,YAAY;AAEvD,UAAM,QAAQ,UAAU,QAAQ,SAAS,eAAe;AAExD,UAAM,aAAa,MAAM,MAAM,OAAO,YAAY;AAAA,MAChD,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAGD,UAAM,aAAaE,MAAK,KAAK,QAAQ,IAAI,GAAG,eAAe,YAAY;AACvE,IAAAD,IAAG,UAAUC,MAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AAC1D,IAAAD,IAAG,cAAc,YAAY,YAAY,MAAM;AAE/C,YAAQ,IAAID,OAAM,MAAM,wBAAmB,CAAC;AAAA,EAC9C,SAAS,OAAgB;AACvB,YAAQ,MAAMA,OAAM,IAAI,gCAA2B,GAAI,MAAgB,OAAO;AAAA,EAChF;AACF;AAEA,SAAS,eAAe,SAAiB,UAA4B;AACnE,EAAAC,IAAG,MAAM,SAAS,EAAE,WAAW,KAAK,GAAG,CAAC,YAAY,aAAa;AAC/D,QAAI,aAAa,SAAS,SAAS,OAAO,KAAK,SAAS,SAAS,MAAM,IAAI;AACzE,cAAQ,IAAID,OAAM,KAAK,2BAAoB,QAAQ,EAAE,CAAC;AACtD,eAAS;AAAA,IACX;AAAA,EACF,CAAC;AACH;AAEA,SAAS,UAAU,UAAkB,UAA4B;AAC/D,EAAAC,IAAG,MAAM,UAAU,CAAC,cAAc;AAChC,QAAI,cAAc,UAAU;AAC1B,cAAQ,IAAID,OAAM,KAAK,2BAAoBE,MAAK,SAAS,QAAQ,CAAC,EAAE,CAAC;AACrE,eAAS;AAAA,IACX;AAAA,EACF,CAAC;AACH;AAEA,eAAe,eAAe,MAA6B;AAEzD,QAAM,OAAO,MAAM,OAAO,MAAM;AAChC,QAAM,MAAM,MAAM,OAAO,KAAK;AAE9B,QAAM,SAAS,KAAK,aAAa,CAAC,KAAK,QAAQ;AAC7C,UAAM,YAAY,IAAI,MAAM,IAAI,OAAO,IAAI,IAAI;AAC/C,UAAM,WAAW,UAAU,YAAY;AAEvC,QAAI,aAAa,OAAO,aAAa,eAAe;AAClD,YAAM,WAAWA,MAAK,KAAK,QAAQ,IAAI,GAAG,eAAe,YAAY;AAErE,UAAID,IAAG,WAAW,QAAQ,GAAG;AAC3B,cAAM,OAAOA,IAAG,aAAa,UAAU,MAAM;AAG7C,cAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYzB,cAAM,eAAe,KAAK,QAAQ,WAAW,GAAG,gBAAgB,SAAS;AAEzE,YAAI,UAAU,KAAK,EAAE,gBAAgB,YAAY,CAAC;AAClD,YAAI,IAAI,YAAY;AAAA,MACtB,OAAO;AACL,YAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,YAAI,IAAI,6DAA6D;AAAA,MACvE;AAAA,IACF,WAAW,aAAa,WAAW;AACjC,UAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;AACzD,UAAI,IAAI,iBAAiB;AAAA,IAC3B,OAAO;AACL,UAAI,UAAU,KAAK,EAAE,gBAAgB,aAAa,CAAC;AACnD,UAAI,IAAI,WAAW;AAAA,IACrB;AAAA,EACF,CAAC;AAED,SAAO,OAAO,MAAM,MAAM;AACxB,YAAQ,IAAID,OAAM,MAAM,sCAA+B,IAAI,EAAE,CAAC;AAAA,EAChE,CAAC;AACH;;;ACpJA,OAAOG,SAAQ;AACf,OAAOC,WAAU;AACjB,OAAO,cAAc;AACrB,OAAOC,YAAW;AAOlB,SAAS,0BAA8C;AACrD,SAAO,SAAS,gBAAgB;AAAA,IAC9B,OAAO,QAAQ;AAAA,IACf,QAAQ,QAAQ;AAAA,EAClB,CAAC;AACH;AAEA,SAAS,IAAI,IAAwB,UAAkB,cAAwC;AAC7F,QAAM,SAAS,eAAe,GAAG,QAAQ,KAAK,YAAY,QAAQ,GAAG,QAAQ;AAC7E,SAAO,IAAI,QAAQ,CAAC,YAAY;AAC9B,OAAG,SAAS,QAAQ,CAAC,WAAW;AAC9B,cAAQ,OAAO,KAAK,KAAK,gBAAgB,EAAE;AAAA,IAC7C,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAsB,WAAW,SAA4C;AAC3E,QAAM,aAAa,QAAQ,UAAU;AACrC,QAAM,WAAWC,MAAK,QAAQ,UAAU;AAExC,QAAM,KAAK,wBAAwB;AAEnC,MAAI;AAEF,QAAIC,IAAG,WAAW,QAAQ,GAAG;AAC3B,YAAM,YAAY,MAAM;AAAA,QACtB;AAAA,QACA,GAAGC,OAAM,OAAO,QAAG,CAAC,KAAK,UAAU;AAAA,QACnC;AAAA,MACF;AACA,UAAI,UAAU,YAAY,MAAM,KAAK;AACnC,gBAAQ,IAAIA,OAAM,KAAK,iCAAiC,CAAC;AACzD;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,IAAIA,OAAM,KAAK,yCAAmC,CAAC;AAE3D,UAAM,OAAO,MAAM,IAAI,IAAI,kBAAkB,UAAU;AACvD,UAAM,QAAQ,MAAM,IAAI,IAAI,iBAAiB,kBAAkB;AAC/D,UAAM,QAAQ,MAAM,IAAI,IAAI,4BAA4B,mBAAmB;AAE3E,UAAM,OAAO,mBAAmB,MAAM,OAAO,KAAK;AAElD,IAAAD,IAAG,UAAUD,MAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACxD,IAAAC,IAAG,cAAc,UAAU,MAAM,MAAM;AAEvC,YAAQ,IAAIC,OAAM,MAAM;AAAA,iBAAe,UAAU,EAAE,CAAC;AACpD,YAAQ,IAAIA,OAAM,KAAK,eAAe,CAAC;AACvC,YAAQ,IAAI,aAAa,UAAU,0BAA0B;AAC7D,YAAQ,IAAI,cAAcA,OAAM,KAAK,8BAA8B,UAAU,CAAC;AAC9E,YAAQ,IAAI,cAAcA,OAAM,KAAK,4BAA4B,aAAa,wBAAwB,CAAC;AAAA,EACzG,UAAE;AACA,OAAG,MAAM;AAAA,EACX;AACF;;;ACjEA,OAAOC,SAAQ;AACf,OAAOC,WAAU;AAKjB,OAAOC,YAAW;AA0BlB,eAAe,iBAAiD;AAC9D,MAAI;AACF,UAAM,EAAE,SAAS,IAAI,MAAM,OAAO,YAAY;AAC9C,WAAO;AAAA,EACT,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,mBACsBA,OAAM,KAAK,wBAAwB,CAAC;AAAA,IAC5D;AAAA,EACF;AACF;AAEA,SAAS,YAAY,QAAyC;AAC5D,QAAM,gBAAgB,EAAE,KAAK,QAAQ,OAAO,QAAQ,QAAQ,QAAQ,MAAM,OAAO;AACjF,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,QAAQ,OAAO,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AACnD,MAAI,MAAM,WAAW,KAAK,MAAM,CAAC,GAAG;AAClC,WAAO,EAAE,KAAK,MAAM,CAAC,GAAG,OAAO,MAAM,CAAC,GAAG,QAAQ,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,EAAE;AAAA,EAC5E;AACA,MAAI,MAAM,WAAW,KAAK,MAAM,CAAC,KAAK,MAAM,CAAC,GAAG;AAC9C,WAAO,EAAE,KAAK,MAAM,CAAC,GAAG,OAAO,MAAM,CAAC,GAAG,QAAQ,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,EAAE;AAAA,EAC5E;AACA,MAAI,MAAM,WAAW,KAAK,MAAM,CAAC,KAAK,MAAM,CAAC,KAAK,MAAM,CAAC,KAAK,MAAM,CAAC,GAAG;AACtE,WAAO,EAAE,KAAK,MAAM,CAAC,GAAG,OAAO,MAAM,CAAC,GAAG,QAAQ,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,EAAE;AAAA,EAC5E;AACA,SAAO;AACT;AAEA,eAAsB,UAAU,SAA2C;AACzE,MAAI,CAAC,QAAQ,OAAO;AAClB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,IAAIA,OAAM,KAAK,+BAA+B,CAAC;AAEvD,MAAI;AAEF,UAAM,YAAY,QAAQ;AAC1B,UAAM,EAAE,aAAa,IAAI,MAAM,gBAAgB,SAAS;AAExD,YAAQ,IAAIA,OAAM,KAAK,0CAA0C,CAAC;AAClE,UAAM,aAAa,MAAM,kBAAkB,YAAY;AACvD,YAAQ,IAAIA,OAAM,MAAM,mDAAmD,CAAC;AAG5E,UAAM,QAAQ,UAAU,QAAQ,KAAK;AACrC,UAAM,aAAa,MAAM,MAAM,OAAO,YAAY;AAAA,MAChD,QAAQ,QAAQ;AAAA,IAClB,CAAC;AAGD,YAAQ,IAAIA,OAAM,KAAK,uBAAuB,CAAC;AAC/C,UAAM,WAAW,MAAM,eAAe;AAGtC,UAAM,aAAa,QAAQ,UAAU;AACrC,UAAM,SAAS,QAAQ,WAAW,WAAW,WAAW;AACxD,UAAM,SAAS,YAAY,QAAQ,MAAM;AAEzC,YAAQ,IAAIA,OAAM,KAAK,mBAAmB,MAAM,aAAa,CAAC;AAE9D,UAAM,UAAU,MAAM,SAAS,OAAO,EAAE,UAAU,KAAK,CAAC;AACxD,QAAI;AACF,YAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,YAAM,KAAK,WAAW,YAAY,EAAE,WAAW,cAAc,CAAC;AAE9D,YAAM,YAAY,MAAM,KAAK,IAAI;AAAA,QAC/B;AAAA,QACA;AAAA,QACA,iBAAiB;AAAA,QACjB,mBAAmB;AAAA,MACrB,CAAC;AAED,MAAAC,IAAG,UAAUC,MAAK,QAAQA,MAAK,QAAQ,UAAU,CAAC,GAAG,EAAE,WAAW,KAAK,CAAC;AACxE,MAAAD,IAAG,cAAc,YAAY,SAAS;AAEtC,cAAQ,IAAID,OAAM,MAAM,iCAA4B,UAAU,EAAE,CAAC;AAAA,IACnE,UAAE;AACA,YAAM,QAAQ,MAAM;AAAA,IACtB;AAAA,EACF,SAAS,OAAgB;AACvB,uBAAmB,OAAO,OAAO,QAAQ,KAAK;AAAA,EAChD;AACF;;;ACtHA,OAAOG,YAAW;AAClB,SAAS,gBAAgB;AAOzB,SAAS,aAAmB;AAC1B,UAAQ,IAAIC,OAAM,KAAK,6CAAsC,CAAC;AAE9D,QAAM,YAAY;AAClB,QAAM,WAAW;AAGjB,UAAQ;AAAA,IACN,KAAK,SAAS,OAAO,EAAE,CAAC,GAAG,OAAO,OAAO,SAAS,CAAC,GAAG,UAAU,OAAO,QAAQ,CAAC;AAAA,EAClF;AACA,UAAQ,IAAI,KAAK,SAAI,OAAO,EAAE,CAAC,GAAG,SAAI,OAAO,SAAS,CAAC,GAAG,SAAI,OAAO,QAAQ,CAAC,GAAG,SAAI,OAAO,EAAE,CAAC,EAAE;AAEjG,aAAW,SAAS,cAAc;AAChC,UAAM,YAAY,iBAAiB,MAAM,GAAG;AAC5C,UAAM,UAAU,YAAY,oBAAoB,MAAM,GAAG,IAAI;AAC7D,UAAM,SAAS,YACXA,OAAM,MAAM,UAAK,WAAW,KAAK,GAAG,OAAO,EAAE,CAAC,IAC9CA,OAAM,OAAO,gBAAgB,UAAU,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC;AAE5D,YAAQ;AAAA,MACN,KAAK,MAAM,GAAG,MAAM,KAAK,OAAO,SAAS,CAAC,GAAGA,OAAM,KAAK,MAAM,IAAI,OAAO,QAAQ,CAAC,CAAC,GAAG,MAAM,WAAW;AAAA,IACzG;AAAA,EACF;AAEA,UAAQ,IAAIA,OAAM,KAAK,oBAAoB,CAAC;AAC5C,UAAQ,IAAI,KAAKA,OAAM,KAAK,gCAAgC,CAAC,EAAE;AAC/D,UAAQ,IAAI,KAAKA,OAAM,KAAK,uCAAuC,CAAC;AAAA,CAAI;AACxE,UAAQ;AAAA,IACNA,OAAM,KAAK,qBAAqB,IAC9B;AAAA,EACJ;AACF;AAEA,SAAS,aAAa,MAAoB;AAExC,QAAM,QAAQ,aAAa,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AACtD,QAAM,MAAM,QAAQ,MAAM,MAAM,KAAK,WAAW,mBAAmB,IAAI,OAAO,oBAAoB,IAAI;AAEtG,UAAQ,IAAIA,OAAM,KAAK;AAAA,uBAAmB,GAAG;AAAA,CAAO,CAAC;AAErD,MAAI;AACF,aAAS,eAAe,GAAG,IAAI,EAAE,OAAO,UAAU,CAAC;AACnD,YAAQ,IAAIA,OAAM,MAAM;AAAA,gCAA8B,GAAG,EAAE,CAAC;AAC5D,YAAQ,IAAIA,OAAM,KAAK;AAAA,eAAkBA,OAAM,KAAK,yBAAyB,OAAO,QAAQ,IAAI,EAAE,CAAC;AAAA,CAAI,CAAC;AAAA,EAC1G,QAAQ;AACN,YAAQ,MAAMA,OAAM,IAAI;AAAA,2BAAyB,GAAG,EAAE,CAAC;AACvD,YAAQ,MAAMA,OAAM,OAAO,+DAA+D,GAAG;AAAA,CAAI,CAAC;AAAA,EACpG;AACF;AAEO,SAAS,aAAa,SAAqC;AAChE,MAAI,QAAQ,SAAS;AACnB,iBAAa,QAAQ,OAAO;AAAA,EAC9B,OAAO;AACL,eAAW;AAAA,EACb;AACF;;;AChEA,eAAsB,YAA2B;AAC/C,QAAM,EAAE,eAAe,IAAI,MAAM,OAAO,iBAAe;AACvD,QAAM,eAAe;AACvB;;;ACHA;AAAA;AAAA;AAAA;AAAA;AAuBA,eAAsB,YACpB,WACA,YACA,cAA2B,CAAC,GAC5B,WACA,WAAmB,MACc;AACjC,MAAI;AACF,QAAI,UAAU,WAAW,aAAa,GAAG;AACvC,aAAO,MAAM,sBAAsB,WAAW,YAAY,SAAS;AAAA,IACrE,OAAO;AACL,aAAO,MAAM,iBAAiB,WAAW,YAAY,aAAa,WAAW,QAAQ;AAAA,IACvF;AAAA,EACF,SAAS,OAAO;AACd,QAAI,iBAAiB,OAAO;AAC1B,YAAM,IAAI,MAAM,0BAA0B,MAAM,OAAO,EAAE;AAAA,IAC3D;AACA,UAAM,IAAI,MAAM,+BAA+B;AAAA,EACjD;AACF;AAKA,eAAe,sBACb,WACA,YACA,WACiC;AAEjC,MAAI;AACJ,MAAI,UAAU,WAAW,mBAAmB,GAAG;AAE7C,uBAAmB;AAAA,EACrB,WAAW,UAAU,WAAW,aAAa,GAAG;AAE9C,uBAAmB,oBAAoB,UAAU,QAAQ,eAAe,EAAE,CAAC;AAAA,EAC7E,OAAO;AAEL,uBAAmB,oBAAoB,SAAS;AAAA,EAClD;AAGA,QAAM,iBAAiB;AAGvB,QAAM,eAAe,MAAM,OAAO;AAClC,QAAM,eAAe,aAAa,QAAQ,OAAO,cAAc;AAE/D,SAAO;AAAA,IACL,YAAY,UAAU,cAAc,SAAS;AAAA,EAC/C;AACF;AAKA,eAAe,iBACb,WACA,YACA,aACA,WACA,WAAmB,MACc;AACjC,MAAI,cAAc,WAAW;AAC3B,UAAM,IAAI,MAAM,mEAAmE;AAAA,EACrF;AAEA,QAAM,mBAAmB,iBAAiB,SAAS;AAGnD,QAAM,eAAe,MAAM,OAAO;AAClC,QAAM,eAAe,aAAa,QAAQ,OAAO,YAAY,aAAa,QAAQ;AAElF,SAAO;AAAA,IACL,YAAY,UAAU,cAAc,SAAS;AAAA,EAC/C;AACF;AAQO,SAAS,UAAU,MAAc,KAAsB;AAC5D,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,KAAK,QAAQ,WAAW,UAAU,GAAG,iBAAiB;AAC/D;;;AZ/FA,IAAM,aAAaC,MAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAE9D,SAAS,gBAAwB;AAC/B,QAAM,kBAAkBA,MAAK,QAAQ,YAAY,iBAAiB;AAClE,MAAIC,IAAG,WAAW,eAAe,GAAG;AAClC,QAAI;AACF,YAAM,cAAc,KAAK,MAAMA,IAAG,aAAa,iBAAiB,MAAM,CAAC;AACvE,aAAO,YAAY,WAAW;AAAA,IAChC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;AAEO,IAAM,UAAU,IAAI,QAAQ;AAEnC,QACG,KAAK,QAAQ,EACb,YAAY,4CAA4C,EACxD,QAAQ,cAAc,CAAC;AAG1B,QACG,QAAQ,UAAU,EAClB,YAAY,2CAA2C,EACvD,OAAO,uBAAuB,8CAA8C,EAC5E,OAAO,WAAW,kCAAkC,EACpD,OAAO,SAAS,6DAA6D,EAC7E,OAAO,eAAe,uEAAuE,EAC7F,OAAO,2BAA2B,iEAAiE,EACnG,OAAO,mBAAmB,iDAAiD,MAAM,EACjF,OAAO,cAAc;AAGxB,QACG,QAAQ,QAAQ,EAChB,YAAY,2CAA2C,EACvD,OAAO,uBAAuB,8CAA8C,EAC5E,OAAO,uBAAuB,0BAA0B,aAAa,EACrE,OAAO,WAAW,sDAAsD,EACxE,OAAO,YAAY;AAGtB,QACG,QAAQ,QAAQ,EAChB,YAAY,kDAAkD,EAC9D,OAAO,uBAAuB,8CAA8C,EAC5E,OAAO,sBAAsB,0CAA0C,EACvE,OAAO,uBAAuB,mBAAmB,EACjD,OAAO,mBAAmB,gCAAgC,MAAM,EAChE,OAAO,qBAAqB,mCAAmC,IAAI,EACnE,OAAO,WAAW,sDAAsD,EACxE,OAAO,YAAY;AAGtB,QACG,QAAQ,KAAK,EACb,YAAY,2CAA2C,EACvD,OAAO,uBAAuB,8CAA8C,EAC5E,OAAO,sBAAsB,0CAA0C,EACvE,OAAO,mBAAmB,gCAAgC,MAAM,EAChE,OAAO,qBAAqB,mCAAmC,IAAI,EACnE,OAAO,WAAW,sDAAsD,EACxE,OAAO,SAAS;AAGnB,QACG,QAAQ,MAAM,EACd,YAAY,wDAAwD,EACpE,OAAO,uBAAuB,0BAA0B,aAAa,EACrE,OAAO,UAAU;AAGpB,QACG,QAAQ,KAAK,EACb,YAAY,wCAAwC,EACpD,OAAO,uBAAuB,8CAA8C,EAC5E,OAAO,sBAAsB,0CAA0C,EACvE,OAAO,uBAAuB,yBAAyB,YAAY,EACnE,OAAO,qBAAqB,mCAAmC,IAAI,EACnE,OAAO,mBAAmB,8BAA8B,IAAI,EAC5D,OAAO,qBAAqB,uDAAuD,EACnF,OAAO,WAAW,sDAAsD,EACxE,OAAO,SAAS;AAGnB,QACG,QAAQ,QAAQ,EAChB,YAAY,qDAAqD,EACjE,OAAO,oBAAoB,yDAAyD,EACpF,OAAO,YAAY;AAGtB,QACG,QAAQ,KAAK,EACb,YAAY,8DAA8D,EAC1E,OAAO,SAAS;AAGnB,IAAI,QAAQ,IAAI,UAAU,MAAM,QAAQ;AACtC,QAAM,YAAY;AAChB,QAAI;AACF,YAAM,QAAQ,WAAW,QAAQ,IAAI;AAAA,IACvC,SAAS,GAAY;AACnB,cAAQ,MAAM,uBAAwB,EAAY,OAAO;AACzD,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF,GAAG;AACL;","names":["path","fs","fs","fs","fs","path","chalk","fs","fs","chalk","fs","fs","path","fs","path","fs","path","chalk","fs","path","fs","path","chalk","path","fs","chalk","fs","path","chalk","fs","path","chalk","chalk","path","fs"]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,12 +1,18 @@
1
1
  import {
2
2
  KNOWN_THEMES,
3
- analyzeAts,
4
3
  generateResumeYaml,
5
4
  getInstalledVersion,
5
+ getRubricEntry,
6
6
  isThemeInstalled,
7
+ listRubricMarkdown,
7
8
  loadTheme,
8
9
  processResumeData
9
- } from "../chunk-GRIYYG45.js";
10
+ } from "../chunk-G4AN2EMI.js";
11
+ import {
12
+ analyzeAts,
13
+ loadConfig
14
+ } from "../chunk-N55EPZ2N.js";
15
+ import "../chunk-QR77BRMN.js";
10
16
 
11
17
  // src/mcp/server.ts
12
18
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -133,87 +139,10 @@ languages:
133
139
  - Never use first person (I, my, me, we)
134
140
  - Summary: 2-4 sentences positioning the candidate for the specific role
135
141
  `;
136
- var ATS_SCORING_RUBRIC = `# ATS Scoring Rubric
137
-
138
- resuml performs deterministic, offline ATS (Applicant Tracking System) analysis.
139
-
140
- ## Scoring system
141
-
142
- ### Rating scale
143
- | Score | Rating | Description |
144
- |-------|--------|-------------|
145
- | 90-100 | Excellent | Resume is well-optimized for ATS |
146
- | 75-89 | Good | Resume passes most ATS checks |
147
- | 60-74 | Needs Work | Several improvements recommended |
148
- | 0-59 | Poor | Significant issues found |
149
-
150
- ### Weight system
151
- Each check has a weight that affects the final score:
152
- - **High weight (3x)**: Critical checks that significantly impact ATS parsing
153
- - **Medium weight (2x)**: Important but not critical checks
154
- - **Low weight (1x)**: Nice-to-have improvements
155
-
156
- ### Combined scoring (with job description)
157
- When a job description is provided:
158
- - Generic checks: 60% of final score
159
- - Keyword match: 40% of final score
160
-
161
- ## Checks performed
162
-
163
- ### Contact Information (category: contact)
164
- | Check | Weight | What it verifies |
165
- |-------|--------|-----------------|
166
- | contact-complete | High | Name, email, phone, and city are all present |
167
- | has-linkedin | Medium | LinkedIn profile exists in profiles section |
168
-
169
- ### Content Quality (category: content)
170
- | Check | Weight | What it verifies |
171
- |-------|--------|-----------------|
172
- | has-summary | High | Professional summary exists (15-100 words) |
173
- | work-highlights | High | Each work entry has at least 2 highlights |
174
- | action-verbs | Medium | Highlights start with action verbs |
175
- | quantified-impact | Medium | 50%+ of highlights include numbers/metrics |
176
- | no-first-person | Low | No first-person pronouns (I, my, me, we) |
177
-
178
- ### Resume Structure (category: structure)
179
- | Check | Weight | What it verifies |
180
- |-------|--------|-----------------|
181
- | date-consistency | Medium | All dates are valid ISO 8601 format |
182
- | skills-populated | Medium | At least 3 skill categories defined |
183
- | education-complete | Medium | Education section has institution and area |
184
- | essential-sections | High | Work, education, and skills sections present |
185
-
186
- ## Job description matching
187
- When a job description is provided, resuml extracts keywords using TF-based extraction
188
- and matches them against the resume using stem matching. Results include:
189
- - **matched**: Keywords found in the resume
190
- - **missing**: Keywords not found (add these to improve score)
191
- - **matchPercentage**: Percentage of JD keywords found in resume
192
-
193
- ## Fit Assessment
194
- When a job description is provided, a \`fitAssessment\` field is included in the result:
195
- | Match % | Level | Meaning |
196
- |---------|-------|---------|
197
- | >= 70% | strong | Resume aligns well with the job description |
198
- | 50-69% | partial | Some alignment; emphasize transferable skills |
199
- | < 50% | weak | Significant skill gaps; role may not match profile |
200
-
201
- The assessment includes the top 5 missing keywords as specific gaps to address.
202
- Use this to advise users whether to apply or focus effort elsewhere.
203
-
204
- ## Tips for improving ATS score
205
- 1. Include all contact information (name, email, phone, city)
206
- 2. Add a LinkedIn profile URL
207
- 3. Write a 2-4 sentence professional summary
208
- 4. Use action verbs to start each highlight
209
- 5. Quantify achievements with numbers (%, $, time saved, team size)
210
- 6. Include at least 3 skill categories with relevant keywords
211
- 7. When targeting a job, mirror exact terminology from the job description
212
- `;
213
142
  function createServer() {
214
143
  const server = new McpServer({
215
144
  name: "resuml",
216
- version: "1.0.0"
145
+ version: "2.0.0"
217
146
  });
218
147
  server.registerResource(
219
148
  "json-resume-schema",
@@ -223,26 +152,30 @@ function createServer() {
223
152
  mimeType: "text/markdown"
224
153
  },
225
154
  () => ({
226
- contents: [{
227
- uri: "resuml://schema/json-resume",
228
- mimeType: "text/markdown",
229
- text: JSON_RESUME_SCHEMA_REFERENCE
230
- }]
155
+ contents: [
156
+ {
157
+ uri: "resuml://schema/json-resume",
158
+ mimeType: "text/markdown",
159
+ text: JSON_RESUME_SCHEMA_REFERENCE
160
+ }
161
+ ]
231
162
  })
232
163
  );
233
164
  server.registerResource(
234
- "ats-scoring-rubric",
235
- "resuml://docs/ats-scoring",
165
+ "ats-rubric",
166
+ "resuml://docs/ats-rubric",
236
167
  {
237
- description: "ATS scoring rubric: checks performed, weight system, rating scale, and tips for improving score",
168
+ description: "Tiered ATS rubric: every check, its tier, weight, evidence level, description, and source URL.",
238
169
  mimeType: "text/markdown"
239
170
  },
240
171
  () => ({
241
- contents: [{
242
- uri: "resuml://docs/ats-scoring",
243
- mimeType: "text/markdown",
244
- text: ATS_SCORING_RUBRIC
245
- }]
172
+ contents: [
173
+ {
174
+ uri: "resuml://docs/ats-rubric",
175
+ mimeType: "text/markdown",
176
+ text: listRubricMarkdown()
177
+ }
178
+ ]
246
179
  })
247
180
  );
248
181
  server.registerResource(
@@ -261,11 +194,13 @@ function createServer() {
261
194
  version: getInstalledVersion(t.pkg)
262
195
  }));
263
196
  return {
264
- contents: [{
265
- uri: "resuml://themes/catalog",
266
- mimeType: "application/json",
267
- text: JSON.stringify({ themes, totalCount: themes.length }, null, 2)
268
- }]
197
+ contents: [
198
+ {
199
+ uri: "resuml://themes/catalog",
200
+ mimeType: "application/json",
201
+ text: JSON.stringify({ themes, totalCount: themes.length }, null, 2)
202
+ }
203
+ ]
269
204
  };
270
205
  }
271
206
  );
@@ -278,15 +213,18 @@ function createServer() {
278
213
  jobDescription: z.string().describe("The full job description text"),
279
214
  candidateName: z.string().optional().describe("Candidate full name"),
280
215
  candidateEmail: z.string().optional().describe("Candidate email address"),
281
- candidateBackground: z.string().optional().describe("Brief summary of the candidate background, skills, and experience to incorporate")
216
+ candidateBackground: z.string().optional().describe(
217
+ "Brief summary of the candidate background, skills, and experience to incorporate"
218
+ )
282
219
  }
283
220
  },
284
221
  ({ jobDescription, candidateName, candidateEmail, candidateBackground }) => ({
285
- messages: [{
286
- role: "user",
287
- content: {
288
- type: "text",
289
- text: `Create a tailored resume in YAML format optimized for the following job description.
222
+ messages: [
223
+ {
224
+ role: "user",
225
+ content: {
226
+ type: "text",
227
+ text: `Create a tailored resume in YAML format optimized for the following job description.
290
228
 
291
229
  ## Job Description
292
230
  ${jobDescription}
@@ -310,13 +248,14 @@ ${candidateEmail ? `10. Use candidate email: ${candidateEmail}` : ""}
310
248
  ## Workflow
311
249
  After generating the YAML:
312
250
  1. Use \`resuml_validate\` to check schema compliance
313
- 2. Use \`resuml_ats_check\` with the job description text. Target: score >= 75, keyword match >= 70%
251
+ 2. Use \`resuml_ats_check\` with the job description text. Target: total >= 75, parsing tier grade A, hard-skill-overlap >= 70%
314
252
  3. If ATS score is low, revise the YAML and re-check
315
253
  4. Use \`resuml_render\` with theme "even" for the final output
316
254
 
317
255
  Output the resume YAML first, then run the validation and ATS check tools.`
256
+ }
318
257
  }
319
- }]
258
+ ]
320
259
  })
321
260
  );
322
261
  server.registerPrompt(
@@ -331,11 +270,12 @@ Output the resume YAML first, then run the validation and ATS check tools.`
331
270
  }
332
271
  },
333
272
  ({ resumeYaml, jobDescription, targetScore }) => ({
334
- messages: [{
335
- role: "user",
336
- content: {
337
- type: "text",
338
- text: `Optimize this resume YAML to maximize its ATS score${targetScore ? ` (target: ${targetScore})` : " (target: 85+)"}.
273
+ messages: [
274
+ {
275
+ role: "user",
276
+ content: {
277
+ type: "text",
278
+ text: `Optimize this resume YAML to maximize its ATS score${targetScore ? ` (target: ${targetScore})` : " (target: 85+)"}.
339
279
 
340
280
  ## Current Resume YAML
341
281
  \`\`\`yaml
@@ -348,7 +288,7 @@ ${jobDescription}
348
288
  ## Instructions
349
289
 
350
290
  1. First, run \`resuml_ats_check\` on the current YAML${jobDescription ? " with the job description" : ""} to get the baseline score
351
- 2. Read the ATS scoring rubric (resuml://docs/ats-scoring) to understand what checks are performed
291
+ 2. Read the ATS rubric (resuml://docs/ats-rubric) to understand what checks are performed, their tier, and weight
352
292
  3. Review each failed or low-scoring check and fix the issues:
353
293
  - Missing contact info \u2192 add it
354
294
  - No summary \u2192 write a 2-4 sentence professional summary
@@ -359,8 +299,9 @@ ${jobDescription}
359
299
  5. Repeat until the target score is reached
360
300
 
361
301
  Output the improved YAML with a summary of changes made.`
302
+ }
362
303
  }
363
- }]
304
+ ]
364
305
  })
365
306
  );
366
307
  server.registerPrompt(
@@ -373,11 +314,12 @@ Output the improved YAML with a summary of changes made.`
373
314
  }
374
315
  },
375
316
  ({ resumeYaml }) => ({
376
- messages: [{
377
- role: "user",
378
- content: {
379
- type: "text",
380
- text: `Perform a comprehensive review of this resume.
317
+ messages: [
318
+ {
319
+ role: "user",
320
+ content: {
321
+ type: "text",
322
+ text: `Perform a comprehensive review of this resume.
381
323
 
382
324
  ## Resume YAML
383
325
  \`\`\`yaml
@@ -400,8 +342,9 @@ ${resumeYaml}
400
342
  - **Strengths**: What the resume does well
401
343
  - **Improvements**: Specific, actionable suggestions
402
344
  - **Revised YAML**: An improved version if significant changes are recommended`
345
+ }
403
346
  }
404
- }]
347
+ ]
405
348
  })
406
349
  );
407
350
  server.registerTool(
@@ -439,13 +382,20 @@ ${resumeYaml}
439
382
  await processResumeData([yaml]);
440
383
  restoreStdout();
441
384
  return {
442
- content: [{ type: "text", text: JSON.stringify({ valid: true, errors: [] }, null, 2) }]
385
+ content: [
386
+ { type: "text", text: JSON.stringify({ valid: true, errors: [] }, null, 2) }
387
+ ]
443
388
  };
444
389
  } catch (e) {
445
390
  restoreStdout();
446
391
  const message = e instanceof Error ? e.message : String(e);
447
392
  return {
448
- content: [{ type: "text", text: JSON.stringify({ valid: false, errors: [message] }, null, 2) }]
393
+ content: [
394
+ {
395
+ type: "text",
396
+ text: JSON.stringify({ valid: false, errors: [message] }, null, 2)
397
+ }
398
+ ]
449
399
  };
450
400
  }
451
401
  }
@@ -465,9 +415,11 @@ ${resumeYaml}
465
415
  suppressStdout();
466
416
  try {
467
417
  const resume = await processResumeData([yaml]);
418
+ const cfg = loadConfig();
468
419
  const result = analyzeAts(resume, {
469
- language: language ?? "en",
470
- jobDescription
420
+ language: language ?? cfg.locale,
421
+ jobDescription,
422
+ config: cfg
471
423
  });
472
424
  restoreStdout();
473
425
  return {
@@ -476,12 +428,42 @@ ${resumeYaml}
476
428
  } catch (e) {
477
429
  restoreStdout();
478
430
  return {
479
- content: [{ type: "text", text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }) }],
431
+ content: [
432
+ {
433
+ type: "text",
434
+ text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) })
435
+ }
436
+ ],
480
437
  isError: true
481
438
  };
482
439
  }
483
440
  }
484
441
  );
442
+ server.registerTool(
443
+ "resuml_ats_explain",
444
+ {
445
+ title: "ATS Rubric Explain",
446
+ description: "Return the rubric entry (tier, weight, evidence level, description, source) for a given check id.",
447
+ inputSchema: {
448
+ checkId: z.string().describe("Check id, e.g. quantification-density")
449
+ }
450
+ },
451
+ ({ checkId }) => {
452
+ const entry = getRubricEntry(checkId);
453
+ if (!entry) {
454
+ return {
455
+ content: [
456
+ {
457
+ type: "text",
458
+ text: JSON.stringify({ error: `Unknown check id: ${checkId}` })
459
+ }
460
+ ],
461
+ isError: true
462
+ };
463
+ }
464
+ return { content: [{ type: "text", text: JSON.stringify(entry, null, 2) }] };
465
+ }
466
+ );
485
467
  server.registerTool(
486
468
  "resuml_render",
487
469
  {
@@ -545,7 +527,9 @@ ${resumeYaml}
545
527
  theme: z.string().default("even").describe("Theme name"),
546
528
  format: z.enum(["A4", "Letter"]).default("A4").describe("Paper format"),
547
529
  locale: z.string().optional().describe("Locale for theme rendering (e.g. en, de)"),
548
- margin: z.string().optional().describe('Page margins. Single value (e.g. "10mm") for all sides, two values (e.g. "10mm,15mm") for vertical/horizontal, or four values (e.g. "10mm,15mm,10mm,15mm") for top/right/bottom/left')
530
+ margin: z.string().optional().describe(
531
+ 'Page margins. Single value (e.g. "10mm") for all sides, two values (e.g. "10mm,15mm") for vertical/horizontal, or four values (e.g. "10mm,15mm,10mm,15mm") for top/right/bottom/left'
532
+ )
549
533
  }
550
534
  },
551
535
  async (args) => {
@@ -566,7 +550,14 @@ ${resumeYaml}
566
550
  } catch {
567
551
  restoreStdout();
568
552
  return {
569
- content: [{ type: "text", text: JSON.stringify({ error: "Playwright is not installed. Run: npm install playwright" }) }],
553
+ content: [
554
+ {
555
+ type: "text",
556
+ text: JSON.stringify({
557
+ error: "Playwright is not installed. Run: npm install playwright"
558
+ })
559
+ }
560
+ ],
570
561
  isError: true
571
562
  };
572
563
  }
@@ -582,19 +573,26 @@ ${resumeYaml}
582
573
  await browser.close();
583
574
  restoreStdout();
584
575
  return {
585
- content: [{
586
- type: "text",
587
- text: JSON.stringify({
588
- pdf: Buffer.from(pdfBuffer).toString("base64"),
589
- encoding: "base64",
590
- format
591
- })
592
- }]
576
+ content: [
577
+ {
578
+ type: "text",
579
+ text: JSON.stringify({
580
+ pdf: Buffer.from(pdfBuffer).toString("base64"),
581
+ encoding: "base64",
582
+ format
583
+ })
584
+ }
585
+ ]
593
586
  };
594
587
  } catch (e) {
595
588
  restoreStdout();
596
589
  return {
597
- content: [{ type: "text", text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }) }],
590
+ content: [
591
+ {
592
+ type: "text",
593
+ text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) })
594
+ }
595
+ ],
598
596
  isError: true
599
597
  };
600
598
  }