resuml 1.8.3 → 1.9.1

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/README.md CHANGED
@@ -301,6 +301,75 @@ console.log(`Matched keywords: ${jdResult.keywords?.matched.join(', ')}`);
301
301
 
302
302
  See the CLI and API for more details.
303
303
 
304
+ ## AI Agent Integration (MCP)
305
+
306
+ Resuml includes a built-in [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server, making it the first resume tool with native AI agent support. Use it with Claude, GitHub Copilot, Cursor, or any MCP-compatible client.
307
+
308
+ ### Setup
309
+
310
+ Add to your Claude Desktop or MCP client config:
311
+
312
+ ```json
313
+ {
314
+ "mcpServers": {
315
+ "resuml": {
316
+ "command": "npx",
317
+ "args": ["resuml", "mcp"]
318
+ }
319
+ }
320
+ }
321
+ ```
322
+
323
+ Or run directly:
324
+
325
+ ```bash
326
+ resuml mcp
327
+ ```
328
+
329
+ ### Available Tools
330
+
331
+ | Tool | Purpose |
332
+ |------|---------|
333
+ | `resuml_init_resume` | Generate a starter YAML template |
334
+ | `resuml_validate` | Validate resume YAML against JSON Resume schema |
335
+ | `resuml_ats_check` | ATS analysis + job description keyword matching |
336
+ | `resuml_render` | Render resume to HTML using a theme |
337
+ | `resuml_list_themes` | List available themes and install status |
338
+ | `resuml_export_pdf` | Export resume as PDF (requires Playwright) |
339
+
340
+ ### Resources
341
+
342
+ The MCP server exposes structured data agents can read:
343
+
344
+ | Resource | URI | Description |
345
+ |----------|-----|-------------|
346
+ | JSON Resume Schema | `resuml://schema/json-resume` | Full schema reference with all sections and field types |
347
+ | ATS Scoring Rubric | `resuml://docs/ats-scoring` | Scoring system, checks performed, and optimization tips |
348
+ | Theme Catalog | `resuml://themes/catalog` | Available themes with descriptions and install status |
349
+
350
+ ### Prompts
351
+
352
+ Pre-built workflows for common tasks:
353
+
354
+ | Prompt | Description |
355
+ |--------|-------------|
356
+ | `tailor-resume-to-jd` | Generate a tailored resume optimized for a specific job description |
357
+ | `optimize-ats-score` | Analyze and improve an existing resume to maximize ATS score |
358
+ | `review-resume` | Comprehensive review with ATS analysis and improvement suggestions |
359
+
360
+ ### Example Workflow
361
+
362
+ Ask your AI assistant:
363
+
364
+ > "Use the tailor-resume-to-jd prompt to create a resume for this job posting: [paste JD]. My name is Jane Smith, jane@example.com. I have 5 years of experience in TypeScript and React."
365
+
366
+ The agent will:
367
+ 1. Generate tailored resume YAML matching the job description keywords
368
+ 2. Validate it against the JSON Resume schema
369
+ 3. Run ATS analysis targeting score ≥ 75
370
+ 4. Iterate until the score meets the target
371
+ 5. Render the final result with a professional theme
372
+
304
373
  ## Troubleshooting
305
374
 
306
375
  ### Common Issues
package/dist/index.cjs CHANGED
@@ -1558,6 +1558,195 @@ function createServer() {
1558
1558
  name: "resuml",
1559
1559
  version: "1.0.0"
1560
1560
  });
1561
+ server.registerResource(
1562
+ "json-resume-schema",
1563
+ "resuml://schema/json-resume",
1564
+ {
1565
+ description: "JSON Resume schema reference with all sections, field types, and formatting rules",
1566
+ mimeType: "text/markdown"
1567
+ },
1568
+ () => ({
1569
+ contents: [{
1570
+ uri: "resuml://schema/json-resume",
1571
+ mimeType: "text/markdown",
1572
+ text: JSON_RESUME_SCHEMA_REFERENCE
1573
+ }]
1574
+ })
1575
+ );
1576
+ server.registerResource(
1577
+ "ats-scoring-rubric",
1578
+ "resuml://docs/ats-scoring",
1579
+ {
1580
+ description: "ATS scoring rubric: checks performed, weight system, rating scale, and tips for improving score",
1581
+ mimeType: "text/markdown"
1582
+ },
1583
+ () => ({
1584
+ contents: [{
1585
+ uri: "resuml://docs/ats-scoring",
1586
+ mimeType: "text/markdown",
1587
+ text: ATS_SCORING_RUBRIC
1588
+ }]
1589
+ })
1590
+ );
1591
+ server.registerResource(
1592
+ "theme-catalog",
1593
+ "resuml://themes/catalog",
1594
+ {
1595
+ description: "Available resume themes with descriptions and installation status",
1596
+ mimeType: "application/json"
1597
+ },
1598
+ () => {
1599
+ const themes = KNOWN_THEMES.map((t) => ({
1600
+ name: t.name,
1601
+ package: t.pkg,
1602
+ description: t.description,
1603
+ installed: isThemeInstalled(t.pkg),
1604
+ version: getInstalledVersion(t.pkg)
1605
+ }));
1606
+ return {
1607
+ contents: [{
1608
+ uri: "resuml://themes/catalog",
1609
+ mimeType: "application/json",
1610
+ text: JSON.stringify({ themes, totalCount: themes.length }, null, 2)
1611
+ }]
1612
+ };
1613
+ }
1614
+ );
1615
+ server.registerPrompt(
1616
+ "tailor-resume-to-jd",
1617
+ {
1618
+ title: "Tailor Resume to Job Description",
1619
+ description: "Generate a tailored resume YAML optimized for a specific job description",
1620
+ argsSchema: {
1621
+ jobDescription: import_zod.z.string().describe("The full job description text"),
1622
+ candidateName: import_zod.z.string().optional().describe("Candidate full name"),
1623
+ candidateEmail: import_zod.z.string().optional().describe("Candidate email address"),
1624
+ candidateBackground: import_zod.z.string().optional().describe("Brief summary of the candidate background, skills, and experience to incorporate")
1625
+ }
1626
+ },
1627
+ ({ jobDescription, candidateName, candidateEmail, candidateBackground }) => ({
1628
+ messages: [{
1629
+ role: "user",
1630
+ content: {
1631
+ type: "text",
1632
+ text: `Create a tailored resume in YAML format optimized for the following job description.
1633
+
1634
+ ## Job Description
1635
+ ${jobDescription}
1636
+
1637
+ ${candidateBackground ? `## Candidate Background
1638
+ ${candidateBackground}
1639
+ ` : ""}
1640
+ ## Instructions
1641
+
1642
+ 1. Analyze the job description to identify required skills, technologies, experience level, and industry terms
1643
+ 2. Generate a complete resume YAML following the JSON Resume schema (read the resuml://schema/json-resume resource for the full schema)
1644
+ 3. Mirror exact terminology from the job description in skills and highlights
1645
+ 4. Start every highlight with an action verb (Developed, Implemented, Led, Optimized, Reduced, Built, Designed)
1646
+ 5. Include numbers in 50%+ of highlights (e.g., "Reduced latency by 40%", "Managed team of 8")
1647
+ 6. Never use "I", "my", "me", "we"
1648
+ 7. Write a 2-4 sentence summary positioning the candidate for this specific role
1649
+ 8. Use ISO 8601 dates (YYYY-MM-DD or YYYY-MM)
1650
+ ${candidateName ? `9. Use candidate name: ${candidateName}` : ""}
1651
+ ${candidateEmail ? `10. Use candidate email: ${candidateEmail}` : ""}
1652
+
1653
+ ## Workflow
1654
+ After generating the YAML:
1655
+ 1. Use \`resuml_validate\` to check schema compliance
1656
+ 2. Use \`resuml_ats_check\` with the job description text. Target: score >= 75, keyword match >= 70%
1657
+ 3. If ATS score is low, revise the YAML and re-check
1658
+ 4. Use \`resuml_render\` with theme "even" for the final output
1659
+
1660
+ Output the resume YAML first, then run the validation and ATS check tools.`
1661
+ }
1662
+ }]
1663
+ })
1664
+ );
1665
+ server.registerPrompt(
1666
+ "optimize-ats-score",
1667
+ {
1668
+ title: "Optimize ATS Score",
1669
+ description: "Analyze and improve an existing resume YAML to maximize its ATS score",
1670
+ argsSchema: {
1671
+ resumeYaml: import_zod.z.string().describe("The current resume YAML content"),
1672
+ jobDescription: import_zod.z.string().optional().describe("Optional job description to optimize against"),
1673
+ targetScore: import_zod.z.string().optional().describe("Target ATS score (default: 85)")
1674
+ }
1675
+ },
1676
+ ({ resumeYaml, jobDescription, targetScore }) => ({
1677
+ messages: [{
1678
+ role: "user",
1679
+ content: {
1680
+ type: "text",
1681
+ text: `Optimize this resume YAML to maximize its ATS score${targetScore ? ` (target: ${targetScore})` : " (target: 85+)"}.
1682
+
1683
+ ## Current Resume YAML
1684
+ \`\`\`yaml
1685
+ ${resumeYaml}
1686
+ \`\`\`
1687
+
1688
+ ${jobDescription ? `## Job Description
1689
+ ${jobDescription}
1690
+ ` : ""}
1691
+ ## Instructions
1692
+
1693
+ 1. First, run \`resuml_ats_check\` on the current YAML${jobDescription ? " with the job description" : ""} to get the baseline score
1694
+ 2. Read the ATS scoring rubric (resuml://docs/ats-scoring) to understand what checks are performed
1695
+ 3. Review each failed or low-scoring check and fix the issues:
1696
+ - Missing contact info \u2192 add it
1697
+ - No summary \u2192 write a 2-4 sentence professional summary
1698
+ - Weak highlights \u2192 rewrite with action verbs and quantified metrics
1699
+ - Missing keywords \u2192 incorporate them naturally into skills and highlights
1700
+ - Structural issues \u2192 ensure all essential sections are present
1701
+ 4. Run \`resuml_ats_check\` again to verify improvement
1702
+ 5. Repeat until the target score is reached
1703
+
1704
+ Output the improved YAML with a summary of changes made.`
1705
+ }
1706
+ }]
1707
+ })
1708
+ );
1709
+ server.registerPrompt(
1710
+ "review-resume",
1711
+ {
1712
+ title: "Review Resume",
1713
+ description: "Comprehensive review of a resume YAML with ATS analysis and improvement suggestions",
1714
+ argsSchema: {
1715
+ resumeYaml: import_zod.z.string().describe("The resume YAML content to review")
1716
+ }
1717
+ },
1718
+ ({ resumeYaml }) => ({
1719
+ messages: [{
1720
+ role: "user",
1721
+ content: {
1722
+ type: "text",
1723
+ text: `Perform a comprehensive review of this resume.
1724
+
1725
+ ## Resume YAML
1726
+ \`\`\`yaml
1727
+ ${resumeYaml}
1728
+ \`\`\`
1729
+
1730
+ ## Review steps
1731
+
1732
+ 1. Run \`resuml_validate\` to check schema compliance
1733
+ 2. Run \`resuml_ats_check\` for ATS analysis
1734
+ 3. Review content quality:
1735
+ - Is the summary compelling and role-specific?
1736
+ - Do highlights use strong action verbs?
1737
+ - Are achievements quantified with metrics?
1738
+ - Are skills well-organized and comprehensive?
1739
+ - Is the work history clear and impactful?
1740
+ 4. Provide a structured review with:
1741
+ - **ATS Score**: Current score and rating
1742
+ - **Schema Issues**: Any validation errors
1743
+ - **Strengths**: What the resume does well
1744
+ - **Improvements**: Specific, actionable suggestions
1745
+ - **Revised YAML**: An improved version if significant changes are recommended`
1746
+ }
1747
+ }]
1748
+ })
1749
+ );
1561
1750
  server.registerTool(
1562
1751
  "resuml_init_resume",
1563
1752
  {
@@ -1643,15 +1832,20 @@ function createServer() {
1643
1832
  description: "Render a resume to HTML using a specified theme",
1644
1833
  inputSchema: {
1645
1834
  yaml: import_zod.z.string().describe("Resume content in YAML format"),
1646
- theme: import_zod.z.string().default("even").describe("Theme name (e.g. even, stackoverflow, elegant, paper, kendall)")
1835
+ theme: import_zod.z.string().default("even").describe("Theme name (e.g. even, stackoverflow, elegant, paper, kendall)"),
1836
+ locale: import_zod.z.string().optional().describe("Locale for theme rendering (e.g. en, de)")
1647
1837
  }
1648
1838
  },
1649
- async ({ yaml, theme }) => {
1839
+ async (args) => {
1840
+ const { yaml, theme } = args;
1841
+ const locale = args["locale"];
1650
1842
  suppressStdout();
1651
1843
  try {
1652
1844
  const resume = await processResumeData([yaml]);
1653
1845
  const themeModule = loadTheme(theme, { autoInstall: false });
1654
- const html = await themeModule.render(resume);
1846
+ const renderOptions = {};
1847
+ if (locale) renderOptions["locale"] = locale;
1848
+ const html = await themeModule.render(resume, renderOptions);
1655
1849
  restoreStdout();
1656
1850
  return { content: [{ type: "text", text: html }] };
1657
1851
  } catch (e) {
@@ -1692,15 +1886,22 @@ function createServer() {
1692
1886
  inputSchema: {
1693
1887
  yaml: import_zod.z.string().describe("Resume content in YAML format"),
1694
1888
  theme: import_zod.z.string().default("even").describe("Theme name"),
1695
- format: import_zod.z.enum(["A4", "Letter"]).default("A4").describe("Paper format")
1889
+ format: import_zod.z.enum(["A4", "Letter"]).default("A4").describe("Paper format"),
1890
+ locale: import_zod.z.string().optional().describe("Locale for theme rendering (e.g. en, de)"),
1891
+ margin: import_zod.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')
1696
1892
  }
1697
1893
  },
1698
- async ({ yaml, theme, format }) => {
1894
+ async (args) => {
1895
+ const { yaml, theme, format } = args;
1896
+ const locale = args["locale"];
1897
+ const margin = args["margin"];
1699
1898
  suppressStdout();
1700
1899
  try {
1701
1900
  const resume = await processResumeData([yaml]);
1702
1901
  const themeModule = loadTheme(theme, { autoInstall: false });
1703
- const html = await themeModule.render(resume);
1902
+ const renderOptions = {};
1903
+ if (locale) renderOptions["locale"] = locale;
1904
+ const html = await themeModule.render(resume, renderOptions);
1704
1905
  let chromium;
1705
1906
  try {
1706
1907
  const pw = await import("playwright");
@@ -1712,14 +1913,14 @@ function createServer() {
1712
1913
  isError: true
1713
1914
  };
1714
1915
  }
1916
+ const parsedMargin = parseMargin2(margin);
1715
1917
  const browser = await chromium.launch({ headless: true });
1716
1918
  const page = await browser.newPage();
1717
1919
  await page.setContent(html, { waitUntil: "networkidle" });
1718
- const margin = "10mm";
1719
1920
  const pdfBuffer = await page.pdf({
1720
1921
  format,
1721
1922
  printBackground: true,
1722
- margin: { top: margin, bottom: margin, left: margin, right: margin }
1923
+ margin: parsedMargin
1723
1924
  });
1724
1925
  await browser.close();
1725
1926
  restoreStdout();
@@ -1744,12 +1945,27 @@ function createServer() {
1744
1945
  );
1745
1946
  return server;
1746
1947
  }
1948
+ function parseMargin2(margin) {
1949
+ const defaultMargin = { top: "10mm", right: "10mm", bottom: "10mm", left: "10mm" };
1950
+ if (!margin) return defaultMargin;
1951
+ const parts = margin.split(",").map((s) => s.trim());
1952
+ if (parts.length === 1 && parts[0]) {
1953
+ return { top: parts[0], right: parts[0], bottom: parts[0], left: parts[0] };
1954
+ }
1955
+ if (parts.length === 2 && parts[0] && parts[1]) {
1956
+ return { top: parts[0], right: parts[1], bottom: parts[0], left: parts[1] };
1957
+ }
1958
+ if (parts.length === 4 && parts[0] && parts[1] && parts[2] && parts[3]) {
1959
+ return { top: parts[0], right: parts[1], bottom: parts[2], left: parts[3] };
1960
+ }
1961
+ return defaultMargin;
1962
+ }
1747
1963
  async function startMcpServer() {
1748
1964
  const server = createServer();
1749
1965
  const transport = new import_stdio.StdioServerTransport();
1750
1966
  await server.connect(transport);
1751
1967
  }
1752
- var import_mcp, import_stdio, import_zod, originalLog, originalWarn;
1968
+ var import_mcp, import_stdio, import_zod, originalLog, originalWarn, JSON_RESUME_SCHEMA_REFERENCE, ATS_SCORING_RUBRIC;
1753
1969
  var init_server = __esm({
1754
1970
  "src/mcp/server.ts"() {
1755
1971
  "use strict";
@@ -1764,6 +1980,179 @@ var init_server = __esm({
1764
1980
  init_themeInfo();
1765
1981
  originalLog = console.log;
1766
1982
  originalWarn = console.warn;
1983
+ JSON_RESUME_SCHEMA_REFERENCE = `# JSON Resume Schema Reference
1984
+
1985
+ The JSON Resume schema defines the structure for resume data. resuml uses YAML as the input format.
1986
+
1987
+ ## Top-level sections
1988
+
1989
+ | Section | Required | Description |
1990
+ |---------|----------|-------------|
1991
+ | basics | Yes | Name, label, email, phone, url, summary, location, profiles |
1992
+ | work | Recommended | Work experience entries |
1993
+ | education | Recommended | Education entries |
1994
+ | skills | Recommended | Skill categories with keywords |
1995
+ | projects | Optional | Project entries |
1996
+ | volunteer | Optional | Volunteer experience |
1997
+ | awards | Optional | Awards and honors |
1998
+ | certificates | Optional | Professional certifications |
1999
+ | publications | Optional | Published works |
2000
+ | languages | Optional | Language proficiencies |
2001
+ | interests | Optional | Personal interests |
2002
+ | references | Optional | Professional references |
2003
+
2004
+ ## Section schemas
2005
+
2006
+ ### basics
2007
+ \`\`\`yaml
2008
+ basics:
2009
+ name: "Full Name" # required
2010
+ label: "Professional Title"
2011
+ email: "email@example.com"
2012
+ phone: "+1-555-123-4567"
2013
+ url: "https://website.com"
2014
+ summary: "2-4 sentence professional summary"
2015
+ location:
2016
+ city: "City"
2017
+ countryCode: "US"
2018
+ region: "State"
2019
+ profiles:
2020
+ - network: "LinkedIn"
2021
+ username: "username"
2022
+ url: "https://linkedin.com/in/username"
2023
+ \`\`\`
2024
+
2025
+ ### work
2026
+ \`\`\`yaml
2027
+ work:
2028
+ - name: "Company Name"
2029
+ position: "Job Title"
2030
+ url: "https://company.com"
2031
+ startDate: "2020-01-01" # ISO 8601
2032
+ endDate: "2023-12-31" # omit for current position
2033
+ summary: "Role description"
2034
+ highlights:
2035
+ - "Achievement with measurable result"
2036
+ \`\`\`
2037
+
2038
+ ### education
2039
+ \`\`\`yaml
2040
+ education:
2041
+ - institution: "University"
2042
+ area: "Field of Study"
2043
+ studyType: "Degree Type" # e.g. Bachelor, Master, PhD
2044
+ startDate: "2014-09-01"
2045
+ endDate: "2018-06-01"
2046
+ \`\`\`
2047
+
2048
+ ### skills
2049
+ \`\`\`yaml
2050
+ skills:
2051
+ - name: "Category"
2052
+ level: "Expert" # Master, Expert, Advanced, Intermediate, Beginner
2053
+ keywords: ["Skill1", "Skill2"]
2054
+ \`\`\`
2055
+
2056
+ ### projects
2057
+ \`\`\`yaml
2058
+ projects:
2059
+ - name: "Project Name"
2060
+ description: "What it does"
2061
+ highlights: ["Key achievement"]
2062
+ keywords: ["Tech1", "Tech2"]
2063
+ startDate: "2023-01-01"
2064
+ url: "https://github.com/..."
2065
+ \`\`\`
2066
+
2067
+ ### certificates
2068
+ \`\`\`yaml
2069
+ certificates:
2070
+ - name: "Certificate Name"
2071
+ date: "2023-01-01"
2072
+ issuer: "Issuing Organization"
2073
+ url: "https://credential-url.com"
2074
+ \`\`\`
2075
+
2076
+ ### languages
2077
+ \`\`\`yaml
2078
+ languages:
2079
+ - language: "English"
2080
+ fluency: "Native speaker" # Native speaker, Fluent, Advanced, Intermediate, Elementary
2081
+ \`\`\`
2082
+
2083
+ ## Formatting rules
2084
+ - Dates: ISO 8601 format (YYYY-MM-DD or YYYY-MM)
2085
+ - Start highlights with action verbs: Developed, Implemented, Led, Optimized, Reduced, Built, Designed
2086
+ - Include numbers in 50%+ of highlights (e.g., "Reduced latency by 40%")
2087
+ - Never use first person (I, my, me, we)
2088
+ - Summary: 2-4 sentences positioning the candidate for the specific role
2089
+ `;
2090
+ ATS_SCORING_RUBRIC = `# ATS Scoring Rubric
2091
+
2092
+ resuml performs deterministic, offline ATS (Applicant Tracking System) analysis.
2093
+
2094
+ ## Scoring system
2095
+
2096
+ ### Rating scale
2097
+ | Score | Rating | Description |
2098
+ |-------|--------|-------------|
2099
+ | 90-100 | Excellent | Resume is well-optimized for ATS |
2100
+ | 75-89 | Good | Resume passes most ATS checks |
2101
+ | 60-74 | Needs Work | Several improvements recommended |
2102
+ | 0-59 | Poor | Significant issues found |
2103
+
2104
+ ### Weight system
2105
+ Each check has a weight that affects the final score:
2106
+ - **High weight (3x)**: Critical checks that significantly impact ATS parsing
2107
+ - **Medium weight (2x)**: Important but not critical checks
2108
+ - **Low weight (1x)**: Nice-to-have improvements
2109
+
2110
+ ### Combined scoring (with job description)
2111
+ When a job description is provided:
2112
+ - Generic checks: 60% of final score
2113
+ - Keyword match: 40% of final score
2114
+
2115
+ ## Checks performed
2116
+
2117
+ ### Contact Information (category: contact)
2118
+ | Check | Weight | What it verifies |
2119
+ |-------|--------|-----------------|
2120
+ | contact-complete | High | Name, email, phone, and city are all present |
2121
+ | has-linkedin | Medium | LinkedIn profile exists in profiles section |
2122
+
2123
+ ### Content Quality (category: content)
2124
+ | Check | Weight | What it verifies |
2125
+ |-------|--------|-----------------|
2126
+ | has-summary | High | Professional summary exists (15-100 words) |
2127
+ | work-highlights | High | Each work entry has at least 2 highlights |
2128
+ | action-verbs | Medium | Highlights start with action verbs |
2129
+ | quantified-impact | Medium | 50%+ of highlights include numbers/metrics |
2130
+ | no-first-person | Low | No first-person pronouns (I, my, me, we) |
2131
+
2132
+ ### Resume Structure (category: structure)
2133
+ | Check | Weight | What it verifies |
2134
+ |-------|--------|-----------------|
2135
+ | date-consistency | Medium | All dates are valid ISO 8601 format |
2136
+ | skills-populated | Medium | At least 3 skill categories defined |
2137
+ | education-complete | Medium | Education section has institution and area |
2138
+ | essential-sections | High | Work, education, and skills sections present |
2139
+
2140
+ ## Job description matching
2141
+ When a job description is provided, resuml extracts keywords using TF-based extraction
2142
+ and matches them against the resume using stem matching. Results include:
2143
+ - **matched**: Keywords found in the resume
2144
+ - **missing**: Keywords not found (add these to improve score)
2145
+ - **matchPercentage**: Percentage of JD keywords found in resume
2146
+
2147
+ ## Tips for improving ATS score
2148
+ 1. Include all contact information (name, email, phone, city)
2149
+ 2. Add a LinkedIn profile URL
2150
+ 3. Write a 2-4 sentence professional summary
2151
+ 4. Use action verbs to start each highlight
2152
+ 5. Quantify achievements with numbers (%, $, time saved, team size)
2153
+ 6. Include at least 3 skill categories with relevant keywords
2154
+ 7. When targeting a job, mirror exact terminology from the job description
2155
+ `;
1767
2156
  }
1768
2157
  });
1769
2158