arthrod commited on
Commit
ca8de5d
Β·
1 Parent(s): 958df6c

looking decent

Browse files
Files changed (4) hide show
  1. Dockerfile +1 -1
  2. app.py +446 -392
  3. layouts/app.slides.json +4 -0
  4. requirements.txt +603 -5
Dockerfile CHANGED
@@ -16,4 +16,4 @@ RUN mkdir -p /app/__marimo__ && \
16
  chmod -R 755 /app
17
  USER user
18
 
19
- CMD ["marimo", "run", "app.py", "--include-code", "--host", "0.0.0.0", "--port", "7860"]
 
16
  chmod -R 755 /app
17
  USER user
18
 
19
+ CMD ["marimo", "run", "app.py", "--token-password","${CICEROJOBAPPS_API_KEY}","--host", "0.0.0.0", "--port", "7860"]
app.py CHANGED
@@ -1,470 +1,524 @@
1
  import marimo
2
 
3
- __generated_with = "0.9.2"
4
  app = marimo.App()
5
 
6
 
7
  @app.cell
8
- def __():
9
  import marimo as mo
10
 
11
- mo.md("# Welcome to marimo! πŸŒŠπŸƒ")
12
  return (mo,)
13
 
14
 
15
  @app.cell
16
- def __(mo):
17
- slider = mo.ui.slider(1, 22)
18
- return (slider,)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
 
21
- @app.cell
22
- def __(mo, slider):
23
- mo.md(
24
- f"""
25
- marimo is a **reactive** Python notebook.
26
-
27
- This means that unlike traditional notebooks, marimo notebooks **run
28
- automatically** when you modify them or
29
- interact with UI elements, like this slider: {slider}.
30
 
31
- {"##" + "πŸƒ" * slider.value}
32
- """
33
- )
34
- return
 
35
 
36
 
37
  @app.cell(hide_code=True)
38
- def __(mo):
39
- mo.accordion(
40
- {
41
- "Tip: disabling automatic execution": mo.md(
42
- rf"""
43
- marimo lets you disable automatic execution: just go into the
44
- notebook settings and set
45
-
46
- "Runtime > On Cell Change" to "lazy".
47
-
48
- When the runtime is lazy, after running a cell, marimo marks its
49
- descendants as stale instead of automatically running them. The
50
- lazy runtime puts you in control over when cells are run, while
51
- still giving guarantees about the notebook state.
52
- """
53
- )
54
- }
55
- )
56
- return
57
 
 
58
 
59
- @app.cell(hide_code=True)
60
- def __(mo):
61
- mo.md(
62
- """
63
- Tip: This is a tutorial notebook. You can create your own notebooks
64
- by entering `marimo edit` at the command line.
65
- """
66
- ).callout()
67
- return
68
 
69
 
70
- @app.cell(hide_code=True)
71
- def __(mo):
72
- mo.md(
73
- """
74
- ## 1. Reactive execution
75
-
76
- A marimo notebook is made up of small blocks of Python code called
77
- cells.
78
-
79
- marimo reads your cells and models the dependencies among them: whenever
80
- a cell that defines a global variable is run, marimo
81
- **automatically runs** all cells that reference that variable.
82
-
83
- Reactivity keeps your program state and outputs in sync with your code,
84
- making for a dynamic programming environment that prevents bugs before they
85
- happen.
86
- """
87
- )
88
- return
89
 
 
90
 
91
- @app.cell(hide_code=True)
92
- def __(changed, mo):
93
- (
94
- mo.md(
95
- f"""
96
- **✨ Nice!** The value of `changed` is now {changed}.
97
-
98
- When you updated the value of the variable `changed`, marimo
99
- **reacted** by running this cell automatically, because this cell
100
- references the global variable `changed`.
101
-
102
- Reactivity ensures that your notebook state is always
103
- consistent, which is crucial for doing good science; it's also what
104
- enables marimo notebooks to double as tools and apps.
105
- """
106
- )
107
- if changed
108
- else mo.md(
109
- """
110
- **🌊 See it in action.** In the next cell, change the value of the
111
- variable `changed` to `True`, then click the run button.
112
- """
113
- )
114
  )
115
- return
116
 
117
 
118
  @app.cell
119
- def __():
120
- changed = False
121
- return (changed,)
122
-
123
-
124
- @app.cell(hide_code=True)
125
- def __(mo):
126
- mo.accordion(
127
- {
128
- "Tip: execution order": (
129
- """
130
- The order of cells on the page has no bearing on
131
- the order in which cells are executed: marimo knows that a cell
132
- reading a variable must run after the cell that defines it. This
133
- frees you to organize your code in the way that makes the most
134
- sense for you.
135
- """
136
- )
137
- }
138
- )
139
  return
140
 
141
 
142
- @app.cell(hide_code=True)
143
- def __(mo):
144
- mo.md(
145
- """
146
- **Global names must be unique.** To enable reactivity, marimo imposes a
147
- constraint on how names appear in cells: no two cells may define the same
148
- variable.
149
- """
150
- )
151
- return
 
 
 
 
 
 
 
 
 
152
 
153
 
154
- @app.cell(hide_code=True)
155
- def __(mo):
156
- mo.accordion(
157
- {
158
- "Tip: encapsulation": (
159
- """
160
- By encapsulating logic in functions, classes, or Python modules,
161
- you can minimize the number of global variables in your notebook.
162
- """
163
- )
164
- }
165
- )
166
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
 
169
- @app.cell(hide_code=True)
170
- def __(mo):
171
- mo.accordion(
172
- {
173
- "Tip: private variables": (
174
- """
175
- Variables prefixed with an underscore are "private" to a cell, so
176
- they can be defined by multiple cells.
177
- """
178
- )
179
- }
180
- )
181
- return
182
 
183
 
184
- @app.cell(hide_code=True)
185
- def __(mo):
186
- mo.md(
187
- """
188
- ## 2. UI elements
189
-
190
- Cells can output interactive UI elements. Interacting with a UI
191
- element **automatically triggers notebook execution**: when
192
- you interact with a UI element, its value is sent back to Python, and
193
- every cell that references that element is re-run.
194
-
195
- marimo provides a library of UI elements to choose from under
196
- `marimo.ui`.
197
- """
198
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  return
200
 
201
 
202
  @app.cell
203
- def __(mo):
204
- mo.md("""**🌊 Some UI elements.** Try interacting with the below elements.""")
205
  return
206
 
207
 
208
  @app.cell
209
- def __(mo):
210
- icon = mo.ui.dropdown(["πŸƒ", "🌊", "✨"], value="πŸƒ")
211
- return (icon,)
 
212
 
 
 
213
 
214
- @app.cell
215
- def __(icon, mo):
216
- repetitions = mo.ui.slider(1, 16, label=f"number of {icon.value}: ")
217
- return (repetitions,)
218
 
219
 
220
  @app.cell
221
- def __(icon, repetitions):
222
- icon, repetitions
223
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
 
225
 
226
  @app.cell
227
- def __(icon, mo, repetitions):
228
- mo.md("# " + icon.value * repetitions.value)
229
  return
230
 
231
 
232
- @app.cell(hide_code=True)
233
- def __(mo):
234
- mo.md(
235
- """
236
- ## 3. marimo is just Python
237
-
238
- marimo cells parse Python (and only Python), and marimo notebooks are
239
- stored as pure Python files β€” outputs are _not_ included. There's no
240
- magical syntax.
241
-
242
- The Python files generated by marimo are:
243
-
244
- - easily versioned with git, yielding minimal diffs
245
- - legible for both humans and machines
246
- - formattable using your tool of choice,
247
- - usable as Python scripts, with UI elements taking their default
248
- values, and
249
- - importable by other modules (more on that in the future).
250
- """
251
- )
252
- return
 
 
 
 
 
 
253
 
254
 
255
- @app.cell(hide_code=True)
256
- def __(mo):
257
- mo.md(
258
- """
259
- ## 4. Running notebooks as apps
260
-
261
- marimo notebooks can double as apps. Click the app window icon in the
262
- bottom-right to see this notebook in "app view."
263
-
264
- Serve a notebook as an app with `marimo run` at the command-line.
265
- Of course, you can use marimo just to level-up your
266
- notebooking, without ever making apps.
267
- """
268
- )
269
  return
270
 
271
 
272
- @app.cell(hide_code=True)
273
- def __(mo):
274
- mo.md(
275
- """
276
- ## 5. The `marimo` command-line tool
277
-
278
- **Creating and editing notebooks.** Use
279
-
280
- ```
281
- marimo edit
282
- ```
283
-
284
- in a terminal to start the marimo notebook server. From here
285
- you can create a new notebook or edit existing ones.
286
-
287
-
288
- **Running as apps.** Use
289
-
290
- ```
291
- marimo run notebook.py
292
- ```
293
-
294
- to start a webserver that serves your notebook as an app in read-only mode,
295
- with code cells hidden.
296
-
297
- **Convert a Jupyter notebook.** Convert a Jupyter notebook to a marimo
298
- notebook using `marimo convert`:
299
-
300
- ```
301
- marimo convert your_notebook.ipynb > your_app.py
302
- ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
- **Tutorials.** marimo comes packaged with tutorials:
305
 
306
- - `dataflow`: more on marimo's automatic execution
307
- - `ui`: how to use UI elements
308
- - `markdown`: how to write markdown, with interpolated values and
309
- LaTeX
310
- - `plots`: how plotting works in marimo
311
- - `sql`: how to use SQL
312
- - `layout`: layout elements in marimo
313
- - `fileformat`: how marimo's file format works
314
- - `markdown-format`: for using `.md` files in marimo
315
- - `for-jupyter-users`: if you are coming from Jupyter
316
 
317
- Start a tutorial with `marimo tutorial`; for example,
318
 
319
- ```
320
- marimo tutorial dataflow
321
- ```
322
 
323
- In addition to tutorials, we have examples in our
324
- [our GitHub repo](https://www.github.com/marimo-team/marimo/tree/main/examples).
325
- """
326
- )
327
- return
328
 
 
329
 
330
- @app.cell(hide_code=True)
331
- def __(mo):
332
- mo.md(
333
- """
334
- ## 6. The marimo editor
 
 
335
 
336
- Here are some tips to help you get started with the marimo editor.
337
- """
338
- )
339
  return
340
 
341
 
342
  @app.cell
343
- def __(mo, tips):
344
- mo.accordion(tips)
345
- return
346
-
347
-
348
- @app.cell(hide_code=True)
349
- def __(mo):
350
- mo.md("""## Finally, a fun fact""")
351
  return
352
 
353
 
354
- @app.cell(hide_code=True)
355
- def __(mo):
356
- mo.md(
357
- """
358
- The name "marimo" is a reference to a type of algae that, under
359
- the right conditions, clumps together to form a small sphere
360
- called a "marimo moss ball". Made of just strands of algae, these
361
- beloved assemblages are greater than the sum of their parts.
362
- """
363
- )
364
- return
365
-
366
-
367
- @app.cell(hide_code=True)
368
- def __():
369
- tips = {
370
- "Saving": (
371
- """
372
- **Saving**
373
-
374
- - _Name_ your app using the box at the top of the screen, or
375
- with `Ctrl/Cmd+s`. You can also create a named app at the
376
- command line, e.g., `marimo edit app_name.py`.
377
-
378
- - _Save_ by clicking the save icon on the bottom right, or by
379
- inputting `Ctrl/Cmd+s`. By default marimo is configured
380
- to autosave.
381
- """
382
- ),
383
- "Running": (
384
- """
385
- 1. _Run a cell_ by clicking the play ( β–· ) button on the top
386
- right of a cell, or by inputting `Ctrl/Cmd+Enter`.
387
-
388
- 2. _Run a stale cell_ by clicking the yellow run button on the
389
- right of the cell, or by inputting `Ctrl/Cmd+Enter`. A cell is
390
- stale when its code has been modified but not run.
391
-
392
- 3. _Run all stale cells_ by clicking the play ( β–· ) button on
393
- the bottom right of the screen, or input `Ctrl/Cmd+Shift+r`.
394
- """
395
- ),
396
- "Console Output": (
397
- """
398
- Console output (e.g., `print()` statements) is shown below a
399
- cell.
400
- """
401
- ),
402
- "Creating, Moving, and Deleting Cells": (
403
- """
404
- 1. _Create_ a new cell above or below a given one by clicking
405
- the plus button to the left of the cell, which appears on
406
- mouse hover.
407
-
408
- 2. _Move_ a cell up or down by dragging on the handle to the
409
- right of the cell, which appears on mouse hover.
410
-
411
- 3. _Delete_ a cell by clicking the trash bin icon. Bring it
412
- back by clicking the undo button on the bottom right of the
413
- screen, or with `Ctrl/Cmd+Shift+z`.
414
- """
415
- ),
416
- "Disabling Automatic Execution": (
417
- """
418
- Via the notebook settings (gear icon) or footer panel, you
419
- can disable automatic execution. This is helpful when
420
- working with expensive notebooks or notebooks that have
421
- side-effects like database transactions.
422
- """
423
- ),
424
- "Disabling Cells": (
425
- """
426
- You can disable a cell via the cell context menu.
427
- marimo will never run a disabled cell or any cells that depend on it.
428
- This can help prevent accidental execution of expensive computations
429
- when editing a notebook.
430
- """
431
- ),
432
- "Code Folding": (
433
- """
434
- You can collapse or fold the code in a cell by clicking the arrow
435
- icons in the line number column to the left, or by using keyboard
436
- shortcuts.
437
-
438
- Use the command palette (`Ctrl/Cmd+k`) or a keyboard shortcut to
439
- quickly fold or unfold all cells.
440
- """
441
- ),
442
- "Code Formatting": (
443
- """
444
- If you have [ruff](https://github.com/astral-sh/ruff) installed,
445
- you can format a cell with the keyboard shortcut `Ctrl/Cmd+b`.
446
- """
447
- ),
448
- "Command Palette": (
449
- """
450
- Use `Ctrl/Cmd+k` to open the command palette.
451
- """
452
- ),
453
- "Keyboard Shortcuts": (
454
- """
455
- Open the notebook menu (top-right) or input `Ctrl/Cmd+Shift+h` to
456
- view a list of all keyboard shortcuts.
457
- """
458
- ),
459
- "Configuration": (
460
- """
461
- Configure the editor by clicking the gears icon near the top-right
462
- of the screen.
463
- """
464
- ),
465
- }
466
- return (tips,)
467
-
468
-
469
  if __name__ == "__main__":
470
  app.run()
 
1
  import marimo
2
 
3
+ __generated_with = "0.17.0"
4
  app = marimo.App()
5
 
6
 
7
  @app.cell
8
+ def _():
9
  import marimo as mo
10
 
11
+ mo.md('# <center>Cicero Jobs</center>')
12
  return (mo,)
13
 
14
 
15
  @app.cell
16
+ def _():
17
+ import glob
18
+ import os
19
+ from pydantic_ai import Agent, BinaryContent, DocumentUrl
20
+ from pydantic import BaseModel, Field
21
+ return Agent, BaseModel, BinaryContent, DocumentUrl, Field
22
+
23
+
24
+ @app.function
25
+ def create_job_application_prompt(resume: str, cover_letter: str, further_instructions: str | None = None) -> str:
26
+ fi = further_instructions or ''
27
+ return f"""
28
+ <JobApplicationGenerationRequest>
29
+ <Persona>
30
+ 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.
31
+ </Persona>
32
+
33
+ <InputData>
34
+ <Resume>
35
+ {resume}
36
+ </Resume>
37
+ <JobOpening>
38
+ {cover_letter}
39
+ </JobOpening>
40
+ <FurtherInstructions>
41
+ {fi}
42
+ </FurtherInstructions>
43
+ </InputData>
44
+
45
+ <OutputSpecification>
46
+ 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.
47
+ </OutputSpecification>
48
+
49
+ <CoreProcessingInstructions>
50
+ 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.
51
+ 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.
52
+ 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.
53
+ 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.
54
+ </CoreProcessingInstructions>
55
+
56
+ <GenerationTasks>
57
+ <Task id="resume">
58
+ <Description>
59
+ Generate a complete, richly formatted Markdown resume, fully tailored for the specific job opening.
60
+ </Description>
61
+ <Instructions>
62
+ - Return the **entire, updated resume**, not just the changed parts. The final output should be a single, complete Markdown document.
63
+ - **Summary Section:** Draft a concise (30-90 words) and impactful summary. This is your elevator pitch.
64
+ - If not extremely excessive, match the professional title based on the job opening:
65
+ - DO: "Commercial Counsel" -> "Commercial and Corporate Attorney"
66
+ - DO: "Corporate Counsel" -> "Corporate Attorney"
67
+ - DO NOT: Attorney --> General Counsel
68
+ - DO NOT: intern --> manager
69
+ - Incorporate the candidate's years of experience, but adjust as follows:
70
+ - If the required experience is incompatible with the candidate's seniority, use phrases like "seasoned" or "experienced" instead of a number.
71
+ - For roles requiring 3-4 years, do not overemphasize seniority.
72
+ - For roles requiring 5+ years, explicitly state the number of years of experience.
73
+ - **Skills Section:**
74
+ - 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.
75
+ - Include industry-standard skills that may not be in the job description but are valuable for the role.
76
+ - Format the skills as a comma-separated list: `Skilla, skillb, skillc`.
77
+ - **Experience Section:**
78
+ - Adapt the language in the resume to use the exact keywords from the job posting, replacing any synonyms.
79
+ - Ensure bullet points are unique, highlight quantifiable achievements, and provide context (e.g., team size, project value).
80
+ - Structure bullet points using the C.A.R. technique, front-loading the result where possible.
81
+ - **Formatting:** Ensure the length and structure of the updated resume are similar to the original provided document.
82
+ - MANDATORY: Your answer must be perfect Markdown syntax (will be rendered by mistune)
83
+ - MANDATORY: headers such as the candidate name, section titles (e.g., Summary, Experience, Skills), and contact information must be wrapped in <p style=\"text-align:center;\"> ... </p> tags.
84
+ - MANDATORY: Must end wishing the candidate good luck and explaining finding a job is a numbers game. Motivate them!
85
+
86
+ </Instructions>
87
+ </Task>
88
+
89
+ <Task id="cover_letter">
90
+ <Description>
91
+ Generate a compelling, personalized, and richly formatted Markdown cover letter.
92
+ </Description>
93
+ <Instructions>
94
+ - The cover letter must be under 400 words and structured in 3-4 paragraphs.
95
+ - Address it to the hiring manager. If a name is available in the job posting, use it.
96
+ - The content should be approximately 60% based on the job opening and 40% on the resume.
97
+ - Explicitly mention experiences from the resume that are directly relevant to the role. Highlight leadership experience (e.g., managing paralegals).
98
+ - When mentioning past employers, do not append suffixes like "LLP" or "Inc."
99
+ - Perform a quick web search for information about the company to add a sentence or two showing genuine interest and research.
100
+ - Ensure all statements are directly supported by the resume.
101
+ </Instructions>
102
+ </Task>
103
+
104
+ <Task id="letter_to_recruiter">
105
+ <Description>
106
+ Generate a concise, professional, and richly formatted Markdown message for LinkedIn or email outreach.
107
+ </Description>
108
+ <Instructions>
109
+ - The message should be 5-7 sentences long.
110
+ - Draft a clear and professional subject line (e.g., "Regarding the Corporate Counsel Position").
111
+ - Address the hiring manager by name if available.
112
+ - Briefly introduce the candidate, state the position being applied for, and highlight 1-2 key qualifications that make them a perfect fit.
113
+ - End with a clear call to action, such as expressing eagerness to discuss the role further.
114
+ - Return the message without any additional markup or bullet points.
115
+ </Instructions>
116
+ </Task>
117
+
118
+ <Task id="tips">
119
+ <Description>
120
+ Generate 5-7 actionable tips specific to this application, formatted as a bulleted list in Markdown.
121
+ </Description>
122
+ <Instructions>
123
+ - Analyze the resume against the job description to identify strengths and potential gaps.
124
+ - Provide concrete advice. For example:
125
+ - "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."
126
+ - "The job emphasizes [Company Value or Mission]. Consider mentioning how your work in [Relevant Experience] aligns with this value."
127
+ - "Your resume mentions [Superficial Skill]. I recommend adding a bullet point with a quantifiable result to strengthen this, such as..."
128
+ - "Research the company's recent acquisition of [Company X] and be prepared to discuss its potential legal integration challenges."
129
+ - The tips should be formatted as a simple Markdown bulleted list.
130
+ </Instructions>
131
+ </Task>
132
+ </GenerationTasks>
133
+
134
+ <WritingGuidelines>
135
+ <Section name="Summary">
136
+ 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.
137
+ Focus on:
138
+ - Who you are and what you do: Clearly state your professional identity and area of expertise.
139
+ - Your achievements: Highlight measurable results, awards, problems solved, and times you exceeded expectations.
140
+ </Section>
141
+ <Section name="Work Experience">
142
+ This is the section of your resume in which you detail your career history.
143
+ - Use bullets for skim value.
144
+ - Use simple present tense for current roles (e.g., manages) and simple past tense for past roles (e.g., managed).
145
+ - Provide context for each position: company type, industry, size, and your role's impact on the bottom line.
146
+ - Focus on your main functions and their relevance to the target job.
147
+ - Emphasize quantifiable impact: landing clients, saving costs, automating tasks, hitting targets.
148
+ </Section>
149
+ <Section name="Bullet Point Structuring">
150
+ - PAR (Problem-Action-Result): Ideal for showing accomplishments and impact. Example: "Reduced customer support response time by 20% by implementing a new ticketing system."
151
+ - 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."
152
+ - Result-First: Leads with the outcome to grab attention. Example: "Increased sales revenue by 18% through targeted marketing campaigns."
153
+ - 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."
154
+ - Prioritize your most relevant bullet points for each specific job application.
155
+ </Section>
156
+ <Section name="Technical Skills">
157
+ - Showcase specific knowledge in tools, software, and processes.
158
+ - Hard Skills: Programming languages (Java, Python), software (Adobe Creative Suite), cloud platforms (AWS, Azure).
159
+ - Soft Technical Skills: Troubleshooting, data analysis, project management, technical writing.
160
+ - 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%."
161
+ </Section>
162
+ <Section name="Soft Skills">
163
+ - Avoid overused terms like "Problem-solving" or "Driven".
164
+ - 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."
165
+ </Section>
166
+ <Section name="Professional Brand and Summary Details">
167
+ - Your brand is the unique combination of your skills, experience, and personality.
168
+ - Your summary is your sales pitch, answering "Who are you and why should I hire you?"
169
+ - It should be concise (60-120 words), factual, data-driven, and placed at the top of your resume.
170
+ - 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."
171
+ </Section>
172
+ <Section name="Experience Section Details">
173
+ - List your most relevant responsibilities and bullets first.
174
+ - Avoid generic statements; provide specific context and quantifiable metrics.
175
+ - Use the C.A.R (Challenge, Action, Result) technique. Where possible, front-load the bullet with the result.
176
+ - Use a short introductory paragraph for each role to provide a high-level overview.
177
+ - Utilize strong action verbs (e.g., Achieved, Coordinated, Implemented, Streamlined, Managed, Maximized).
178
+ </Section>
179
+ </WritingGuidelines>
180
+ </JobApplicationGenerationRequest>
181
+ """
182
 
183
 
184
+ @app.cell(hide_code=True)
185
+ def _(BaseModel, Field):
186
+ class ApplicationMaterials(BaseModel):
187
+ """Structured output for resume and cover letter generation."""
 
 
 
 
 
188
 
189
+ resume: str = Field(..., description='Markdown Richly Formatted resume highlighting relevant experience for the job')
190
+ cover_letter: str = Field(..., description='Markdown Richly Formatted and Compelling, personalized cover letter (3-4 paragraphs)')
191
+ letter_to_recruiter: str = Field(..., description='Markdown Richly Formatted and Concise professional message for LinkedIn/email outreach (5-7 sentences)')
192
+ tips: str = Field(..., description='Markdown Richly Formatted and 5-7 actionable tips specific to this application, formatted as bullet points')
193
+ return (ApplicationMaterials,)
194
 
195
 
196
  @app.cell(hide_code=True)
197
+ def _(mo):
198
+ resume_button = mo.ui.file(kind='area', filetypes=['.pdf', '.txt', '.doc', '.docx'], label='Or drop your pdf/doc/docx/txt file rΓ©sumΓ© here!')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
 
200
+ job_posting_area = mo.ui.text_area(placeholder='πŸ“‹ Drop your job posting here!', full_width=True, rows=27)
201
 
202
+ 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)
 
 
 
 
 
 
 
 
203
 
204
 
205
+ job_url = mo.ui.text(placeholder='https://example.com/job-posting', label='Alternatively, provide the URL of the job posting', full_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
 
207
+ continue_button = mo.ui.button(value=None, kind='success', tooltip='Click submit to continue.', label='Submit All (or press Ctrl/Cmd Enter)', keyboard_shortcut='Ctrl+Enter', full_width=True)
208
 
209
+ continue_render = mo.vstack(
210
+ [
211
+ mo.md("# Let's start with your materials!"),
212
+ mo.vstack(
213
+ [
214
+ mo.md('## '),
215
+ mo.hstack(
216
+ [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'
217
+ ),
218
+ continue_button,
219
+ ],
220
+ align='start',
221
+ ),
222
+ ],
223
+ align='center',
 
 
 
 
 
 
 
 
224
  )
225
+ return continue_render, job_posting_area, job_url, resume_button
226
 
227
 
228
  @app.cell
229
+ def _(continue_render):
230
+ continue_render
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  return
232
 
233
 
234
+ @app.cell
235
+ def _(job_posting_area, job_url, mo):
236
+ if job_posting_area.value != '':
237
+ buceta = 'Vamo'
238
+ job_posting = job_posting_area.value
239
+ elif job_posting_area.value == '' and job_url.value == '':
240
+ buceta = None
241
+ job_posting = None
242
+ elif job_posting_area.value == '' and job_url.value != '':
243
+ buceta = 'Vamo'
244
+ import httpx
245
+
246
+ url = f'https://r.jina.ai/{job_url.value}'
247
+ headers = {f'Authorization': 'Bearer '}
248
+ response = httpx.get(url, headers=headers)
249
+ job_posting = response.text
250
+
251
+ mo.stop(buceta is None, mo.callout(mo.md('⚠️ **Please provide your materials before continuing!**'), kind='danger'))
252
+ return (job_posting,)
253
 
254
 
255
+ @app.cell
256
+ def _(resume_button):
257
+ # Helper function to determine media type from filename
258
+ import io
259
+
260
+
261
+ try:
262
+ if resume_button.contents() is not '':
263
+ print(f'we got button contents: {resume_button.contents()}')
264
+ resume_contents = resume_button.contents()
265
+ try:
266
+ from markitdown import MarkItDown
267
+
268
+ md = MarkItDown(enable_plugins=True)
269
+ forcing_bytes = io.BytesIO(resume_contents)
270
+ docx_converted = md.convert(forcing_bytes)
271
+ resume_complete = str(docx_converted.text_content)
272
+ print('Converted to markdown')
273
+ except Exception as e:
274
+ print(f'an exception occurrred {e}')
275
+ if isinstance(resume_contents, (bytes, bytearray)):
276
+ resume_complete = io.BytesIO(resume_contents)
277
+ else:
278
+ resume_complete = resume_contents
279
+ else:
280
+ resume_contents = ''
281
+ except Exception:
282
+ resume_contents = ''
283
+ return io, resume_complete
284
 
285
 
286
+ @app.cell
287
+ def _(job_posting, resume_complete):
288
+ final_prompt = create_job_application_prompt(resume_complete, job_posting)
289
+ return (final_prompt,)
 
 
 
 
 
 
 
 
 
290
 
291
 
292
+ @app.cell
293
+ def _(BinaryContent, DocumentUrl, job_posting_area, job_url):
294
+ job_posting_complete = ''
295
+ job_posting_mime = 'text/plain'
296
+ is_url = False
297
+
298
+ job_area_value = job_posting_area.value if hasattr(job_posting_area, 'value') else None
299
+ job_url_value = job_url.value if hasattr(job_url, 'value') else None
300
+
301
+ if job_area_value:
302
+ job_posting_complete = job_area_value
303
+ job_posting_mime = 'text/plain'
304
+ is_url = False
305
+ elif job_url_value:
306
+ job_posting_complete = job_url_value.strip()
307
+ # Basic URL detection
308
+ if job_posting_complete.lower().startswith(('http://', 'https://')):
309
+ is_url = True
310
+ job_posting_mime = None
311
+ else:
312
+ # If user provided something in the URL field that's not an HTTP URL, treat it as plain text
313
+ is_url = False
314
+ job_posting_mime = 'text/plain'
315
+ else:
316
+ job_posting_complete = ''
317
+ job_posting_mime = 'text/plain'
318
+ is_url = False
319
+
320
+ # Build inputs for the agent
321
+ if is_url:
322
+ job_posting_input = DocumentUrl(job_posting_complete)
323
+ else:
324
+ job_posting_input = BinaryContent(data=job_posting_complete, media_type=job_posting_mime or 'text/plain')
325
  return
326
 
327
 
328
  @app.cell
329
+ def _(resume_complete):
330
+ print(resume_complete)
331
  return
332
 
333
 
334
  @app.cell
335
+ def _(Agent, ApplicationMaterials, final_prompt):
336
+ import asyncio
337
+ import uvloop
338
+ import nest_asyncio
339
 
340
+ asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
341
+ nest_asyncio.apply()
342
 
343
+ career_agent = Agent('gemini-2.5-flash', output_type=ApplicationMaterials)
344
+ if final_prompt is not None:
345
+ result = asyncio.run(career_agent.run(final_prompt))
346
+ return (result,)
347
 
348
 
349
  @app.cell
350
+ def _(result):
351
+ import json
352
+
353
+ _output = getattr(result, 'output', None)
354
+
355
+ _parsed = None
356
+ if isinstance(_output, str):
357
+ try:
358
+ _parsed = json.loads(_output)
359
+ except Exception:
360
+ _parsed = None
361
+ elif isinstance(_output, dict):
362
+ _parsed = _output
363
+ else:
364
+ try:
365
+ _parsed = {}
366
+ for k in dir(_output):
367
+ if k.startswith('_') or k.startswith('model_'):
368
+ continue
369
+ try:
370
+ v = getattr(_output, k)
371
+ except Exception:
372
+ continue
373
+ if callable(v):
374
+ continue
375
+ _parsed[k] = v
376
+ except Exception:
377
+ _parsed = None
378
+
379
+
380
+ def _get_field(*keys, default=None):
381
+ if _parsed and isinstance(_parsed, dict):
382
+ for k in keys:
383
+ if k in _parsed:
384
+ return _parsed[k]
385
+ for k in keys:
386
+ if hasattr(_output, k):
387
+ try:
388
+ return getattr(_output, k)
389
+ except Exception:
390
+ continue
391
+ return default
392
+
393
+
394
+ processed_data = {
395
+ 'resume': _get_field('resume', 'resume_text', default=None),
396
+ 'cover_letter': _get_field('cover_letter', 'coverletter', 'cover_letter_text', default=None),
397
+ 'recruiter_message': _get_field('letter_to_recruiter', 'recruiter_message', 'message_to_recruiter', default=None),
398
+ 'tips': _get_field('tips', 'advice', 'suggestions', default=None),
399
+ }
400
+ return (processed_data,)
401
 
402
 
403
  @app.cell
404
+ def _():
 
405
  return
406
 
407
 
408
+ @app.cell
409
+ def _(mo, processed_data):
410
+ preview_section = mo.vstack([
411
+ mo.md('## Preview Your Documents'),
412
+ mo.md('---'),
413
+ mo.md('### Here is your rΓ©sumΓ©, how it looks like?'),
414
+ mo.md('---'),
415
+ mo.md(processed_data.get('resume')),
416
+ mo.md('---'),
417
+ mo.md('---'),
418
+ mo.md('### πŸ’Ό Here is your Cover Letter'),
419
+ mo.md('---'),
420
+ mo.md(processed_data.get('cover_letter')),
421
+ mo.md('---'),
422
+ mo.md('---'),
423
+ mo.md('### πŸ“§ Here is your message to reach out to the recruiter'),
424
+ mo.md('---'),
425
+ mo.md(processed_data.get('recruiter_message')),
426
+ mo.md('---'),
427
+ mo.md('---'),
428
+ mo.md('### πŸ’‘ Here are some additional tips'),
429
+ mo.md('---'),
430
+ mo.md(processed_data.get('tips')),
431
+ mo.md('---'),
432
+ mo.md('---'),
433
+ ])
434
+ return (preview_section,)
435
 
436
 
437
+ @app.cell
438
+ def _(preview_section):
439
+ preview_section
 
 
 
 
 
 
 
 
 
 
 
440
  return
441
 
442
 
443
+ @app.cell
444
+ def _(io):
445
+ from html4docx import HtmlToDocx
446
+ import mistune
447
+ from mistune.plugins.abbr import abbr
448
+ from mistune.plugins.def_list import def_list
449
+ from mistune.plugins.footnotes import footnotes
450
+ from mistune.plugins.formatting import insert, mark, strikethrough, subscript, superscript
451
+ from mistune.plugins.table import table
452
+ from mistune.plugins.task_lists import task_lists
453
+
454
+ # Define mistune plugins to use
455
+ MISTUNE_PLUGINS = [strikethrough, footnotes, table, task_lists, insert, def_list, abbr, mark, subscript, superscript]
456
+
457
+
458
+ def _build_markdown_parser(hard_wrap: bool = True):
459
+ """Create a Mistune Markdown parser with desired plugins and hard-wrap behavior."""
460
+ renderer = mistune.HTMLRenderer()
461
+ inline = mistune.InlineParser(hard_wrap=hard_wrap)
462
+ return mistune.Markdown(renderer=renderer, inline=inline, plugins=MISTUNE_PLUGINS)
463
+
464
+
465
+ def markdown_to_docx_bytes(markdown_text: str) -> bytes | str:
466
+ """Asynchronously converts markdown text to DOCX bytes with legal formatting."""
467
+ try:
468
+ markdown_parser = _build_markdown_parser(hard_wrap=True)
469
+ html_content = markdown_parser(markdown_text)
470
+ try:
471
+ parser = HtmlToDocx()
472
+
473
+ docx_document = parser.parse_html_string(html_content)
474
+ # Save to memory
475
+ docx_bytes = io.BytesIO()
476
+ docx_document.save(docx_bytes)
477
+ docx_bytes.seek(0)
478
+
479
+ print('Successfully converted markdown to DOCX using html4docx with mistune')
480
+ return docx_bytes.getvalue()
481
+
482
+ except Exception as e:
483
+ print("Couldn't do it")
484
+ return markdown_text
485
+
486
+ except Exception as e:
487
+ print("Couldn't do it")
488
+ return markdown_text
489
+ return (markdown_to_docx_bytes,)
490
 
 
491
 
492
+ @app.cell
493
+ def _(markdown_to_docx_bytes, mo, processed_data):
494
+ final_resume = markdown_to_docx_bytes(processed_data.get('resume'))
495
+ final_cover_letter = markdown_to_docx_bytes(processed_data.get('cover_letter'))
496
+ final_recruiter_message = markdown_to_docx_bytes(processed_data.get('recruiter_message'))
 
 
 
 
 
497
 
 
498
 
499
+ resume_download = mo.download(data=final_resume, filename='resume.docx', label='RΓ©sumΓ©')
 
 
500
 
501
+ cover_letter_download = mo.download(data=final_cover_letter, filename='cover_letter.docx', label='Cover Letter')
 
 
 
 
502
 
503
+ message_download = mo.download(data=final_recruiter_message, filename='recruiter_message.docx', label='Recruiter Message')
504
 
505
+ downloads_section = mo.vstack([
506
+ mo.md('## πŸŽ‰ Your Documents Are Ready!'),
507
+ mo.md('Click the buttons below to download your documents:'),
508
+ mo.hstack([resume_download, cover_letter_download, message_download], justify='center', gap=2),
509
+ mo.md('---'),
510
+ mo.callout(mo.md('βœ… **Success!** Your documents have been generated and are ready to download.'), kind='success'),
511
+ ])
512
 
513
+ downloads_section
 
 
514
  return
515
 
516
 
517
  @app.cell
518
+ def _(mo):
519
+ mo.md('# <center>Time to do another application! You got this!')
 
 
 
 
 
 
520
  return
521
 
522
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
523
  if __name__ == "__main__":
524
  app.run()
layouts/app.slides.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "type": "slides",
3
+ "data": {}
4
+ }
requirements.txt CHANGED
@@ -1,5 +1,603 @@
1
- marimo
2
- # Or a specific version
3
- # marimo>=0.9.0
4
-
5
- # Add other dependencies as needed
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # This file was autogenerated by uv via the following command:
2
+ # uv pip compile pyproject.toml -o requirements.txt
3
+ ag-ui-protocol==0.1.9
4
+ # via pydantic-ai-slim
5
+ aiohappyeyeballs==2.6.1
6
+ # via aiohttp
7
+ aiohttp==3.13.0
8
+ # via huggingface-hub
9
+ aiosignal==1.4.0
10
+ # via aiohttp
11
+ annotated-types==0.7.0
12
+ # via pydantic
13
+ anthropic==0.70.0
14
+ # via
15
+ # resume-generator (pyproject.toml)
16
+ # pydantic-ai-slim
17
+ anyio==4.11.0
18
+ # via
19
+ # anthropic
20
+ # google-genai
21
+ # groq
22
+ # httpx
23
+ # mcp
24
+ # openai
25
+ # pydantic-evals
26
+ # sse-starlette
27
+ # starlette
28
+ argcomplete==3.6.2
29
+ # via pydantic-ai-slim
30
+ attrs==25.4.0
31
+ # via
32
+ # aiohttp
33
+ # cyclopts
34
+ # jsonschema
35
+ # referencing
36
+ authlib==1.6.5
37
+ # via fastmcp
38
+ basedpyright==1.31.7
39
+ # via resume-generator (pyproject.toml)
40
+ beautifulsoup4==4.14.2
41
+ # via
42
+ # html-for-docx
43
+ # markdownify
44
+ # markitdown
45
+ black==25.9.0
46
+ # via python-lsp-server
47
+ boto3==1.40.53
48
+ # via pydantic-ai-slim
49
+ botocore==1.40.53
50
+ # via
51
+ # boto3
52
+ # s3transfer
53
+ cachetools==6.2.1
54
+ # via google-auth
55
+ certifi==2025.10.5
56
+ # via
57
+ # httpcore
58
+ # httpx
59
+ # requests
60
+ cffi==2.0.0
61
+ # via cryptography
62
+ charset-normalizer==3.4.4
63
+ # via
64
+ # markitdown
65
+ # pdfminer-six
66
+ # requests
67
+ click==8.3.0
68
+ # via
69
+ # black
70
+ # magika
71
+ # marimo
72
+ # uvicorn
73
+ cobble==0.1.4
74
+ # via mammoth
75
+ cohere==5.19.0
76
+ # via pydantic-ai-slim
77
+ colorama==0.4.6
78
+ # via griffe
79
+ coloredlogs==15.0.1
80
+ # via onnxruntime
81
+ cryptography==46.0.3
82
+ # via
83
+ # authlib
84
+ # pdfminer-six
85
+ cyclopts==3.24.0
86
+ # via fastmcp
87
+ defusedxml==0.7.1
88
+ # via markitdown
89
+ distro==1.9.0
90
+ # via
91
+ # anthropic
92
+ # groq
93
+ # openai
94
+ dnspython==2.8.0
95
+ # via email-validator
96
+ docstring-parser==0.17.0
97
+ # via
98
+ # anthropic
99
+ # cyclopts
100
+ docstring-to-markdown==0.17
101
+ # via python-lsp-server
102
+ docutils==0.22.2
103
+ # via
104
+ # marimo
105
+ # rich-rst
106
+ email-validator==2.3.0
107
+ # via pydantic
108
+ eval-type-backport==0.2.2
109
+ # via mistralai
110
+ exceptiongroup==1.3.0
111
+ # via fastmcp
112
+ executing==2.2.1
113
+ # via logfire
114
+ fastavro==1.12.1
115
+ # via cohere
116
+ fastmcp==2.12.4
117
+ # via resume-generator (pyproject.toml)
118
+ filelock==3.20.0
119
+ # via huggingface-hub
120
+ flatbuffers==25.9.23
121
+ # via onnxruntime
122
+ frozenlist==1.8.0
123
+ # via
124
+ # aiohttp
125
+ # aiosignal
126
+ fsspec==2025.9.0
127
+ # via huggingface-hub
128
+ genai-prices==0.0.32
129
+ # via pydantic-ai-slim
130
+ google-auth==2.41.1
131
+ # via
132
+ # google-genai
133
+ # pydantic-ai-slim
134
+ google-genai==1.45.0
135
+ # via pydantic-ai-slim
136
+ googleapis-common-protos==1.70.0
137
+ # via opentelemetry-exporter-otlp-proto-http
138
+ griffe==1.14.0
139
+ # via pydantic-ai-slim
140
+ groq==0.32.0
141
+ # via pydantic-ai-slim
142
+ h11==0.16.0
143
+ # via
144
+ # httpcore
145
+ # uvicorn
146
+ hf-xet==1.1.10
147
+ # via huggingface-hub
148
+ html-for-docx==1.0.10
149
+ # via resume-generator (pyproject.toml)
150
+ httpcore==1.0.9
151
+ # via httpx
152
+ httpx==0.28.1
153
+ # via
154
+ # anthropic
155
+ # cohere
156
+ # fastmcp
157
+ # genai-prices
158
+ # google-genai
159
+ # groq
160
+ # mcp
161
+ # mistralai
162
+ # openai
163
+ # pydantic-ai-slim
164
+ # pydantic-graph
165
+ httpx-sse==0.4.0
166
+ # via
167
+ # cohere
168
+ # mcp
169
+ huggingface-hub==0.35.3
170
+ # via
171
+ # pydantic-ai-slim
172
+ # tokenizers
173
+ humanfriendly==10.0
174
+ # via coloredlogs
175
+ idna==3.11
176
+ # via
177
+ # anyio
178
+ # email-validator
179
+ # httpx
180
+ # requests
181
+ # yarl
182
+ importlib-metadata==8.7.0
183
+ # via
184
+ # docstring-to-markdown
185
+ # opentelemetry-api
186
+ iniconfig==2.1.0
187
+ # via pytest
188
+ invoke==2.2.1
189
+ # via mistralai
190
+ isodate==0.7.2
191
+ # via openapi-core
192
+ itsdangerous==2.2.0
193
+ # via marimo
194
+ jedi==0.19.2
195
+ # via
196
+ # marimo
197
+ # python-lsp-server
198
+ jiter==0.11.0
199
+ # via
200
+ # anthropic
201
+ # openai
202
+ jmespath==1.0.1
203
+ # via
204
+ # boto3
205
+ # botocore
206
+ jsonschema==4.25.1
207
+ # via
208
+ # mcp
209
+ # openapi-core
210
+ # openapi-schema-validator
211
+ # openapi-spec-validator
212
+ jsonschema-path==0.3.4
213
+ # via
214
+ # openapi-core
215
+ # openapi-spec-validator
216
+ jsonschema-specifications==2025.9.1
217
+ # via
218
+ # jsonschema
219
+ # openapi-schema-validator
220
+ lazy-object-proxy==1.12.0
221
+ # via openapi-spec-validator
222
+ logfire==4.13.2
223
+ # via pydantic-ai-slim
224
+ logfire-api==4.13.2
225
+ # via
226
+ # pydantic-evals
227
+ # pydantic-graph
228
+ loro==1.8.1
229
+ # via marimo
230
+ lxml==6.0.2
231
+ # via
232
+ # markitdown
233
+ # python-docx
234
+ magika==0.6.2
235
+ # via markitdown
236
+ mammoth==1.11.0
237
+ # via markitdown
238
+ marimo==0.17.0
239
+ # via resume-generator (pyproject.toml)
240
+ markdown==3.9
241
+ # via
242
+ # marimo
243
+ # pymdown-extensions
244
+ markdown-it-py==4.0.0
245
+ # via rich
246
+ markdownify==1.2.0
247
+ # via markitdown
248
+ markitdown==0.1.3
249
+ # via resume-generator (pyproject.toml)
250
+ markupsafe==3.0.3
251
+ # via werkzeug
252
+ mcp==1.17.0
253
+ # via
254
+ # resume-generator (pyproject.toml)
255
+ # fastmcp
256
+ # pydantic-ai-slim
257
+ mdurl==0.1.2
258
+ # via markdown-it-py
259
+ mistralai==1.9.11
260
+ # via pydantic-ai-slim
261
+ mistune==3.1.4
262
+ # via resume-generator (pyproject.toml)
263
+ more-itertools==10.8.0
264
+ # via openapi-core
265
+ mpmath==1.3.0
266
+ # via sympy
267
+ msgspec-m==0.19.2
268
+ # via marimo
269
+ multidict==6.7.0
270
+ # via
271
+ # aiohttp
272
+ # yarl
273
+ mypy-extensions==1.1.0
274
+ # via black
275
+ narwhals==2.8.0
276
+ # via marimo
277
+ nest-asyncio==1.6.0
278
+ # via resume-generator (pyproject.toml)
279
+ nexus-rpc==1.1.0
280
+ # via temporalio
281
+ nodejs-wheel-binaries==22.20.0
282
+ # via basedpyright
283
+ numpy==2.3.4
284
+ # via
285
+ # magika
286
+ # onnxruntime
287
+ onnxruntime==1.23.1
288
+ # via magika
289
+ openai==2.3.0
290
+ # via
291
+ # resume-generator (pyproject.toml)
292
+ # pydantic-ai-slim
293
+ openapi-core==0.19.5
294
+ # via fastmcp
295
+ openapi-pydantic==0.5.1
296
+ # via fastmcp
297
+ openapi-schema-validator==0.6.3
298
+ # via
299
+ # openapi-core
300
+ # openapi-spec-validator
301
+ openapi-spec-validator==0.7.2
302
+ # via openapi-core
303
+ opentelemetry-api==1.37.0
304
+ # via
305
+ # opentelemetry-exporter-otlp-proto-http
306
+ # opentelemetry-instrumentation
307
+ # opentelemetry-instrumentation-httpx
308
+ # opentelemetry-sdk
309
+ # opentelemetry-semantic-conventions
310
+ # pydantic-ai-slim
311
+ opentelemetry-exporter-otlp-proto-common==1.37.0
312
+ # via opentelemetry-exporter-otlp-proto-http
313
+ opentelemetry-exporter-otlp-proto-http==1.37.0
314
+ # via logfire
315
+ opentelemetry-instrumentation==0.58b0
316
+ # via
317
+ # logfire
318
+ # opentelemetry-instrumentation-httpx
319
+ opentelemetry-instrumentation-httpx==0.58b0
320
+ # via logfire
321
+ opentelemetry-proto==1.37.0
322
+ # via
323
+ # opentelemetry-exporter-otlp-proto-common
324
+ # opentelemetry-exporter-otlp-proto-http
325
+ opentelemetry-sdk==1.37.0
326
+ # via
327
+ # logfire
328
+ # opentelemetry-exporter-otlp-proto-http
329
+ opentelemetry-semantic-conventions==0.58b0
330
+ # via
331
+ # opentelemetry-instrumentation
332
+ # opentelemetry-instrumentation-httpx
333
+ # opentelemetry-sdk
334
+ opentelemetry-util-http==0.58b0
335
+ # via opentelemetry-instrumentation-httpx
336
+ packaging==25.0
337
+ # via
338
+ # black
339
+ # huggingface-hub
340
+ # marimo
341
+ # onnxruntime
342
+ # opentelemetry-instrumentation
343
+ # pytest
344
+ parse==1.20.2
345
+ # via openapi-core
346
+ parso==0.8.5
347
+ # via jedi
348
+ pathable==0.4.4
349
+ # via jsonschema-path
350
+ pathspec==0.12.1
351
+ # via black
352
+ pdfminer-six==20250506
353
+ # via markitdown
354
+ platformdirs==4.5.0
355
+ # via black
356
+ pluggy==1.6.0
357
+ # via
358
+ # pytest
359
+ # python-lsp-server
360
+ prompt-toolkit==3.0.52
361
+ # via pydantic-ai-slim
362
+ propcache==0.4.1
363
+ # via
364
+ # aiohttp
365
+ # yarl
366
+ protobuf==6.33.0
367
+ # via
368
+ # googleapis-common-protos
369
+ # logfire
370
+ # onnxruntime
371
+ # opentelemetry-proto
372
+ # temporalio
373
+ psutil==7.1.0
374
+ # via marimo
375
+ pyasn1==0.6.1
376
+ # via
377
+ # pyasn1-modules
378
+ # rsa
379
+ pyasn1-modules==0.4.2
380
+ # via google-auth
381
+ pycparser==2.23
382
+ # via cffi
383
+ pydantic==2.12.2
384
+ # via
385
+ # resume-generator (pyproject.toml)
386
+ # ag-ui-protocol
387
+ # anthropic
388
+ # cohere
389
+ # fastmcp
390
+ # genai-prices
391
+ # google-genai
392
+ # groq
393
+ # mcp
394
+ # mistralai
395
+ # openai
396
+ # openapi-pydantic
397
+ # pydantic-ai-slim
398
+ # pydantic-evals
399
+ # pydantic-graph
400
+ # pydantic-settings
401
+ pydantic-ai==1.1.0
402
+ # via resume-generator (pyproject.toml)
403
+ pydantic-ai-slim==1.1.0
404
+ # via
405
+ # pydantic-ai
406
+ # pydantic-evals
407
+ pydantic-core==2.41.4
408
+ # via
409
+ # cohere
410
+ # pydantic
411
+ pydantic-evals==1.1.0
412
+ # via pydantic-ai-slim
413
+ pydantic-graph==1.1.0
414
+ # via pydantic-ai-slim
415
+ pydantic-settings==2.11.0
416
+ # via mcp
417
+ pygments==2.19.2
418
+ # via
419
+ # marimo
420
+ # pytest
421
+ # rich
422
+ pymdown-extensions==10.16.1
423
+ # via marimo
424
+ pyperclip==1.11.0
425
+ # via
426
+ # fastmcp
427
+ # pydantic-ai-slim
428
+ pytest==8.4.2
429
+ # via resume-generator (pyproject.toml)
430
+ python-dateutil==2.9.0.post0
431
+ # via
432
+ # botocore
433
+ # mistralai
434
+ python-docx==1.2.0
435
+ # via
436
+ # resume-generator (pyproject.toml)
437
+ # html-for-docx
438
+ # python-docx-replace
439
+ python-docx-replace==0.4.4
440
+ # via resume-generator (pyproject.toml)
441
+ python-dotenv==1.1.1
442
+ # via
443
+ # resume-generator (pyproject.toml)
444
+ # fastmcp
445
+ # magika
446
+ # pydantic-settings
447
+ python-lsp-jsonrpc==1.1.2
448
+ # via python-lsp-server
449
+ python-lsp-server==1.13.1
450
+ # via resume-generator (pyproject.toml)
451
+ python-multipart==0.0.20
452
+ # via mcp
453
+ pytokens==0.2.0
454
+ # via black
455
+ pyyaml==6.0.3
456
+ # via
457
+ # huggingface-hub
458
+ # jsonschema-path
459
+ # marimo
460
+ # mistralai
461
+ # pydantic-evals
462
+ # pymdown-extensions
463
+ referencing==0.36.2
464
+ # via
465
+ # jsonschema
466
+ # jsonschema-path
467
+ # jsonschema-specifications
468
+ requests==2.32.5
469
+ # via
470
+ # cohere
471
+ # google-genai
472
+ # huggingface-hub
473
+ # jsonschema-path
474
+ # markitdown
475
+ # opentelemetry-exporter-otlp-proto-http
476
+ # pydantic-ai-slim
477
+ rfc3339-validator==0.1.4
478
+ # via openapi-schema-validator
479
+ rich==14.2.0
480
+ # via
481
+ # cyclopts
482
+ # fastmcp
483
+ # logfire
484
+ # pydantic-ai-slim
485
+ # pydantic-evals
486
+ # rich-rst
487
+ rich-rst==1.3.2
488
+ # via cyclopts
489
+ rpds-py==0.27.1
490
+ # via
491
+ # jsonschema
492
+ # referencing
493
+ rsa==4.9.1
494
+ # via google-auth
495
+ ruff==0.14.0
496
+ # via resume-generator (pyproject.toml)
497
+ s3transfer==0.14.0
498
+ # via boto3
499
+ six==1.17.0
500
+ # via
501
+ # markdownify
502
+ # python-dateutil
503
+ # rfc3339-validator
504
+ sniffio==1.3.1
505
+ # via
506
+ # anthropic
507
+ # anyio
508
+ # groq
509
+ # openai
510
+ soupsieve==2.8
511
+ # via beautifulsoup4
512
+ sqlglot==27.27.0
513
+ # via resume-generator (pyproject.toml)
514
+ sse-starlette==3.0.2
515
+ # via mcp
516
+ starlette==0.48.0
517
+ # via
518
+ # marimo
519
+ # mcp
520
+ # pydantic-ai-slim
521
+ sympy==1.14.0
522
+ # via onnxruntime
523
+ temporalio==1.18.0
524
+ # via pydantic-ai-slim
525
+ tenacity==9.1.2
526
+ # via
527
+ # google-genai
528
+ # pydantic-ai-slim
529
+ tokenizers==0.22.1
530
+ # via cohere
531
+ tomlkit==0.13.3
532
+ # via marimo
533
+ tqdm==4.67.1
534
+ # via
535
+ # huggingface-hub
536
+ # openai
537
+ ty==0.0.1a22
538
+ # via resume-generator (pyproject.toml)
539
+ types-protobuf==6.32.1.20250918
540
+ # via temporalio
541
+ types-requests==2.32.4.20250913
542
+ # via cohere
543
+ typing-extensions==4.15.0
544
+ # via
545
+ # anthropic
546
+ # beautifulsoup4
547
+ # cohere
548
+ # docstring-to-markdown
549
+ # google-genai
550
+ # groq
551
+ # huggingface-hub
552
+ # logfire
553
+ # nexus-rpc
554
+ # openai
555
+ # openapi-core
556
+ # opentelemetry-api
557
+ # opentelemetry-exporter-otlp-proto-http
558
+ # opentelemetry-sdk
559
+ # opentelemetry-semantic-conventions
560
+ # pydantic
561
+ # pydantic-core
562
+ # python-docx
563
+ # temporalio
564
+ # typing-inspection
565
+ typing-inspection==0.4.2
566
+ # via
567
+ # mistralai
568
+ # pydantic
569
+ # pydantic-ai-slim
570
+ # pydantic-graph
571
+ # pydantic-settings
572
+ ujson==5.11.0
573
+ # via
574
+ # python-lsp-jsonrpc
575
+ # python-lsp-server
576
+ urllib3==2.5.0
577
+ # via
578
+ # botocore
579
+ # requests
580
+ # types-requests
581
+ uvicorn==0.37.0
582
+ # via
583
+ # marimo
584
+ # mcp
585
+ uvloop==0.21.0
586
+ # via resume-generator (pyproject.toml)
587
+ wcwidth==0.2.14
588
+ # via prompt-toolkit
589
+ websockets==15.0.1
590
+ # via
591
+ # resume-generator (pyproject.toml)
592
+ # google-genai
593
+ # marimo
594
+ werkzeug==3.1.1
595
+ # via openapi-core
596
+ wrapt==1.17.3
597
+ # via
598
+ # opentelemetry-instrumentation
599
+ # opentelemetry-instrumentation-httpx
600
+ yarl==1.22.0
601
+ # via aiohttp
602
+ zipp==3.23.0
603
+ # via importlib-metadata