arthrod commited on
Commit
ea75269
·
1 Parent(s): 83032d7
Dockerfile CHANGED
@@ -1,19 +1,21 @@
1
- FROM python:3.12
2
- COPY --from=ghcr.io/astral-sh/uv:0.4.20 /uv /bin/uv
3
-
4
- RUN useradd -m -u 1000 user
5
- ENV PATH="/home/user/.local/bin:$PATH"
6
- ENV UV_SYSTEM_PYTHON=1
7
 
8
  WORKDIR /app
9
 
10
- COPY --chown=user ./requirements.txt requirements.txt
11
- RUN uv pip install -r requirements.txt
 
 
 
 
 
 
 
 
 
 
12
 
13
- COPY --chown=user . /app
14
- RUN mkdir -p /app/__marimo__ && \
15
- chown -R user:user /app && \
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
 
1
+ FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
 
 
 
 
 
2
 
3
  WORKDIR /app
4
 
5
+ # Create a non-root user
6
+ RUN useradd -m appuser
7
+
8
+ # Copy application files
9
+ COPY --chown=appuser:appuser . /app
10
+
11
+ # Switch to non-root user
12
+ USER appuser
13
+
14
+ # Create virtual environment and install dependencies
15
+ RUN uv venv
16
+ RUN uv export --script _server/main.py | uv pip install -r -
17
 
18
+ ENV PORT=7860
19
+ EXPOSE 7860
 
 
 
20
 
21
+ CMD ["uv", "run", "_server/main.py"]
_server/main.py ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.12"
3
+ # dependencies = [
4
+ # ag-ui-protocol==0.1.9
5
+ # aiohappyeyeballs==2.6.1
6
+ # aiohttp==3.13.0
7
+ # aiosignal==1.4.0
8
+ # annotated-types==0.7.0
9
+ # anthropic==0.70.0
10
+ # anyio==4.11.0
11
+ # argcomplete==3.6.2
12
+ # attrs==25.4.0
13
+ # authlib==1.6.5
14
+ # beautifulsoup4==4.14.2
15
+ # black==25.9.0
16
+ # boto3==1.40.53
17
+ # botocore==1.40.53
18
+ # cachetools==6.2.1
19
+ # certifi==2025.10.5
20
+ # cffi==2.0.0
21
+ # charset-normalizer==3.4.4
22
+ # click==8.3.0
23
+ # cobble==0.1.4
24
+ # cohere==5.19.0
25
+ # colorama==0.4.6
26
+ # coloredlogs==15.0.1
27
+ # cryptography==46.0.3
28
+ # cyclopts==3.24.0
29
+ # defusedxml==0.7.1
30
+ # distro==1.9.0
31
+ # dnspython==2.8.0
32
+ # docstring-parser==0.17.0
33
+ # docstring-to-markdown==0.17
34
+ # docutils==0.22.2
35
+ # email-validator==2.3.0
36
+ # eval-type-backport==0.2.2
37
+ # exceptiongroup==1.3.0
38
+ # executing==2.2.1
39
+ # fastapi>=0.119.0
40
+ # fastavro==1.12.1
41
+ # fastmcp==2.12.4
42
+ # filelock==3.20.0
43
+ # flatbuffers==25.9.23
44
+ # frozenlist==1.8.0
45
+ # fsspec==2025.9.0
46
+ # genai-prices==0.0.32
47
+ # google-auth==2.41.1
48
+ # google-genai==1.45.0
49
+ # googleapis-common-protos==1.70.0
50
+ # griffe==1.14.0
51
+ # groq==0.32.0
52
+ # h11==0.16.0
53
+ # hf-xet==1.1.10
54
+ # html-for-docx==1.0.10
55
+ # httpcore==1.0.9
56
+ # httpx==0.28.1
57
+ # httpx-sse==0.4.0
58
+ # huggingface-hub==0.35.3
59
+ # humanfriendly==10.0
60
+ # idna==3.11
61
+ # importlib-metadata==8.7.0
62
+ # invoke==2.2.1
63
+ # isodate==0.7.2
64
+ # itsdangerous==2.2.0
65
+ # jedi==0.19.2
66
+ # jiter==0.11.0
67
+ # jmespath==1.0.1
68
+ # jsonschema==4.25.1
69
+ # jsonschema-path==0.3.4
70
+ # jsonschema-specifications==2025.9.1
71
+ # lazy-object-proxy==1.12.0
72
+ # logfire==4.13.2
73
+ # logfire-api==4.13.2
74
+ # loro==1.8.1
75
+ # lxml==6.0.2
76
+ # magika==0.6.2
77
+ # mammoth==1.11.0
78
+ # marimo==0.17.0
79
+ # markdown==3.9
80
+ # markdown-it-py==4.0.0
81
+ # markdownify==1.2.0
82
+ # markitdown>=0.1.2
83
+ # markupsafe==3.0.3
84
+ # mcp==1.17.0
85
+ # mdurl==0.1.2
86
+ # mistralai==1.9.11
87
+ # mistune==3.1.4
88
+ # more-itertools==10.8.0
89
+ # mpmath==1.3.0
90
+ # msgspec-m==0.19.2
91
+ # multidict==6.7.0
92
+ # mypy-extensions==1.1.0
93
+ # narwhals==2.8.0
94
+ # nest-asyncio==1.6.0
95
+ # nexus-rpc==1.1.0
96
+ # numpy==2.3.4
97
+ # onnxruntime==1.23.1
98
+ # openai==2.3.0
99
+ # openapi-core==0.19.5
100
+ # openapi-pydantic==0.5.1
101
+ # openapi-schema-validator==0.6.3
102
+ # openapi-spec-validator==0.7.2
103
+ # opentelemetry-api==1.37.0
104
+ # opentelemetry-exporter-otlp-proto-common==1.37.0
105
+ # opentelemetry-exporter-otlp-proto-http==1.37.0
106
+ # opentelemetry-instrumentation==0.58b0
107
+ # opentelemetry-instrumentation-httpx==0.58b0
108
+ # opentelemetry-proto==1.37.0
109
+ # opentelemetry-sdk==1.37.0
110
+ # opentelemetry-semantic-conventions==0.58b0
111
+ # opentelemetry-util-http==0.58b0
112
+ # packaging==25.0
113
+ # parse==1.20.2
114
+ # parso==0.8.5
115
+ # pathable==0.4.4
116
+ # pathspec==0.12.1
117
+ # pdfminer-six==20250506
118
+ # platformdirs==4.5.0
119
+ # pluggy==1.6.0
120
+ # prompt-toolkit==3.0.52
121
+ # propcache==0.4.1
122
+ # protobuf==6.33.0
123
+ # psutil==7.1.0
124
+ # pyasn1==0.6.1
125
+ # pyasn1-modules==0.4.2
126
+ # pycparser==2.23
127
+ # pydantic==2.12.2
128
+ # pydantic-ai==1.1.0
129
+ # pydantic-ai-slim==1.1.0
130
+ # pydantic-core==2.41.4
131
+ # pydantic-evals==1.1.0
132
+ # pydantic-graph==1.1.0
133
+ # pydantic-settings==2.11.0
134
+ # pygments==2.19.2
135
+ # pymdown-extensions==10.16.1
136
+ # pyperclip==1.11.0
137
+ # python-dateutil==2.9.0.post0
138
+ # python-docx==1.2.0
139
+ # python-docx-replace==0.4.4
140
+ # python-dotenv==1.1.1
141
+ # python-lsp-jsonrpc==1.1.2
142
+ # python-lsp-server==1.13.1
143
+ # python-multipart==0.0.20
144
+ # pytokens==0.2.0
145
+ # pyyaml==6.0.3
146
+ # referencing==0.36.2
147
+ # requests==2.32.5
148
+ # rfc3339-validator==0.1.4
149
+ # rich==14.2.0
150
+ # rich-rst==1.3.2
151
+ # rpds-py==0.27.1
152
+ # rsa==4.9.1
153
+ # s3transfer==0.14.0
154
+ # six==1.17.0
155
+ # sniffio==1.3.1
156
+ # soupsieve==2.8
157
+ # sse-starlette==3.0.2
158
+ # starlette==0.48.0
159
+ # sympy==1.14.0
160
+ # temporalio==1.18.0
161
+ # tenacity==9.1.2
162
+ # tokenizers==0.22.1
163
+ # tomlkit==0.13.3
164
+ # tqdm==4.67.1
165
+ # types-protobuf==6.32.1.20250918
166
+ # types-requests==2.32.4.20250913
167
+ # typing-extensions==4.15.0
168
+ # typing-inspection==0.4.2
169
+ # ujson==5.11.0
170
+ # urllib3==2.5.0
171
+ # uvicorn==0.37.0
172
+ # uvloop==0.21.0
173
+ # wcwidth==0.2.14
174
+ # websockets==15.0.1
175
+ # werkzeug==3.1.1
176
+ # wrapt==1.17.3
177
+ # yarl==1.22.0
178
+ # zipp==3.23.0
179
+ # ]
180
+ # ///
181
+
182
+ import logging
183
+ import os
184
+ from pathlib import Path
185
+
186
+ import marimo
187
+ from dotenv import load_dotenv
188
+ from fastapi import FastAPI, Request
189
+ from fastapi.responses import HTMLResponse
190
+
191
+ # Load environment variables
192
+ load_dotenv()
193
+
194
+ # Set up logging
195
+ logging.basicConfig(level=logging.INFO)
196
+ logger = logging.getLogger(__name__)
197
+
198
+ # Get port from environment variable or use default
199
+ PORT = int(os.environ.get('PORT', 7860))
200
+
201
+ root_dir = Path(__file__).parent.parent
202
+
203
+ ROOTS = [root_dir / 'cicero_jobs']
204
+
205
+
206
+ server = marimo.create_asgi_app(include_code=True)
207
+ app_names: list[str] = []
208
+
209
+ for root in ROOTS:
210
+ for filename in root.iterdir():
211
+ if filename.is_file() and filename.suffix == '.py':
212
+ app_path = root.stem + '/' + filename.stem
213
+ server = server.with_app(path=f'/{app_path}', root=str(filename))
214
+ app_names.append(app_path)
215
+
216
+ # Create a FastAPI app
217
+ app = FastAPI()
218
+
219
+ logger.info(f'Mounting {len(app_names)} apps')
220
+ for app_name in app_names:
221
+ logger.info(' /%s', app_name)
222
+
223
+
224
+ @app.get('/')
225
+ async def home(request: Request):
226
+ html_content = """
227
+ <!DOCTYPE html>
228
+ <html>
229
+ <head>
230
+ <title>CICERO</title>
231
+ </head>
232
+ <body>
233
+ <h1>Welcome to Cicero Jobs!</h1>
234
+ <p>Cicero will get you a job.</p>
235
+ </body>
236
+ </html>
237
+ """
238
+ return HTMLResponse(content=html_content)
239
+
240
+
241
+ app.mount('/', server.build())
242
+
243
+ # Run the server
244
+ if __name__ == '__main__':
245
+ import uvicorn
246
+
247
+ uvicorn.run(app, host='0.0.0.0', port=PORT, log_level='info')
app.py → cicero_jobs/app.py RENAMED
@@ -1,23 +1,51 @@
 
 
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
  from pydantic import BaseModel, Field
18
- from pydantic_ai import Agent, BinaryContent, DocumentUrl
19
 
20
- return Agent, BaseModel, BinaryContent, DocumentUrl, Field
21
 
22
 
23
  @app.function
@@ -185,7 +213,8 @@ def _(BaseModel, Field):
185
  """Structured output for resume and cover letter generation."""
186
 
187
  resume: str = Field(
188
- ..., description='Markdown Richly Formatted resume highlighting relevant experience for the job. 2 pages MAX, aim for 1.'
 
189
  )
190
  cover_letter: str = Field(
191
  ..., description='Markdown Richly Formatted and Compelling, personalized cover letter (3-4 paragraphs)'
@@ -262,15 +291,14 @@ def _(continue_render) -> None:
262
 
263
 
264
  @app.cell
265
- def _(job_posting_area, job_url, mo):
266
  if job_posting_area.value != '':
267
- buceta = 'Vamo'
268
  job_posting = job_posting_area.value
 
269
  elif job_posting_area.value == '' and job_url.value == '':
270
  buceta = None
271
  job_posting = None
272
  elif job_posting_area.value == '' and job_url.value != '':
273
- buceta = 'Vamo'
274
  import httpx
275
 
276
  url = f'https://r.jina.ai/{job_url.value}'
@@ -280,9 +308,11 @@ def _(job_posting_area, job_url, mo):
280
  headers = {'Authorization': f'Bearer {jina_api_key}'}
281
  response = httpx.get(url, headers=headers)
282
  job_posting = response.text
283
-
284
- mo.stop(buceta is None, mo.callout(mo.md('⚠️ **Please provide your materials before continuing!**'), kind='danger'))
285
- return (job_posting,)
 
 
286
 
287
 
288
  @app.cell
@@ -300,16 +330,23 @@ def _(resume_button):
300
  forcing_bytes = io.BytesIO(resume_contents)
301
  docx_converted = md.convert(forcing_bytes)
302
  resume_complete = str(docx_converted.text_content)
 
303
  except Exception:
304
  if isinstance(resume_contents, (bytes, bytearray)):
305
  resume_complete = io.BytesIO(resume_contents)
 
306
  else:
307
  resume_complete = resume_contents
 
308
  else:
309
  resume_contents = ''
 
310
  except Exception:
311
  resume_contents = ''
312
- return io, resume_complete
 
 
 
313
 
314
 
315
  @app.cell
@@ -319,48 +356,21 @@ def _(job_posting, resume_complete):
319
 
320
 
321
  @app.cell
322
- def _(BinaryContent, DocumentUrl, job_posting_area, job_url):
323
- job_posting_complete = ''
324
- job_posting_mime = 'text/plain'
325
- is_url = False
326
-
327
- job_area_value = job_posting_area.value if hasattr(job_posting_area, 'value') else None
328
- job_url_value = job_url.value if hasattr(job_url, 'value') else None
329
-
330
- if job_area_value:
331
- job_posting_complete = job_area_value
332
- job_posting_mime = 'text/plain'
333
- is_url = False
334
- elif job_url_value:
335
- job_posting_complete = job_url_value.strip()
336
- # Basic URL detection
337
- if job_posting_complete.lower().startswith(('http://', 'https://')):
338
- is_url = True
339
- job_posting_mime = None
340
- else:
341
- # If user provided something in the URL field that's not an HTTP URL, treat it as plain text
342
- is_url = False
343
- job_posting_mime = 'text/plain'
344
- else:
345
- job_posting_complete = ''
346
- job_posting_mime = 'text/plain'
347
- is_url = False
348
-
349
- # Build inputs for the agent
350
- if is_url:
351
- job_posting_input = DocumentUrl(job_posting_complete)
352
- else:
353
- job_posting_input = BinaryContent(data=job_posting_complete, media_type=job_posting_mime or 'text/plain')
354
- return job_posting_input
355
 
356
 
357
  @app.cell
358
- def _(resume_complete) -> None:
359
- return
 
 
360
 
361
 
362
  @app.cell
363
- def _(Agent, ApplicationMaterials, final_prompt):
 
364
  import asyncio
365
 
366
  import nest_asyncio
@@ -370,8 +380,7 @@ def _(Agent, ApplicationMaterials, final_prompt):
370
  nest_asyncio.apply()
371
 
372
  career_agent = Agent('gemini-2.5-flash', output_type=ApplicationMaterials)
373
- if final_prompt is not None:
374
- result = asyncio.run(career_agent.run(final_prompt))
375
  return (result,)
376
 
377
 
@@ -548,7 +557,7 @@ def _(markdown_to_docx_bytes, mo, processed_data) -> None:
548
 
549
  @app.cell
550
  def _(mo) -> None:
551
- mo.md('# <center>Time to do another application! Just add new materials. You got this!</center>')
552
 
553
 
554
  if __name__ == '__main__':
 
1
+ import contextlib
2
+
3
  import marimo
4
 
5
  __generated_with = '0.17.0'
6
  app = marimo.App()
7
 
8
+ with app.setup:
9
+ import subprocess
10
+ import sys
11
+
12
+ with contextlib.suppress(Exception):
13
+ subprocess.run([sys.executable, '-m', 'pip', 'install', 'uv'], check=True)
14
+
15
+ with contextlib.suppress(Exception):
16
+ subprocess.run([sys.executable, '-m', 'uv', 'pip', 'install', '-r', 'requirements.txt'], check=True)
17
+
18
 
19
  @app.cell
20
  def _():
21
  import marimo as mo
22
 
23
+ mo.md("""
24
+ # <center>Cicero Jobs</center>
25
+
26
+ ## How to Use This App:
27
+
28
+ 1. **Upload Your Resume**: Drop a PDF/DOC/DOCX/TXT file or paste your resume text
29
+ 2. **Add Job Posting**: Paste the job description or provide the job posting URL
30
+ 3. **Click Submit**: Wait while AI generates your materials (takes about 30-60 seconds)
31
+ 4. **Review & Download**: Preview all documents and download as Word files
32
+
33
+ ## What You'll Get:
34
+
35
+ - **Tailored Resume**: Your resume optimized with keywords from the job posting
36
+ - **Cover Letter**: Personalized 3-4 paragraph letter highlighting relevant experience
37
+ - **Recruiter Message**: Professional 5-7 sentence outreach message for LinkedIn/email
38
+ - **Application Tips**: 5-7 actionable tips specific to this job application
39
+ """)
40
  return (mo,)
41
 
42
 
43
  @app.cell
44
  def _():
45
  from pydantic import BaseModel, Field
46
+ from pydantic_ai import Agent
47
 
48
+ return Agent, BaseModel, Field
49
 
50
 
51
  @app.function
 
213
  """Structured output for resume and cover letter generation."""
214
 
215
  resume: str = Field(
216
+ ...,
217
+ description='Markdown Richly Formatted resume highlighting relevant experience for the job. 2 pages MAX, aim for 1.',
218
  )
219
  cover_letter: str = Field(
220
  ..., description='Markdown Richly Formatted and Compelling, personalized cover letter (3-4 paragraphs)'
 
291
 
292
 
293
  @app.cell
294
+ def _(job_posting_area, job_url):
295
  if job_posting_area.value != '':
 
296
  job_posting = job_posting_area.value
297
+ buceta = True
298
  elif job_posting_area.value == '' and job_url.value == '':
299
  buceta = None
300
  job_posting = None
301
  elif job_posting_area.value == '' and job_url.value != '':
 
302
  import httpx
303
 
304
  url = f'https://r.jina.ai/{job_url.value}'
 
308
  headers = {'Authorization': f'Bearer {jina_api_key}'}
309
  response = httpx.get(url, headers=headers)
310
  job_posting = response.text
311
+ buceta = True
312
+ else:
313
+ buceta = None
314
+ job_posting = None
315
+ return buceta, job_posting
316
 
317
 
318
  @app.cell
 
330
  forcing_bytes = io.BytesIO(resume_contents)
331
  docx_converted = md.convert(forcing_bytes)
332
  resume_complete = str(docx_converted.text_content)
333
+ buceta_ = True
334
  except Exception:
335
  if isinstance(resume_contents, (bytes, bytearray)):
336
  resume_complete = io.BytesIO(resume_contents)
337
+ buceta_ = True
338
  else:
339
  resume_complete = resume_contents
340
+ buceta_ = True
341
  else:
342
  resume_contents = ''
343
+ buceta_ = None
344
  except Exception:
345
  resume_contents = ''
346
+ buceta_ = None
347
+ if resume_contents == '' or resume_contents is None:
348
+ resume_complete = None
349
+ return buceta_, io, resume_complete
350
 
351
 
352
  @app.cell
 
356
 
357
 
358
  @app.cell
359
+ def _(buceta, buceta_):
360
+ bucetilda = buceta_ and buceta
361
+ return (bucetilda,)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
 
363
 
364
  @app.cell
365
+ def _(bucetilda, mo) -> None:
366
+ mo.stop(
367
+ bucetilda is None, mo.callout(mo.md('⚠️ **Please provide your materials before continuing!**'), kind='danger')
368
+ )
369
 
370
 
371
  @app.cell
372
+ def _(Agent, ApplicationMaterials, bucetilda, final_prompt, mo):
373
+ mo.stop(bucetilda is None)
374
  import asyncio
375
 
376
  import nest_asyncio
 
380
  nest_asyncio.apply()
381
 
382
  career_agent = Agent('gemini-2.5-flash', output_type=ApplicationMaterials)
383
+ result = asyncio.run(career_agent.run(final_prompt)) if final_prompt is not None else None
 
384
  return (result,)
385
 
386
 
 
557
 
558
  @app.cell
559
  def _(mo) -> None:
560
+ mo.md("""# <center>Time to do another application! Just add new materials. You got this!</center>""")
561
 
562
 
563
  if __name__ == '__main__':
{layouts → cicero_jobs/layouts}/app.slides.json RENAMED
File without changes
pyproject.toml CHANGED
@@ -40,6 +40,7 @@ dependencies = [
40
  "eval-type-backport==0.2.2",
41
  "exceptiongroup==1.3.0",
42
  "executing==2.2.1",
 
43
  "fastavro==1.12.1",
44
  "fastmcp==2.12.4",
45
  "filelock==3.20.0",
@@ -180,3 +181,11 @@ dependencies = [
180
  "yarl==1.22.0",
181
  "zipp==3.23.0",
182
  ]
 
 
 
 
 
 
 
 
 
40
  "eval-type-backport==0.2.2",
41
  "exceptiongroup==1.3.0",
42
  "executing==2.2.1",
43
+ "fastapi>=0.119.0",
44
  "fastavro==1.12.1",
45
  "fastmcp==2.12.4",
46
  "filelock==3.20.0",
 
181
  "yarl==1.22.0",
182
  "zipp==3.23.0",
183
  ]
184
+
185
+ [dependency-groups]
186
+ dev = [
187
+ "basedpyright>=1.31.7",
188
+ "loro>=1.8.1",
189
+ "ruff>=0.14.1",
190
+ "ty>=0.0.1a23",
191
+ ]
requirements.txt CHANGED
@@ -1,6 +1,5 @@
1
- httpx
2
- python_dotenv
3
- marimo
4
  ag-ui-protocol==0.1.9
5
  # via
6
  # cicero-job-apps (pyproject.toml)
@@ -167,6 +166,8 @@ executing==2.2.1
167
  # via
168
  # cicero-job-apps (pyproject.toml)
169
  # logfire
 
 
170
  fastavro==1.12.1
171
  # via
172
  # cicero-job-apps (pyproject.toml)
@@ -559,6 +560,7 @@ pydantic==2.12.2
559
  # ag-ui-protocol
560
  # anthropic
561
  # cohere
 
562
  # fastmcp
563
  # genai-prices
564
  # google-genai
@@ -720,6 +722,7 @@ sse-starlette==3.0.2
720
  starlette==0.48.0
721
  # via
722
  # cicero-job-apps (pyproject.toml)
 
723
  # marimo
724
  # mcp
725
  # pydantic-ai-slim
@@ -767,6 +770,7 @@ typing-extensions==4.15.0
767
  # cohere
768
  # docstring-to-markdown
769
  # exceptiongroup
 
770
  # google-genai
771
  # groq
772
  # huggingface-hub
 
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
5
  # cicero-job-apps (pyproject.toml)
 
166
  # via
167
  # cicero-job-apps (pyproject.toml)
168
  # logfire
169
+ fastapi==0.119.0
170
+ # via cicero-job-apps (pyproject.toml)
171
  fastavro==1.12.1
172
  # via
173
  # cicero-job-apps (pyproject.toml)
 
560
  # ag-ui-protocol
561
  # anthropic
562
  # cohere
563
+ # fastapi
564
  # fastmcp
565
  # genai-prices
566
  # google-genai
 
722
  starlette==0.48.0
723
  # via
724
  # cicero-job-apps (pyproject.toml)
725
+ # fastapi
726
  # marimo
727
  # mcp
728
  # pydantic-ai-slim
 
770
  # cohere
771
  # docstring-to-markdown
772
  # exceptiongroup
773
+ # fastapi
774
  # google-genai
775
  # groq
776
  # huggingface-hub