Spaces:
Running
Running
| import contextlib | |
| import marimo | |
| __generated_with = '0.17.0' | |
| app = marimo.App() | |
| with app.setup: | |
| import subprocess | |
| import sys | |
| with contextlib.suppress(Exception): | |
| subprocess.run([sys.executable, '-m', 'pip', 'install', 'uv'], check=True) | |
| with contextlib.suppress(Exception): | |
| subprocess.run([sys.executable, '-m', 'uv', 'pip', 'install', '-r', 'requirements.txt'], check=True) | |
| def _(): | |
| import marimo as mo | |
| mo.md(""" | |
| # <center>Cicero Jobs</center> | |
| ## How to Use This App: | |
| 1. **Upload Your Resume**: Drop a PDF/DOC/DOCX/TXT file or paste your resume text | |
| 2. **Add Job Posting**: Paste the job description or provide the job posting URL | |
| 3. **Click Submit**: Wait while AI generates your materials (takes about 30-60 seconds) | |
| 4. **Review & Download**: Preview all documents and download as Word files | |
| ## What You'll Get: | |
| - **Tailored Resume**: Your resume optimized with keywords from the job posting | |
| - **Cover Letter**: Personalized 3-4 paragraph letter highlighting relevant experience | |
| - **Recruiter Message**: Professional 5-7 sentence outreach message for LinkedIn/email | |
| - **Application Tips**: 5-7 actionable tips specific to this job application | |
| """) | |
| return (mo,) | |
| def _(): | |
| from pydantic import BaseModel, Field | |
| from pydantic_ai import Agent | |
| return Agent, BaseModel, Field | |
| def create_job_application_prompt(resume: str, cover_letter: str, further_instructions: str | None = None) -> str: | |
| fi = further_instructions or '' | |
| return f""" | |
| <JobApplicationGenerationRequest> | |
| <Persona> | |
| You are an expert human resources professional and a resume and cover letter drafting specialist, with a deep focus on the in-house legal market. Your task is to generate a complete set of application materials based on the provided resume and job description. | |
| </Persona> | |
| <InputData> | |
| <Resume> | |
| {resume} | |
| </Resume> | |
| <JobOpening> | |
| {cover_letter} | |
| </JobOpening> | |
| <FurtherInstructions> | |
| {fi} | |
| </FurtherInstructions> | |
| </InputData> | |
| <OutputSpecification> | |
| Your final output must be a structured object with four distinct fields: `resume`, `cover_letter`, `letter_to_recruiter`, and `tips`. Follow the detailed instructions for each field below. | |
| </OutputSpecification> | |
| <CoreProcessingInstructions> | |
| 1. **Analyze and Synthesize:** Meticulously analyze the provided `<Resume>` and the `<JobOpening>`. Your primary goal is to tailor the candidate's materials to perfectly align with the requirements and keywords of the job description. | |
| 2. **Adhere to Writing Guidelines:** In all generated content, strictly apply the principles outlined in the `<WritingGuidelines>` section. This includes using appropriate tense, structuring bullet points effectively (PAR, STAR), incorporating action verbs, and maintaining a professional brand. | |
| 3. **Fact-Based Generation:** Do not invent skills or experiences. All generated content must be directly supported by the information in the provided `<Resume>`. If a skill is treated superficially, you may suggest enhancements or ask clarifying questions in the `tips` section. | |
| 4. **Keyword Integration:** Identify core competencies, role-specific skills, and industry-relevant keywords from the job posting. Systematically integrate these exact keywords (avoiding synonyms unless instructed) into all generated documents to optimize for Applicant Tracking Systems (ATS) and recruiter review. | |
| </CoreProcessingInstructions> | |
| <GenerationTasks> | |
| <Task id="resume"> | |
| <Description> | |
| Generate a complete, richly formatted Markdown resume, fully tailored for the specific job opening. | |
| </Description> | |
| <Instructions> | |
| - Return the **entire, updated resume**, not just the changed parts. The final output should be a single, complete Markdown document. | |
| - **Summary Section:** Draft a concise (30-90 words) and impactful summary. This is your elevator pitch. | |
| - If not extremely excessive, match the professional title based on the job opening: | |
| - DO: "Commercial Counsel" -> "Commercial and Corporate Attorney" | |
| - DO: "Corporate Counsel" -> "Corporate Attorney" | |
| - DO NOT: Attorney --> General Counsel | |
| - DO NOT: intern --> manager | |
| - Incorporate the candidate's years of experience, but adjust as follows: | |
| - If the required experience is incompatible with the candidate's seniority, use phrases like "seasoned" or "experienced" instead of a number. | |
| - For roles requiring 3-4 years, do not overemphasize seniority. | |
| - For roles requiring 5+ years, explicitly state the number of years of experience. | |
| - **Skills Section:** | |
| - Identify the most important keywords from the job posting. Prioritize keywords that appear multiple times, are standard for the role, or showcase unique relevant skills. | |
| - Include industry-standard skills that may not be in the job description but are valuable for the role. | |
| - Format the skills as a comma-separated list: `Skilla, skillb, skillc`. | |
| - **Experience Section:** | |
| - Adapt the language in the resume to use the exact keywords from the job posting, replacing any synonyms. | |
| - Ensure bullet points are unique, highlight quantifiable achievements, and provide context (e.g., team size, project value). | |
| - Structure bullet points using the C.A.R. technique, front-loading the result where possible. | |
| - **Formatting:** Ensure the length and structure of the updated resume are similar to the original provided document. | |
| - MANDATORY: Your answer must be perfect Markdown syntax (will be rendered by mistune). Do NOT use h1 (e.g.\\#", but use the styles appropriately (like all caps for name, sections, bold and italics where appropriate)! | |
| </Instructions> | |
| </Task> | |
| <Task id="cover_letter"> | |
| <Description> | |
| Generate a compelling, personalized, and richly formatted Markdown cover letter. | |
| </Description> | |
| <Instructions> | |
| - The cover letter must be under 400 words and structured in 3-4 paragraphs. | |
| - Address it to the hiring manager. If a name is available in the job posting, use it. | |
| - The content should be approximately 60% based on the job opening and 40% on the resume. | |
| - Explicitly mention experiences from the resume that are directly relevant to the role. Highlight leadership experience (e.g., managing paralegals). | |
| - When mentioning past employers, do not append suffixes like "LLP" or "Inc." | |
| - Perform a quick web search for information about the company to add a sentence or two showing genuine interest and research. | |
| - Ensure all statements are directly supported by the resume. | |
| </Instructions> | |
| </Task> | |
| <Task id="letter_to_recruiter"> | |
| <Description> | |
| Generate a concise, professional, and richly formatted Markdown message for LinkedIn or email outreach. | |
| </Description> | |
| <Instructions> | |
| - The message should be 5-7 sentences long. | |
| - Draft a clear and professional subject line (e.g., "Regarding the Corporate Counsel Position"). | |
| - Address the hiring manager by name if available. | |
| - Briefly introduce the candidate, state the position being applied for, and highlight 1-2 key qualifications that make them a perfect fit. | |
| - End with a clear call to action, such as expressing eagerness to discuss the role further. | |
| - Return the message without any additional markup or bullet points. | |
| </Instructions> | |
| </Task> | |
| <Task id="tips"> | |
| <Description> | |
| Generate 5-7 actionable tips specific to this application, formatted as a bulleted list in Markdown. | |
| </Description> | |
| <Instructions> | |
| - Analyze the resume against the job description to identify strengths and potential gaps. | |
| - Provide concrete advice. For example: | |
| - "During the interview, be prepared to elaborate on your experience with [Specific Skill from Job Posting] by using the project at [Previous Company] as a prime example." | |
| - "The job emphasizes [Company Value or Mission]. Consider mentioning how your work in [Relevant Experience] aligns with this value." | |
| - "Your resume mentions [Superficial Skill]. I recommend adding a bullet point with a quantifiable result to strengthen this, such as..." | |
| - "Research the company's recent acquisition of [Company X] and be prepared to discuss its potential legal integration challenges." | |
| - The tips should be formatted as a simple Markdown bulleted list. | |
| </Instructions> | |
| </Task> | |
| </GenerationTasks> | |
| <WritingGuidelines> | |
| <Section name="Summary"> | |
| Not always required: If you've included a cover letter, your summary might be redundant. Valuable for quickly grabbing attention and highlighting your most relevant qualifications, as well as filling the gap if you're not submitting a cover letter. | |
| Focus on: | |
| - Who you are and what you do: Clearly state your professional identity and area of expertise. | |
| - Your achievements: Highlight measurable results, awards, problems solved, and times you exceeded expectations. | |
| </Section> | |
| <Section name="Work Experience"> | |
| This is the section of your resume in which you detail your career history. | |
| - Use bullets for skim value. | |
| - Use simple present tense for current roles (e.g., manages) and simple past tense for past roles (e.g., managed). | |
| - Provide context for each position: company type, industry, size, and your role's impact on the bottom line. | |
| - Focus on your main functions and their relevance to the target job. | |
| - Emphasize quantifiable impact: landing clients, saving costs, automating tasks, hitting targets. | |
| </Section> | |
| <Section name="Bullet Point Structuring"> | |
| - PAR (Problem-Action-Result): Ideal for showing accomplishments and impact. Example: "Reduced customer support response time by 20% by implementing a new ticketing system." | |
| - STAR (Situation-Task-Action-Result): Provides a more detailed narrative for demonstrating specific competencies. Example: "Managed inventory levels in a fast-paced retail setting; achieved a 15% reduction in inventory costs." | |
| - Result-First: Leads with the outcome to grab attention. Example: "Increased sales revenue by 18% through targeted marketing campaigns." | |
| - Action Verb + Skill + Result: Concise and effective for technical roles. Example: "Developed a user-friendly website using HTML, CSS, and JavaScript, resulting in a 30% increase in website traffic." | |
| - Prioritize your most relevant bullet points for each specific job application. | |
| </Section> | |
| <Section name="Technical Skills"> | |
| - Showcase specific knowledge in tools, software, and processes. | |
| - Hard Skills: Programming languages (Java, Python), software (Adobe Creative Suite), cloud platforms (AWS, Azure). | |
| - Soft Technical Skills: Troubleshooting, data analysis, project management, technical writing. | |
| - Use the PAR method to demonstrate technical skills in context. Example: "Problem: Website traffic was declining. Action: Implemented optimization techniques using HTML, CSS, JavaScript. Result: Increased traffic by 25%." | |
| </Section> | |
| <Section name="Soft Skills"> | |
| - Avoid overused terms like "Problem-solving" or "Driven". | |
| - Instead of listing soft skills, demonstrate them through your achievement-oriented bullet points. Example: Instead of "excellent communication skills," write "Presented quarterly performance updates to internal stakeholders." | |
| </Section> | |
| <Section name="Professional Brand and Summary Details"> | |
| - Your brand is the unique combination of your skills, experience, and personality. | |
| - Your summary is your sales pitch, answering "Who are you and why should I hire you?" | |
| - It should be concise (60-120 words), factual, data-driven, and placed at the top of your resume. | |
| - Use a combination of present tense ("who you are") and past tense ("what you've achieved"). Example: "Project manager (PMP) with 11 years of experience... Successfully completed three plant projects in 2022 valued at $15MM." | |
| </Section> | |
| <Section name="Experience Section Details"> | |
| - List your most relevant responsibilities and bullets first. | |
| - Avoid generic statements; provide specific context and quantifiable metrics. | |
| - Use the C.A.R (Challenge, Action, Result) technique. Where possible, front-load the bullet with the result. | |
| - Use a short introductory paragraph for each role to provide a high-level overview. | |
| - Utilize strong action verbs (e.g., Achieved, Coordinated, Implemented, Streamlined, Managed, Maximized). | |
| </Section> | |
| </WritingGuidelines> | |
| </JobApplicationGenerationRequest> | |
| """ | |
| def _(BaseModel, Field): | |
| class ApplicationMaterials(BaseModel): | |
| """Structured output for resume and cover letter generation.""" | |
| resume: str = Field( | |
| ..., | |
| description='Markdown Richly Formatted resume highlighting relevant experience for the job. 2 pages MAX, aim for 1.', | |
| ) | |
| cover_letter: str = Field( | |
| ..., description='Markdown Richly Formatted and Compelling, personalized cover letter (3-4 paragraphs)' | |
| ) | |
| letter_to_recruiter: str = Field( | |
| ..., | |
| description='Markdown Richly Formatted and Concise professional message for LinkedIn/email outreach (5-7 sentences)', | |
| ) | |
| tips: str = Field( | |
| ..., | |
| description='Markdown Richly Formatted and 5-7 actionable tips specific to this application, formatted as bullet points. MANDATORY: Must end wishing the candidate good luck and explaining finding a job is a numbers game. Motivate them!', | |
| ) | |
| return (ApplicationMaterials,) | |
| def _(mo): | |
| resume_button = mo.ui.file( | |
| kind='area', | |
| filetypes=['.pdf', '.txt', '.doc', '.docx'], | |
| label='Or drop your pdf/doc/docx/txt file résumé here!', | |
| ) | |
| job_posting_area = mo.ui.text_area(placeholder='📋 Drop your job posting here!', full_width=True, rows=27) | |
| resume_area = mo.ui.text_area( | |
| placeholder='📄 Provide the full content of your résumé. Uploading file in pdf/docx/doc/txt is preferred.', | |
| full_width=True, | |
| rows=20, | |
| ) | |
| job_url = mo.ui.text( | |
| placeholder='https://example.com/job-posting', | |
| label='Alternatively, provide the URL of the job posting', | |
| full_width=True, | |
| ) | |
| continue_button = mo.ui.button( | |
| value=None, | |
| kind='success', | |
| tooltip='Click submit to continue.', | |
| label='Submit All (it takes a little bit, please be patient!)', | |
| keyboard_shortcut='Ctrl+Enter', | |
| full_width=True, | |
| ) | |
| continue_render = mo.vstack( | |
| [ | |
| mo.md("# Let's start with your materials!"), | |
| mo.vstack( | |
| [ | |
| mo.md('## '), | |
| mo.hstack( | |
| [ | |
| mo.vstack([mo.md('### Resume:'), resume_button, resume_area], align='center'), | |
| mo.vstack([mo.md('### Job Posting:'), job_posting_area, job_url], align='center'), | |
| ], | |
| align='center', | |
| ), | |
| continue_button, | |
| ], | |
| align='start', | |
| ), | |
| ], | |
| align='center', | |
| ) | |
| return continue_render, job_posting_area, job_url, resume_button | |
| def _(continue_render) -> None: | |
| continue_render | |
| def _(job_posting_area, job_url): | |
| if job_posting_area.value != '': | |
| job_posting = job_posting_area.value | |
| buceta = True | |
| elif job_posting_area.value == '' and job_url.value == '': | |
| buceta = None | |
| job_posting = None | |
| elif job_posting_area.value == '' and job_url.value != '': | |
| import httpx | |
| url = f'https://r.jina.ai/{job_url.value}' | |
| import os as _os | |
| jina_api_key = _os.environ.get('JINA_API_KEY', '') | |
| headers = {'Authorization': f'Bearer {jina_api_key}'} | |
| response = httpx.get(url, headers=headers) | |
| job_posting = response.text | |
| buceta = True | |
| else: | |
| buceta = None | |
| job_posting = None | |
| return buceta, job_posting | |
| def _(resume_button): | |
| # Helper function to determine media type from filename | |
| import io | |
| try: | |
| if resume_button.contents() != '': | |
| resume_contents = resume_button.contents() | |
| try: | |
| from markitdown import MarkItDown | |
| md = MarkItDown(enable_plugins=True) | |
| forcing_bytes = io.BytesIO(resume_contents) | |
| docx_converted = md.convert(forcing_bytes) | |
| resume_complete = str(docx_converted.text_content) | |
| buceta_ = True | |
| except Exception: | |
| if isinstance(resume_contents, (bytes, bytearray)): | |
| resume_complete = io.BytesIO(resume_contents) | |
| buceta_ = True | |
| else: | |
| resume_complete = resume_contents | |
| buceta_ = True | |
| else: | |
| resume_contents = '' | |
| buceta_ = None | |
| except Exception: | |
| resume_contents = '' | |
| buceta_ = None | |
| if resume_contents == '' or resume_contents is None: | |
| resume_complete = None | |
| return buceta_, io, resume_complete | |
| def _(job_posting, resume_complete): | |
| final_prompt = create_job_application_prompt(resume_complete, job_posting) | |
| return (final_prompt,) | |
| def _(buceta, buceta_): | |
| bucetilda = buceta_ and buceta | |
| return (bucetilda,) | |
| def _(bucetilda, mo) -> None: | |
| mo.stop( | |
| bucetilda is None, mo.callout(mo.md('⚠️ **Please provide your materials before continuing!**'), kind='danger') | |
| ) | |
| def _(Agent, ApplicationMaterials, bucetilda, final_prompt, mo): | |
| mo.stop(bucetilda is None) | |
| import asyncio | |
| import nest_asyncio | |
| import uvloop | |
| asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) | |
| nest_asyncio.apply() | |
| career_agent = Agent('gemini-2.5-flash', output_type=ApplicationMaterials) | |
| result = asyncio.run(career_agent.run(final_prompt)) if final_prompt is not None else None | |
| return (result,) | |
| def _(result): | |
| import json | |
| output = getattr(result, 'output', None) | |
| parsed = None | |
| if isinstance(output, str): | |
| try: | |
| parsed = json.loads(output) | |
| except Exception: | |
| parsed = None | |
| elif isinstance(output, dict): | |
| parsed = output | |
| else: | |
| try: | |
| parsed = {} | |
| for k in dir(output): | |
| if k.startswith(('_', 'model_')): | |
| continue | |
| try: | |
| v = getattr(output, k) | |
| except Exception: | |
| continue | |
| if callable(v): | |
| continue | |
| parsed[k] = v | |
| except Exception: | |
| parsed = None | |
| def _get_field(*keys, default=None): | |
| if parsed and isinstance(parsed, dict): | |
| for k in keys: | |
| if k in parsed: | |
| return parsed[k] | |
| for k in keys: | |
| if hasattr(output, k): | |
| try: | |
| return getattr(output, k) | |
| except Exception: | |
| continue | |
| return default | |
| processed_data = { | |
| 'resume': _get_field('resume', 'resume_text', default=None), | |
| 'cover_letter': _get_field('cover_letter', 'coverletter', 'cover_letter_text', default=None), | |
| 'recruiter_message': _get_field( | |
| 'letter_to_recruiter', 'recruiter_message', 'message_to_recruiter', default=None | |
| ), | |
| 'tips': _get_field('tips', 'advice', 'suggestions', default=None), | |
| } | |
| return (processed_data,) | |
| def _(mo, processed_data): | |
| preview_section = mo.vstack([ | |
| mo.md('## Preview Your Documents'), | |
| mo.md('---'), | |
| mo.md('### Here is your résumé, how it looks like?'), | |
| mo.md('---'), | |
| mo.md(processed_data.get('resume')), | |
| mo.md('---'), | |
| mo.md('---'), | |
| mo.md('### 💼 Here is your Cover Letter'), | |
| mo.md('---'), | |
| mo.md(processed_data.get('cover_letter')), | |
| mo.md('---'), | |
| mo.md('---'), | |
| mo.md('### 📧 Here is your message to reach out to the recruiter'), | |
| mo.md('---'), | |
| mo.md(processed_data.get('recruiter_message')), | |
| mo.md('---'), | |
| mo.md('---'), | |
| mo.md('### 💡 Here are some additional tips'), | |
| mo.md('---'), | |
| mo.md(processed_data.get('tips')), | |
| mo.md('---'), | |
| mo.md('---'), | |
| ]) | |
| return (preview_section,) | |
| def _(preview_section) -> None: | |
| preview_section | |
| def _(io): | |
| import mistune | |
| from html4docx import HtmlToDocx | |
| from mistune.plugins.abbr import abbr | |
| from mistune.plugins.def_list import def_list | |
| from mistune.plugins.footnotes import footnotes | |
| from mistune.plugins.formatting import insert, mark, strikethrough, subscript, superscript | |
| from mistune.plugins.table import table | |
| from mistune.plugins.task_lists import task_lists | |
| # Define mistune plugins to use | |
| MISTUNE_PLUGINS = [ | |
| strikethrough, | |
| footnotes, | |
| table, | |
| task_lists, | |
| insert, | |
| def_list, | |
| abbr, | |
| mark, | |
| subscript, | |
| superscript, | |
| ] | |
| def _build_markdown_parser(hard_wrap: bool = True): | |
| """Create a Mistune Markdown parser with desired plugins and hard-wrap behavior.""" | |
| renderer = mistune.HTMLRenderer() | |
| inline = mistune.InlineParser(hard_wrap=hard_wrap) | |
| return mistune.Markdown(renderer=renderer, inline=inline, plugins=MISTUNE_PLUGINS) | |
| def markdown_to_docx_bytes(markdown_text: str) -> bytes | str: | |
| """Asynchronously converts markdown text to DOCX bytes with legal formatting.""" | |
| try: | |
| markdown_parser = _build_markdown_parser(hard_wrap=True) | |
| html_content = markdown_parser(markdown_text) | |
| try: | |
| parser = HtmlToDocx() | |
| docx_document = parser.parse_html_string(html_content) | |
| # Save to memory | |
| docx_bytes = io.BytesIO() | |
| docx_document.save(docx_bytes) | |
| docx_bytes.seek(0) | |
| return docx_bytes.getvalue() | |
| except Exception: | |
| return markdown_text | |
| except Exception: | |
| return markdown_text | |
| return (markdown_to_docx_bytes,) | |
| def _(markdown_to_docx_bytes, mo, processed_data) -> None: | |
| final_resume = markdown_to_docx_bytes(processed_data.get('resume')) | |
| final_cover_letter = markdown_to_docx_bytes(processed_data.get('cover_letter')) | |
| final_recruiter_message = markdown_to_docx_bytes(processed_data.get('recruiter_message')) | |
| resume_download = mo.download(data=final_resume, filename='resume.docx', label='Résumé') | |
| cover_letter_download = mo.download(data=final_cover_letter, filename='cover_letter.docx', label='Cover Letter') | |
| message_download = mo.download( | |
| data=final_recruiter_message, filename='recruiter_message.docx', label='Recruiter Message' | |
| ) | |
| downloads_section = mo.vstack([ | |
| mo.md('## 🎉 Your Documents Are Ready!'), | |
| mo.md('Click the buttons below to download your documents:'), | |
| mo.hstack([resume_download, cover_letter_download, message_download], justify='center', gap=2), | |
| mo.md('---'), | |
| mo.callout( | |
| mo.md('✅ **Success!** Your documents have been generated and are ready to download.'), kind='success' | |
| ), | |
| ]) | |
| downloads_section | |
| def _(mo) -> None: | |
| mo.md("""# <center>Time to do another application! Just add new materials. You got this!</center>""") | |
| if __name__ == '__main__': | |
| app.run() | |