resuml 3.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/commands/ats.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';\nimport { atsExplain, atsConfigPrint } from './commands/ats';\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 {\n version?: string;\n };\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(\n '--ats-threshold <score>',\n 'Minimum ATS score (0-100). Exit with code 1 if below threshold.'\n )\n .option('--format <type>', 'Output format for ATS results (text or json).', 'text')\n .option('--config <path>', 'Path to resuml.config.yaml (default: ./resuml.config.yaml).')\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// ATS Subcommand\nconst ats = program.command('ats').description('ATS rubric utilities.');\n\nats\n .command('explain [id]')\n .description('Print rubric entry for a check id, or full rubric if id omitted.')\n .action((id?: string) => {\n atsExplain(id);\n });\n\nats\n .command('config')\n .description('Print the effective merged ATS config.')\n .option('--print', 'Print the merged config (default action).')\n .option('--config <path>', 'Path to resuml.config.yaml.')\n .action((opts: { config?: string }) => {\n atsConfigPrint(opts);\n });\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 { TieredAtsResult, 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 { TieredAtsResult } from '../ats/index';\nimport { loadConfig } from '../utils/config';\n\ninterface ValidateCommandOptions {\n resume?: string;\n debug?: boolean;\n ats?: boolean;\n jd?: string;\n atsThreshold?: string;\n format?: string;\n config?: string;\n}\n\nfunction formatAtsReport(\n result: TieredAtsResult,\n debug: boolean,\n chalk: typeof import('chalk').default\n): void {\n const scoreColor =\n 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(\n ` Score: ${scoreColor(chalk.bold(`${result.score}/100`))} (${result.rating.replace('-', ' ')})`\n );\n console.log(` ${result.summary}`);\n console.log('');\n\n const tierLabels: Record<string, string> = {\n parsing: 'Parsing',\n recruiter: 'Recruiter',\n match: 'JD Match',\n };\n\n for (const [tierName, tier] of Object.entries(result.tiers)) {\n const label = tierLabels[tierName] ?? tierName;\n console.log(chalk.bold(` ${label} (${tier.score}/100, grade ${tier.grade})`));\n\n for (const check of tier.checks) {\n if (!debug && (check.status === 'pass' || check.status === 'skipped')) continue;\n const icon =\n check.status === 'pass'\n ? chalk.green('v')\n : check.status === 'skipped'\n ? chalk.dim('-')\n : check.status === 'warn'\n ? chalk.yellow('!')\n : chalk.red('x');\n const scoreText = chalk.dim(`[${check.score}]`);\n console.log(` ${icon} ${check.message} ${scoreText}`);\n for (const hint of check.hints) {\n console.log(chalk.dim(` -> ${hint}`));\n }\n }\n console.log('');\n }\n\n if (result.knockouts.length > 0) {\n console.log(chalk.bold(' Knockout Signals'));\n for (const k of result.knockouts) {\n console.log(chalk.red(` ! ${k.signal}: ${k.evidence}`));\n console.log(chalk.dim(` -> ${k.recommendation}`));\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 cfg = loadConfig(options.config ? { configPath: options.config } : {});\n const result = analyzeAts(resumeData, {\n language: cfg.locale,\n jobDescription,\n config: cfg,\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') {\n // 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, () => {\n void renderResume(options);\n });\n } else if (fs.existsSync(inputPath)) {\n watchFile(inputPath, () => {\n void renderResume(options);\n });\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 const configPath = path.join(path.dirname(fullPath), 'resuml.config.yaml');\n const template = `# resuml ATS configuration\n# Override defaults; everything is optional.\n# Run \\`resuml ats config --print\\` to see the merged effective config.\nats:\n weights:\n tiers:\n parsing: 30\n match: 50\n recruiter: 20\n thresholds:\n seniorYoeCutoff: 10\n disable: []\n`;\n try {\n fs.writeFileSync(configPath, template, { encoding: 'utf8', flag: 'wx' });\n console.log(chalk.green(`Created resuml.config.yaml`));\n } catch (err) {\n // EEXIST is the expected race-safe outcome when the file already exists.\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;\n }\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(\n ' 3. Run ' + chalk.cyan('resuml render --resume ' + outputPath + ' --theme stackoverflow')\n );\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 evaluate<T>(fn: () => T): Promise<T>;\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 bodyText = await page.evaluate(() => document.body.innerText || '');\n const bodyWords = bodyText.trim().split(/\\s+/).filter(Boolean).length;\n const resumeContentParts: string[] = [];\n if (resumeData.basics?.summary) resumeContentParts.push(resumeData.basics.summary);\n for (const w of resumeData.work || []) {\n if (w.summary) resumeContentParts.push(w.summary);\n resumeContentParts.push(...(w.highlights || []));\n }\n for (const p of resumeData.projects || []) {\n if (p.description) resumeContentParts.push(p.description);\n resumeContentParts.push(...(p.highlights || []));\n }\n for (const s of resumeData.skills || []) {\n if (s.name) resumeContentParts.push(s.name);\n resumeContentParts.push(...(s.keywords || []));\n }\n const resumeWords = resumeContentParts.join(' ').split(/\\s+/).filter(Boolean).length;\n if (resumeWords > 0 && bodyWords < resumeWords * 0.7) {\n console.warn(\n chalk.yellow(\n `pdf-text-extractable: rendered text ${bodyWords} words vs resume ${resumeWords} (under 70%). Theme may use image-based glyphs.`\n )\n );\n }\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 const sizeMb = pdfBuffer.length / (1024 * 1024);\n if (sizeMb > 2.5) {\n console.warn(\n chalk.yellow(\n `pdf-size-under-2.5mb: PDF is ${sizeMb.toFixed(2)} MB (Greenhouse limit 2.5 MB).`\n )\n );\n }\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(\n ` ${'─'.repeat(10)}${'─'.repeat(nameWidth)}${'─'.repeat(pkgWidth)}${'─'.repeat(30)}`\n );\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: ') + '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\n ? known.pkg\n : name.startsWith('jsonresume-theme-')\n ? name\n : `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(\n chalk.blue(`\\nUse it with: ${chalk.cyan(`resuml render --theme ${known?.name || name}`)}\\n`)\n );\n } catch {\n console.error(chalk.red(`\\n❌ Failed to install ${pkg}`));\n console.error(\n chalk.yellow(`Make sure the package exists: https://www.npmjs.com/package/${pkg}\\n`)\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 yaml from 'yaml';\nimport chalk from 'chalk';\nimport { getRubricEntry, listRubricMarkdown } from '../ats/rubric';\nimport { loadConfig } from '../utils/config';\n\nexport function atsExplain(id?: string): void {\n if (!id) {\n console.log(listRubricMarkdown());\n return;\n }\n const entry = getRubricEntry(id);\n if (!entry) {\n console.error(chalk.red(`Unknown rubric id: ${id}`));\n process.exit(1);\n return;\n }\n console.log(chalk.bold(entry.id));\n console.log(` Tier: ${entry.tier}`);\n console.log(` Weight: ${entry.weight}`);\n console.log(` Evidence: ${entry.evidenceLevel}`);\n console.log(` Description: ${entry.description}`);\n if (entry.source) console.log(` Source: ${entry.source}`);\n}\n\nexport function atsConfigPrint(opts: { config?: string }): void {\n const cfg = loadConfig(opts.config ? { configPath: opts.config } : {});\n console.log(yaml.stringify({ ats: cfg }));\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 {\n default: { render: (d: unknown) => string };\n };\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 {\n default: { render: (d: unknown, c: unknown, l: string) => string };\n };\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":"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","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","yaml","fs","path","chalk","fs","path","chalk","chalk","chalk","chalk","path","fs"]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,15 +1,18 @@
1
1
  import {
2
2
  KNOWN_THEMES,
3
- analyzeAts,
4
3
  generateResumeYaml,
5
4
  getInstalledVersion,
6
5
  getRubricEntry,
7
6
  isThemeInstalled,
8
7
  listRubricMarkdown,
9
- loadConfig,
10
8
  loadTheme,
11
9
  processResumeData
12
- } from "../chunk-R4MD5YMV.js";
10
+ } from "../chunk-G4AN2EMI.js";
11
+ import {
12
+ analyzeAts,
13
+ loadConfig
14
+ } from "../chunk-N55EPZ2N.js";
15
+ import "../chunk-QR77BRMN.js";
13
16
 
14
17
  // src/mcp/server.ts
15
18
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/mcp/server.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod';\nimport { processResumeData } from '../core';\nimport { analyzeAts } from '../ats/index';\nimport { listRubricMarkdown, getRubricEntry } from '../ats/rubric';\nimport { loadConfig } from '../utils/config';\nimport { loadTheme } from '../utils/themeLoader';\nimport { generateResumeYaml } from '../utils/resumeTemplate';\nimport { KNOWN_THEMES, isThemeInstalled, getInstalledVersion } from '../utils/themeInfo';\n\n// Redirect console.log to stderr so it doesn't corrupt the MCP stdio channel\nconst originalLog = console.log;\nconst originalWarn = console.warn;\n\nfunction suppressStdout() {\n console.log = (...args: unknown[]) => {\n console.error('[resuml]', ...args);\n };\n console.warn = (...args: unknown[]) => {\n console.error('[resuml]', ...args);\n };\n}\n\nfunction restoreStdout() {\n console.log = originalLog;\n console.warn = originalWarn;\n}\n\n// ── Shared data ─────────────────────────────────────────────────────\n\nconst JSON_RESUME_SCHEMA_REFERENCE = `# JSON Resume Schema Reference\n\nThe JSON Resume schema defines the structure for resume data. resuml uses YAML as the input format.\n\n## Top-level sections\n\n| Section | Required | Description |\n|---------|----------|-------------|\n| basics | Yes | Name, label, email, phone, url, summary, location, profiles |\n| work | Recommended | Work experience entries |\n| education | Recommended | Education entries |\n| skills | Recommended | Skill categories with keywords |\n| projects | Optional | Project entries |\n| volunteer | Optional | Volunteer experience |\n| awards | Optional | Awards and honors |\n| certificates | Optional | Professional certifications |\n| publications | Optional | Published works |\n| languages | Optional | Language proficiencies |\n| interests | Optional | Personal interests |\n| references | Optional | Professional references |\n\n## Section schemas\n\n### basics\n\\`\\`\\`yaml\nbasics:\n name: \"Full Name\" # required\n label: \"Professional Title\"\n email: \"email@example.com\"\n phone: \"+1-555-123-4567\"\n url: \"https://website.com\"\n summary: \"2-4 sentence professional summary\"\n location:\n city: \"City\"\n countryCode: \"US\"\n region: \"State\"\n profiles:\n - network: \"LinkedIn\"\n username: \"username\"\n url: \"https://linkedin.com/in/username\"\n\\`\\`\\`\n\n### work\n\\`\\`\\`yaml\nwork:\n - name: \"Company Name\"\n position: \"Job Title\"\n url: \"https://company.com\"\n startDate: \"2020-01-01\" # ISO 8601\n endDate: \"2023-12-31\" # omit for current position\n summary: \"Role description\"\n highlights:\n - \"Achievement with measurable result\"\n\\`\\`\\`\n\n### education\n\\`\\`\\`yaml\neducation:\n - institution: \"University\"\n area: \"Field of Study\"\n studyType: \"Degree Type\" # e.g. Bachelor, Master, PhD\n startDate: \"2014-09-01\"\n endDate: \"2018-06-01\"\n\\`\\`\\`\n\n### skills\n\\`\\`\\`yaml\nskills:\n - name: \"Category\"\n level: \"Expert\" # Master, Expert, Advanced, Intermediate, Beginner\n keywords: [\"Skill1\", \"Skill2\"]\n\\`\\`\\`\n\n### projects\n\\`\\`\\`yaml\nprojects:\n - name: \"Project Name\"\n description: \"What it does\"\n highlights: [\"Key achievement\"]\n keywords: [\"Tech1\", \"Tech2\"]\n startDate: \"2023-01-01\"\n url: \"https://github.com/...\"\n\\`\\`\\`\n\n### certificates\n\\`\\`\\`yaml\ncertificates:\n - name: \"Certificate Name\"\n date: \"2023-01-01\"\n issuer: \"Issuing Organization\"\n url: \"https://credential-url.com\"\n\\`\\`\\`\n\n### languages\n\\`\\`\\`yaml\nlanguages:\n - language: \"English\"\n fluency: \"Native speaker\" # Native speaker, Fluent, Advanced, Intermediate, Elementary\n\\`\\`\\`\n\n## Formatting rules\n- Dates: ISO 8601 format (YYYY-MM-DD or YYYY-MM)\n- Start highlights with action verbs: Developed, Implemented, Led, Optimized, Reduced, Built, Designed\n- Include numbers in 50%+ of highlights (e.g., \"Reduced latency by 40%\")\n- Never use first person (I, my, me, we)\n- Summary: 2-4 sentences positioning the candidate for the specific role\n`;\n\nfunction createServer(): McpServer {\n const server = new McpServer({\n name: 'resuml',\n version: '2.0.0',\n });\n\n // ═══════════════════════════════════════════════════════════════════\n // RESOURCES\n // ═══════════════════════════════════════════════════════════════════\n\n // ── JSON Resume Schema Reference ──────────────────────────────────\n\n server.registerResource(\n 'json-resume-schema',\n 'resuml://schema/json-resume',\n {\n description:\n 'JSON Resume schema reference with all sections, field types, and formatting rules',\n mimeType: 'text/markdown',\n },\n () => ({\n contents: [\n {\n uri: 'resuml://schema/json-resume',\n mimeType: 'text/markdown',\n text: JSON_RESUME_SCHEMA_REFERENCE,\n },\n ],\n })\n );\n\n // ── ATS Rubric ───────────────────────────────────────────────────\n\n server.registerResource(\n 'ats-rubric',\n 'resuml://docs/ats-rubric',\n {\n description:\n 'Tiered ATS rubric: every check, its tier, weight, evidence level, description, and source URL.',\n mimeType: 'text/markdown',\n },\n () => ({\n contents: [\n {\n uri: 'resuml://docs/ats-rubric',\n mimeType: 'text/markdown',\n text: listRubricMarkdown(),\n },\n ],\n })\n );\n\n // ── Theme Catalog ─────────────────────────────────────────────────\n\n server.registerResource(\n 'theme-catalog',\n 'resuml://themes/catalog',\n {\n description: 'Available resume themes with descriptions and installation status',\n mimeType: 'application/json',\n },\n () => {\n const themes = KNOWN_THEMES.map((t) => ({\n name: t.name,\n package: t.pkg,\n description: t.description,\n installed: isThemeInstalled(t.pkg),\n version: getInstalledVersion(t.pkg),\n }));\n return {\n contents: [\n {\n uri: 'resuml://themes/catalog',\n mimeType: 'application/json',\n text: JSON.stringify({ themes, totalCount: themes.length }, null, 2),\n },\n ],\n };\n }\n );\n\n // ═══════════════════════════════════════════════════════════════════\n // PROMPTS\n // ═══════════════════════════════════════════════════════════════════\n\n // ── Tailor Resume to Job Description ──────────────────────────────\n\n server.registerPrompt(\n 'tailor-resume-to-jd',\n {\n title: 'Tailor Resume to Job Description',\n description: 'Generate a tailored resume YAML optimized for a specific job description',\n argsSchema: {\n jobDescription: z.string().describe('The full job description text'),\n candidateName: z.string().optional().describe('Candidate full name'),\n candidateEmail: z.string().optional().describe('Candidate email address'),\n candidateBackground: z\n .string()\n .optional()\n .describe(\n 'Brief summary of the candidate background, skills, and experience to incorporate'\n ),\n },\n },\n ({ jobDescription, candidateName, candidateEmail, candidateBackground }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Create a tailored resume in YAML format optimized for the following job description.\n\n## Job Description\n${jobDescription}\n\n${candidateBackground ? `## Candidate Background\\n${candidateBackground}\\n` : ''}\n## Instructions\n\n1. Analyze the job description to identify required skills, technologies, experience level, and industry terms\n2. Generate a complete resume YAML following the JSON Resume schema (read the resuml://schema/json-resume resource for the full schema)\n3. Mirror exact terminology from the job description in skills and highlights\n4. Start every highlight with an action verb (Developed, Implemented, Led, Optimized, Reduced, Built, Designed)\n5. Include numbers in 50%+ of highlights (e.g., \"Reduced latency by 40%\", \"Managed team of 8\")\n6. Never use \"I\", \"my\", \"me\", \"we\"\n7. Write a 2-4 sentence summary positioning the candidate for this specific role\n8. Use ISO 8601 dates (YYYY-MM-DD or YYYY-MM)\n${candidateName ? `9. Use candidate name: ${candidateName}` : ''}\n${candidateEmail ? `10. Use candidate email: ${candidateEmail}` : ''}\n\n## Workflow\nAfter generating the YAML:\n1. Use \\`resuml_validate\\` to check schema compliance\n2. Use \\`resuml_ats_check\\` with the job description text. Target: total >= 75, parsing tier grade A, hard-skill-overlap >= 70%\n3. If ATS score is low, revise the YAML and re-check\n4. Use \\`resuml_render\\` with theme \"even\" for the final output\n\nOutput the resume YAML first, then run the validation and ATS check tools.`,\n },\n },\n ],\n })\n );\n\n // ── Optimize ATS Score ────────────────────────────────────────────\n\n server.registerPrompt(\n 'optimize-ats-score',\n {\n title: 'Optimize ATS Score',\n description: 'Analyze and improve an existing resume YAML to maximize its ATS score',\n argsSchema: {\n resumeYaml: z.string().describe('The current resume YAML content'),\n jobDescription: z\n .string()\n .optional()\n .describe('Optional job description to optimize against'),\n targetScore: z.string().optional().describe('Target ATS score (default: 85)'),\n },\n },\n ({ resumeYaml, jobDescription, targetScore }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Optimize this resume YAML to maximize its ATS score${targetScore ? ` (target: ${targetScore})` : ' (target: 85+)'}.\n\n## Current Resume YAML\n\\`\\`\\`yaml\n${resumeYaml}\n\\`\\`\\`\n\n${jobDescription ? `## Job Description\\n${jobDescription}\\n` : ''}\n## Instructions\n\n1. First, run \\`resuml_ats_check\\` on the current YAML${jobDescription ? ' with the job description' : ''} to get the baseline score\n2. Read the ATS rubric (resuml://docs/ats-rubric) to understand what checks are performed, their tier, and weight\n3. Review each failed or low-scoring check and fix the issues:\n - Missing contact info → add it\n - No summary → write a 2-4 sentence professional summary\n - Weak highlights → rewrite with action verbs and quantified metrics\n - Missing keywords → incorporate them naturally into skills and highlights\n - Structural issues → ensure all essential sections are present\n4. Run \\`resuml_ats_check\\` again to verify improvement\n5. Repeat until the target score is reached\n\nOutput the improved YAML with a summary of changes made.`,\n },\n },\n ],\n })\n );\n\n // ── Review Resume ─────────────────────────────────────────────────\n\n server.registerPrompt(\n 'review-resume',\n {\n title: 'Review Resume',\n description:\n 'Comprehensive review of a resume YAML with ATS analysis and improvement suggestions',\n argsSchema: {\n resumeYaml: z.string().describe('The resume YAML content to review'),\n },\n },\n ({ resumeYaml }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Perform a comprehensive review of this resume.\n\n## Resume YAML\n\\`\\`\\`yaml\n${resumeYaml}\n\\`\\`\\`\n\n## Review steps\n\n1. Run \\`resuml_validate\\` to check schema compliance\n2. Run \\`resuml_ats_check\\` for ATS analysis\n3. Review content quality:\n - Is the summary compelling and role-specific?\n - Do highlights use strong action verbs?\n - Are achievements quantified with metrics?\n - Are skills well-organized and comprehensive?\n - Is the work history clear and impactful?\n4. Provide a structured review with:\n - **ATS Score**: Current score and rating\n - **Schema Issues**: Any validation errors\n - **Strengths**: What the resume does well\n - **Improvements**: Specific, actionable suggestions\n - **Revised YAML**: An improved version if significant changes are recommended`,\n },\n },\n ],\n })\n );\n\n // ═══════════════════════════════════════════════════════════════════\n // TOOLS\n // ═══════════════════════════════════════════════════════════════════\n\n // ── resuml_init_resume ──────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_init_resume',\n {\n title: 'Init Resume',\n description: 'Generate a starter resume YAML template following the JSON Resume schema',\n inputSchema: {\n name: z.string().optional().describe('Full name for the resume'),\n title: z.string().optional().describe('Professional title/label'),\n email: z.string().optional().describe('Email address'),\n },\n },\n ({ name, title, email }) => {\n const yaml = generateResumeYaml(\n name ?? 'Your Name',\n email ?? 'email@example.com',\n title ?? 'Professional Title'\n );\n return { content: [{ type: 'text' as const, text: yaml }] };\n }\n );\n\n // ── resuml_validate ─────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_validate',\n {\n title: 'Validate Resume',\n description: 'Validate resume YAML against the JSON Resume schema',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n },\n },\n async ({ yaml }) => {\n suppressStdout();\n try {\n await processResumeData([yaml]);\n restoreStdout();\n return {\n content: [\n { type: 'text' as const, text: JSON.stringify({ valid: true, errors: [] }, null, 2) },\n ],\n };\n } catch (e: unknown) {\n restoreStdout();\n const message = e instanceof Error ? e.message : String(e);\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ valid: false, errors: [message] }, null, 2),\n },\n ],\n };\n }\n }\n );\n\n // ── resuml_ats_check ────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_ats_check',\n {\n title: 'ATS Check',\n description:\n 'Run ATS (Applicant Tracking System) analysis on a resume, optionally matching against a job description',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n jobDescription: z\n .string()\n .optional()\n .describe('Job description text to match keywords against'),\n language: z.enum(['en', 'de']).optional().describe('Language for analysis (default: en)'),\n },\n },\n async ({ yaml, jobDescription, language }) => {\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const cfg = loadConfig();\n const result = analyzeAts(resume, {\n language: language ?? cfg.locale,\n jobDescription,\n config: cfg,\n });\n restoreStdout();\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n } catch (e: unknown) {\n restoreStdout();\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }),\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n // ── resuml_ats_explain ──────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_ats_explain',\n {\n title: 'ATS Rubric Explain',\n description:\n 'Return the rubric entry (tier, weight, evidence level, description, source) for a given check id.',\n inputSchema: {\n checkId: z.string().describe('Check id, e.g. quantification-density'),\n },\n },\n ({ checkId }) => {\n const entry = getRubricEntry(checkId);\n if (!entry) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: `Unknown check id: ${checkId}` }),\n },\n ],\n isError: true,\n };\n }\n return { content: [{ type: 'text' as const, text: JSON.stringify(entry, null, 2) }] };\n }\n );\n\n // ── resuml_render ───────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_render',\n {\n title: 'Render Resume',\n description: 'Render a resume to HTML using a specified theme',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n theme: z\n .string()\n .default('even')\n .describe('Theme name (e.g. even, stackoverflow, elegant, paper, kendall)'),\n locale: z.string().optional().describe('Locale for theme rendering (e.g. en, de)'),\n },\n },\n async (args) => {\n const { yaml, theme } = args;\n const locale = args['locale'];\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const themeModule = loadTheme(theme, { autoInstall: false });\n const renderOptions: Record<string, unknown> = {};\n if (locale) renderOptions['locale'] = locale;\n const html = await themeModule.render(resume, renderOptions);\n restoreStdout();\n return { content: [{ type: 'text' as const, text: html }] };\n } catch (e: unknown) {\n restoreStdout();\n const message = e instanceof Error ? e.message : String(e);\n const hint = message.includes('Cannot find module')\n ? `. Install with: resuml themes --install ${theme}`\n : '';\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: message + hint }) }],\n isError: true,\n };\n }\n }\n );\n\n // ── resuml_list_themes ──────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_list_themes',\n {\n title: 'List Themes',\n description: 'List available resume themes with their installation status',\n },\n () => {\n const themes = KNOWN_THEMES.map((t) => ({\n name: t.name,\n package: t.pkg,\n description: t.description,\n installed: isThemeInstalled(t.pkg),\n version: getInstalledVersion(t.pkg),\n }));\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ themes }, null, 2) }],\n };\n }\n );\n\n // ── resuml_export_pdf ───────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_export_pdf',\n {\n title: 'Export PDF',\n description: 'Export a resume as PDF (requires Playwright to be installed)',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n theme: z.string().default('even').describe('Theme name'),\n format: z.enum(['A4', 'Letter']).default('A4').describe('Paper format'),\n locale: z.string().optional().describe('Locale for theme rendering (e.g. en, de)'),\n margin: z\n .string()\n .optional()\n .describe(\n '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'\n ),\n },\n },\n async (args) => {\n const { yaml, theme, format } = args;\n const locale = args['locale'];\n const margin = args['margin'];\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const themeModule = loadTheme(theme, { autoInstall: false });\n const renderOptions: Record<string, unknown> = {};\n if (locale) renderOptions['locale'] = locale;\n const html = await themeModule.render(resume, renderOptions);\n\n let chromium;\n try {\n const pw = await import('playwright');\n chromium = pw.chromium;\n } catch {\n restoreStdout();\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Playwright is not installed. Run: npm install playwright',\n }),\n },\n ],\n isError: true,\n };\n }\n\n const parsedMargin = parseMargin(margin);\n const browser = await chromium.launch({ headless: true });\n const page = await browser.newPage();\n await page.setContent(html, { waitUntil: 'networkidle' });\n const pdfBuffer = await page.pdf({\n format,\n printBackground: true,\n margin: parsedMargin,\n });\n await browser.close();\n restoreStdout();\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n pdf: Buffer.from(pdfBuffer).toString('base64'),\n encoding: 'base64',\n format,\n }),\n },\n ],\n };\n } catch (e: unknown) {\n restoreStdout();\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }),\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n return server;\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 startMcpServer(): Promise<void> {\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/mcp/server.ts"],"sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { z } from 'zod';\nimport { processResumeData } from '../core';\nimport { analyzeAts } from '../ats/index';\nimport { listRubricMarkdown, getRubricEntry } from '../ats/rubric';\nimport { loadConfig } from '../utils/config';\nimport { loadTheme } from '../utils/themeLoader';\nimport { generateResumeYaml } from '../utils/resumeTemplate';\nimport { KNOWN_THEMES, isThemeInstalled, getInstalledVersion } from '../utils/themeInfo';\n\n// Redirect console.log to stderr so it doesn't corrupt the MCP stdio channel\nconst originalLog = console.log;\nconst originalWarn = console.warn;\n\nfunction suppressStdout() {\n console.log = (...args: unknown[]) => {\n console.error('[resuml]', ...args);\n };\n console.warn = (...args: unknown[]) => {\n console.error('[resuml]', ...args);\n };\n}\n\nfunction restoreStdout() {\n console.log = originalLog;\n console.warn = originalWarn;\n}\n\n// ── Shared data ─────────────────────────────────────────────────────\n\nconst JSON_RESUME_SCHEMA_REFERENCE = `# JSON Resume Schema Reference\n\nThe JSON Resume schema defines the structure for resume data. resuml uses YAML as the input format.\n\n## Top-level sections\n\n| Section | Required | Description |\n|---------|----------|-------------|\n| basics | Yes | Name, label, email, phone, url, summary, location, profiles |\n| work | Recommended | Work experience entries |\n| education | Recommended | Education entries |\n| skills | Recommended | Skill categories with keywords |\n| projects | Optional | Project entries |\n| volunteer | Optional | Volunteer experience |\n| awards | Optional | Awards and honors |\n| certificates | Optional | Professional certifications |\n| publications | Optional | Published works |\n| languages | Optional | Language proficiencies |\n| interests | Optional | Personal interests |\n| references | Optional | Professional references |\n\n## Section schemas\n\n### basics\n\\`\\`\\`yaml\nbasics:\n name: \"Full Name\" # required\n label: \"Professional Title\"\n email: \"email@example.com\"\n phone: \"+1-555-123-4567\"\n url: \"https://website.com\"\n summary: \"2-4 sentence professional summary\"\n location:\n city: \"City\"\n countryCode: \"US\"\n region: \"State\"\n profiles:\n - network: \"LinkedIn\"\n username: \"username\"\n url: \"https://linkedin.com/in/username\"\n\\`\\`\\`\n\n### work\n\\`\\`\\`yaml\nwork:\n - name: \"Company Name\"\n position: \"Job Title\"\n url: \"https://company.com\"\n startDate: \"2020-01-01\" # ISO 8601\n endDate: \"2023-12-31\" # omit for current position\n summary: \"Role description\"\n highlights:\n - \"Achievement with measurable result\"\n\\`\\`\\`\n\n### education\n\\`\\`\\`yaml\neducation:\n - institution: \"University\"\n area: \"Field of Study\"\n studyType: \"Degree Type\" # e.g. Bachelor, Master, PhD\n startDate: \"2014-09-01\"\n endDate: \"2018-06-01\"\n\\`\\`\\`\n\n### skills\n\\`\\`\\`yaml\nskills:\n - name: \"Category\"\n level: \"Expert\" # Master, Expert, Advanced, Intermediate, Beginner\n keywords: [\"Skill1\", \"Skill2\"]\n\\`\\`\\`\n\n### projects\n\\`\\`\\`yaml\nprojects:\n - name: \"Project Name\"\n description: \"What it does\"\n highlights: [\"Key achievement\"]\n keywords: [\"Tech1\", \"Tech2\"]\n startDate: \"2023-01-01\"\n url: \"https://github.com/...\"\n\\`\\`\\`\n\n### certificates\n\\`\\`\\`yaml\ncertificates:\n - name: \"Certificate Name\"\n date: \"2023-01-01\"\n issuer: \"Issuing Organization\"\n url: \"https://credential-url.com\"\n\\`\\`\\`\n\n### languages\n\\`\\`\\`yaml\nlanguages:\n - language: \"English\"\n fluency: \"Native speaker\" # Native speaker, Fluent, Advanced, Intermediate, Elementary\n\\`\\`\\`\n\n## Formatting rules\n- Dates: ISO 8601 format (YYYY-MM-DD or YYYY-MM)\n- Start highlights with action verbs: Developed, Implemented, Led, Optimized, Reduced, Built, Designed\n- Include numbers in 50%+ of highlights (e.g., \"Reduced latency by 40%\")\n- Never use first person (I, my, me, we)\n- Summary: 2-4 sentences positioning the candidate for the specific role\n`;\n\nfunction createServer(): McpServer {\n const server = new McpServer({\n name: 'resuml',\n version: '2.0.0',\n });\n\n // ═══════════════════════════════════════════════════════════════════\n // RESOURCES\n // ═══════════════════════════════════════════════════════════════════\n\n // ── JSON Resume Schema Reference ──────────────────────────────────\n\n server.registerResource(\n 'json-resume-schema',\n 'resuml://schema/json-resume',\n {\n description:\n 'JSON Resume schema reference with all sections, field types, and formatting rules',\n mimeType: 'text/markdown',\n },\n () => ({\n contents: [\n {\n uri: 'resuml://schema/json-resume',\n mimeType: 'text/markdown',\n text: JSON_RESUME_SCHEMA_REFERENCE,\n },\n ],\n })\n );\n\n // ── ATS Rubric ───────────────────────────────────────────────────\n\n server.registerResource(\n 'ats-rubric',\n 'resuml://docs/ats-rubric',\n {\n description:\n 'Tiered ATS rubric: every check, its tier, weight, evidence level, description, and source URL.',\n mimeType: 'text/markdown',\n },\n () => ({\n contents: [\n {\n uri: 'resuml://docs/ats-rubric',\n mimeType: 'text/markdown',\n text: listRubricMarkdown(),\n },\n ],\n })\n );\n\n // ── Theme Catalog ─────────────────────────────────────────────────\n\n server.registerResource(\n 'theme-catalog',\n 'resuml://themes/catalog',\n {\n description: 'Available resume themes with descriptions and installation status',\n mimeType: 'application/json',\n },\n () => {\n const themes = KNOWN_THEMES.map((t) => ({\n name: t.name,\n package: t.pkg,\n description: t.description,\n installed: isThemeInstalled(t.pkg),\n version: getInstalledVersion(t.pkg),\n }));\n return {\n contents: [\n {\n uri: 'resuml://themes/catalog',\n mimeType: 'application/json',\n text: JSON.stringify({ themes, totalCount: themes.length }, null, 2),\n },\n ],\n };\n }\n );\n\n // ═══════════════════════════════════════════════════════════════════\n // PROMPTS\n // ═══════════════════════════════════════════════════════════════════\n\n // ── Tailor Resume to Job Description ──────────────────────────────\n\n server.registerPrompt(\n 'tailor-resume-to-jd',\n {\n title: 'Tailor Resume to Job Description',\n description: 'Generate a tailored resume YAML optimized for a specific job description',\n argsSchema: {\n jobDescription: z.string().describe('The full job description text'),\n candidateName: z.string().optional().describe('Candidate full name'),\n candidateEmail: z.string().optional().describe('Candidate email address'),\n candidateBackground: z\n .string()\n .optional()\n .describe(\n 'Brief summary of the candidate background, skills, and experience to incorporate'\n ),\n },\n },\n ({ jobDescription, candidateName, candidateEmail, candidateBackground }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Create a tailored resume in YAML format optimized for the following job description.\n\n## Job Description\n${jobDescription}\n\n${candidateBackground ? `## Candidate Background\\n${candidateBackground}\\n` : ''}\n## Instructions\n\n1. Analyze the job description to identify required skills, technologies, experience level, and industry terms\n2. Generate a complete resume YAML following the JSON Resume schema (read the resuml://schema/json-resume resource for the full schema)\n3. Mirror exact terminology from the job description in skills and highlights\n4. Start every highlight with an action verb (Developed, Implemented, Led, Optimized, Reduced, Built, Designed)\n5. Include numbers in 50%+ of highlights (e.g., \"Reduced latency by 40%\", \"Managed team of 8\")\n6. Never use \"I\", \"my\", \"me\", \"we\"\n7. Write a 2-4 sentence summary positioning the candidate for this specific role\n8. Use ISO 8601 dates (YYYY-MM-DD or YYYY-MM)\n${candidateName ? `9. Use candidate name: ${candidateName}` : ''}\n${candidateEmail ? `10. Use candidate email: ${candidateEmail}` : ''}\n\n## Workflow\nAfter generating the YAML:\n1. Use \\`resuml_validate\\` to check schema compliance\n2. Use \\`resuml_ats_check\\` with the job description text. Target: total >= 75, parsing tier grade A, hard-skill-overlap >= 70%\n3. If ATS score is low, revise the YAML and re-check\n4. Use \\`resuml_render\\` with theme \"even\" for the final output\n\nOutput the resume YAML first, then run the validation and ATS check tools.`,\n },\n },\n ],\n })\n );\n\n // ── Optimize ATS Score ────────────────────────────────────────────\n\n server.registerPrompt(\n 'optimize-ats-score',\n {\n title: 'Optimize ATS Score',\n description: 'Analyze and improve an existing resume YAML to maximize its ATS score',\n argsSchema: {\n resumeYaml: z.string().describe('The current resume YAML content'),\n jobDescription: z\n .string()\n .optional()\n .describe('Optional job description to optimize against'),\n targetScore: z.string().optional().describe('Target ATS score (default: 85)'),\n },\n },\n ({ resumeYaml, jobDescription, targetScore }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Optimize this resume YAML to maximize its ATS score${targetScore ? ` (target: ${targetScore})` : ' (target: 85+)'}.\n\n## Current Resume YAML\n\\`\\`\\`yaml\n${resumeYaml}\n\\`\\`\\`\n\n${jobDescription ? `## Job Description\\n${jobDescription}\\n` : ''}\n## Instructions\n\n1. First, run \\`resuml_ats_check\\` on the current YAML${jobDescription ? ' with the job description' : ''} to get the baseline score\n2. Read the ATS rubric (resuml://docs/ats-rubric) to understand what checks are performed, their tier, and weight\n3. Review each failed or low-scoring check and fix the issues:\n - Missing contact info → add it\n - No summary → write a 2-4 sentence professional summary\n - Weak highlights → rewrite with action verbs and quantified metrics\n - Missing keywords → incorporate them naturally into skills and highlights\n - Structural issues → ensure all essential sections are present\n4. Run \\`resuml_ats_check\\` again to verify improvement\n5. Repeat until the target score is reached\n\nOutput the improved YAML with a summary of changes made.`,\n },\n },\n ],\n })\n );\n\n // ── Review Resume ─────────────────────────────────────────────────\n\n server.registerPrompt(\n 'review-resume',\n {\n title: 'Review Resume',\n description:\n 'Comprehensive review of a resume YAML with ATS analysis and improvement suggestions',\n argsSchema: {\n resumeYaml: z.string().describe('The resume YAML content to review'),\n },\n },\n ({ resumeYaml }) => ({\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text: `Perform a comprehensive review of this resume.\n\n## Resume YAML\n\\`\\`\\`yaml\n${resumeYaml}\n\\`\\`\\`\n\n## Review steps\n\n1. Run \\`resuml_validate\\` to check schema compliance\n2. Run \\`resuml_ats_check\\` for ATS analysis\n3. Review content quality:\n - Is the summary compelling and role-specific?\n - Do highlights use strong action verbs?\n - Are achievements quantified with metrics?\n - Are skills well-organized and comprehensive?\n - Is the work history clear and impactful?\n4. Provide a structured review with:\n - **ATS Score**: Current score and rating\n - **Schema Issues**: Any validation errors\n - **Strengths**: What the resume does well\n - **Improvements**: Specific, actionable suggestions\n - **Revised YAML**: An improved version if significant changes are recommended`,\n },\n },\n ],\n })\n );\n\n // ═══════════════════════════════════════════════════════════════════\n // TOOLS\n // ═══════════════════════════════════════════════════════════════════\n\n // ── resuml_init_resume ──────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_init_resume',\n {\n title: 'Init Resume',\n description: 'Generate a starter resume YAML template following the JSON Resume schema',\n inputSchema: {\n name: z.string().optional().describe('Full name for the resume'),\n title: z.string().optional().describe('Professional title/label'),\n email: z.string().optional().describe('Email address'),\n },\n },\n ({ name, title, email }) => {\n const yaml = generateResumeYaml(\n name ?? 'Your Name',\n email ?? 'email@example.com',\n title ?? 'Professional Title'\n );\n return { content: [{ type: 'text' as const, text: yaml }] };\n }\n );\n\n // ── resuml_validate ─────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_validate',\n {\n title: 'Validate Resume',\n description: 'Validate resume YAML against the JSON Resume schema',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n },\n },\n async ({ yaml }) => {\n suppressStdout();\n try {\n await processResumeData([yaml]);\n restoreStdout();\n return {\n content: [\n { type: 'text' as const, text: JSON.stringify({ valid: true, errors: [] }, null, 2) },\n ],\n };\n } catch (e: unknown) {\n restoreStdout();\n const message = e instanceof Error ? e.message : String(e);\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ valid: false, errors: [message] }, null, 2),\n },\n ],\n };\n }\n }\n );\n\n // ── resuml_ats_check ────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_ats_check',\n {\n title: 'ATS Check',\n description:\n 'Run ATS (Applicant Tracking System) analysis on a resume, optionally matching against a job description',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n jobDescription: z\n .string()\n .optional()\n .describe('Job description text to match keywords against'),\n language: z.enum(['en', 'de']).optional().describe('Language for analysis (default: en)'),\n },\n },\n async ({ yaml, jobDescription, language }) => {\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const cfg = loadConfig();\n const result = analyzeAts(resume, {\n language: language ?? cfg.locale,\n jobDescription,\n config: cfg,\n });\n restoreStdout();\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n };\n } catch (e: unknown) {\n restoreStdout();\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }),\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n // ── resuml_ats_explain ──────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_ats_explain',\n {\n title: 'ATS Rubric Explain',\n description:\n 'Return the rubric entry (tier, weight, evidence level, description, source) for a given check id.',\n inputSchema: {\n checkId: z.string().describe('Check id, e.g. quantification-density'),\n },\n },\n ({ checkId }) => {\n const entry = getRubricEntry(checkId);\n if (!entry) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: `Unknown check id: ${checkId}` }),\n },\n ],\n isError: true,\n };\n }\n return { content: [{ type: 'text' as const, text: JSON.stringify(entry, null, 2) }] };\n }\n );\n\n // ── resuml_render ───────────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_render',\n {\n title: 'Render Resume',\n description: 'Render a resume to HTML using a specified theme',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n theme: z\n .string()\n .default('even')\n .describe('Theme name (e.g. even, stackoverflow, elegant, paper, kendall)'),\n locale: z.string().optional().describe('Locale for theme rendering (e.g. en, de)'),\n },\n },\n async (args) => {\n const { yaml, theme } = args;\n const locale = args['locale'];\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const themeModule = loadTheme(theme, { autoInstall: false });\n const renderOptions: Record<string, unknown> = {};\n if (locale) renderOptions['locale'] = locale;\n const html = await themeModule.render(resume, renderOptions);\n restoreStdout();\n return { content: [{ type: 'text' as const, text: html }] };\n } catch (e: unknown) {\n restoreStdout();\n const message = e instanceof Error ? e.message : String(e);\n const hint = message.includes('Cannot find module')\n ? `. Install with: resuml themes --install ${theme}`\n : '';\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ error: message + hint }) }],\n isError: true,\n };\n }\n }\n );\n\n // ── resuml_list_themes ──────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_list_themes',\n {\n title: 'List Themes',\n description: 'List available resume themes with their installation status',\n },\n () => {\n const themes = KNOWN_THEMES.map((t) => ({\n name: t.name,\n package: t.pkg,\n description: t.description,\n installed: isThemeInstalled(t.pkg),\n version: getInstalledVersion(t.pkg),\n }));\n return {\n content: [{ type: 'text' as const, text: JSON.stringify({ themes }, null, 2) }],\n };\n }\n );\n\n // ── resuml_export_pdf ───────────────────────────────────────────────\n\n server.registerTool(\n 'resuml_export_pdf',\n {\n title: 'Export PDF',\n description: 'Export a resume as PDF (requires Playwright to be installed)',\n inputSchema: {\n yaml: z.string().describe('Resume content in YAML format'),\n theme: z.string().default('even').describe('Theme name'),\n format: z.enum(['A4', 'Letter']).default('A4').describe('Paper format'),\n locale: z.string().optional().describe('Locale for theme rendering (e.g. en, de)'),\n margin: z\n .string()\n .optional()\n .describe(\n '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'\n ),\n },\n },\n async (args) => {\n const { yaml, theme, format } = args;\n const locale = args['locale'];\n const margin = args['margin'];\n suppressStdout();\n try {\n const resume = await processResumeData([yaml]);\n const themeModule = loadTheme(theme, { autoInstall: false });\n const renderOptions: Record<string, unknown> = {};\n if (locale) renderOptions['locale'] = locale;\n const html = await themeModule.render(resume, renderOptions);\n\n let chromium;\n try {\n const pw = await import('playwright');\n chromium = pw.chromium;\n } catch {\n restoreStdout();\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: 'Playwright is not installed. Run: npm install playwright',\n }),\n },\n ],\n isError: true,\n };\n }\n\n const parsedMargin = parseMargin(margin);\n const browser = await chromium.launch({ headless: true });\n const page = await browser.newPage();\n await page.setContent(html, { waitUntil: 'networkidle' });\n const pdfBuffer = await page.pdf({\n format,\n printBackground: true,\n margin: parsedMargin,\n });\n await browser.close();\n restoreStdout();\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n pdf: Buffer.from(pdfBuffer).toString('base64'),\n encoding: 'base64',\n format,\n }),\n },\n ],\n };\n } catch (e: unknown) {\n restoreStdout();\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: e instanceof Error ? e.message : String(e) }),\n },\n ],\n isError: true,\n };\n }\n }\n );\n\n return server;\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 startMcpServer(): Promise<void> {\n const server = createServer();\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,67 @@
1
+ type SkillType = 'language' | 'framework' | 'library' | 'tool' | 'platform' | 'database' | 'practice';
2
+ interface Skill {
3
+ id: string;
4
+ canonical: string;
5
+ aliases: string[];
6
+ type: SkillType;
7
+ hot: boolean;
8
+ sources?: string[];
9
+ }
10
+ interface SkillMatch {
11
+ skill: Skill;
12
+ occurrences: number;
13
+ }
14
+
15
+ /**
16
+ * Tokenize text for skill matching.
17
+ *
18
+ * Splits on whitespace and sentence punctuation, but keeps internal punctuation
19
+ * that's part of a skill name (e.g. "Node.js", "CI/CD", "shadcn/ui", "c#", "c++").
20
+ * Returns normalized lowercase tokens alongside their original form.
21
+ */
22
+ interface Token {
23
+ raw: string;
24
+ norm: string;
25
+ isAllUpper: boolean;
26
+ }
27
+ /**
28
+ * Linear-time tokenizer. Iterates once over the string, accumulating runs of
29
+ * token chars. Avoids regex alternation and unbounded quantifiers to keep
30
+ * worst-case complexity O(n) even on adversarial inputs.
31
+ */
32
+ declare function tokenize(text: string): Token[];
33
+ /**
34
+ * Indexed skill database for fast longest-match lookup.
35
+ *
36
+ * Strategy: group all skill phrases (canonical + every alias) by their first
37
+ * token. For each first token we get a list of candidate phrases, sorted
38
+ * longest-first so we find the most specific match first (e.g. "React Query"
39
+ * beats "React" when the text contains both words adjacent).
40
+ */
41
+ declare class SkillIndex {
42
+ private readonly skills;
43
+ private readonly byFirstToken;
44
+ private readonly maxPhraseLen;
45
+ constructor(skills: readonly Skill[]);
46
+ /**
47
+ * Scan text for skills. Longest-match wins at each position, e.g. "Next.js"
48
+ * doesn't also fire "Next", and "Google Cloud Platform" doesn't also fire
49
+ * "Google".
50
+ *
51
+ * Returns a list of unique skills with occurrence counts.
52
+ */
53
+ scan(text: string): SkillMatch[];
54
+ get size(): number;
55
+ get maxPhraseTokens(): number;
56
+ }
57
+
58
+ /**
59
+ * Bundled ATS skill taxonomy: ~1.9k skills from O*NET (US DOL, public domain)
60
+ * plus a curated emerging-tech allowlist. Used for keyword extraction from
61
+ * job descriptions and resumes.
62
+ */
63
+ declare function getSkillIndex(): SkillIndex;
64
+ /** Reset for tests. */
65
+ declare function _resetSkillIndexCache(): void;
66
+
67
+ export { type Skill, SkillIndex, type SkillMatch, type SkillType, _resetSkillIndexCache, getSkillIndex, tokenize };
@@ -0,0 +1,13 @@
1
+ import {
2
+ SkillIndex,
3
+ _resetSkillIndexCache,
4
+ getSkillIndex,
5
+ tokenize
6
+ } from "../chunk-QR77BRMN.js";
7
+ export {
8
+ SkillIndex,
9
+ _resetSkillIndexCache,
10
+ getSkillIndex,
11
+ tokenize
12
+ };
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}