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) @app.cell def _(): import marimo as mo mo.md(""" #
Cicero Jobs
## 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,) @app.cell def _(): from pydantic import BaseModel, Field from pydantic_ai import Agent return Agent, BaseModel, Field @app.function def create_job_application_prompt(resume: str, cover_letter: str, further_instructions: str | None = None) -> str: fi = further_instructions or '' return f""" 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. {resume} {cover_letter} {fi} 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. 1. **Analyze and Synthesize:** Meticulously analyze the provided `` and the ``. 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 `` 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 ``. 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. Generate a complete, richly formatted Markdown resume, fully tailored for the specific job opening. - 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)! Generate a compelling, personalized, and richly formatted Markdown cover letter. - 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. Generate a concise, professional, and richly formatted Markdown message for LinkedIn or email outreach. - 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. Generate 5-7 actionable tips specific to this application, formatted as a bulleted list in Markdown. - 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.
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.
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.
- 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.
- 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%."
- 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."
- 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."
- 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).
""" @app.cell(hide_code=True) 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,) @app.cell(hide_code=True) 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 @app.cell def _(continue_render) -> None: continue_render @app.cell 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 @app.cell 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 @app.cell def _(job_posting, resume_complete): final_prompt = create_job_application_prompt(resume_complete, job_posting) return (final_prompt,) @app.cell def _(buceta, buceta_): bucetilda = buceta_ and buceta return (bucetilda,) @app.cell def _(bucetilda, mo) -> None: mo.stop( bucetilda is None, mo.callout(mo.md('⚠️ **Please provide your materials before continuing!**'), kind='danger') ) @app.cell 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,) @app.cell 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,) @app.cell 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,) @app.cell def _(preview_section) -> None: preview_section @app.cell 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,) @app.cell 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 @app.cell def _(mo) -> None: mo.md("""#
Time to do another application! Just add new materials. You got this!
""") if __name__ == '__main__': app.run()