Skip to content

feat: add CLI for resume analysis via levelup command#62

Merged
MuhammedSenn merged 1 commit into
mainfrom
feat/cli
May 16, 2026
Merged

feat: add CLI for resume analysis via levelup command#62
MuhammedSenn merged 1 commit into
mainfrom
feat/cli

Conversation

@baranylcn
Copy link
Copy Markdown
Member

@baranylcn baranylcn commented May 15, 2026

Closes #2

Summary by CodeRabbit

Release Notes

  • New Features
    • Added a new levelup command-line tool for AI-powered resume analysis
    • Analyze PDF resumes to receive structured analysis output
    • Support for multiple languages and optional role specification
    • Flexible output options (save to file or display in terminal)
    • Clear error messaging for configuration and file issues

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

Walkthrough

This PR introduces a new Typer-based CLI tool that analyzes PDF resumes using the Gemini API. The levelup command validates input files and language support, extracts text from PDFs, calls the Gemini model, parses structured JSON output, and writes results to a file or stdout with explicit error handling.

Changes

Resume Analysis CLI

Layer / File(s) Summary
CLI app structure and language support
levelup/cli.py
Typer app definition, module imports, and a fixed list of supported language codes for resume reports.
PDF and JSON extraction helpers
levelup/cli.py
extract_pdf_text() uses pdfplumber to extract plain text from PDFs; extract_json_from_response() parses JSON from fenced code blocks or raw {...} payloads with regex and error handling.
Resume analysis command
levelup/cli.py
analyze() command validates input file, language, and API key presence; extracts PDF text; configures and invokes Gemini model; normalizes response text; parses JSON; and outputs formatted results to file or stdout with per-error exit codes.
CLI entrypoint and project configuration
levelup/cli.py, pyproject.toml
main() entrypoint runs the Typer app; typer>=0.12.0 dependency added to [project].dependencies; [project.scripts] registers the levelup console command to levelup.cli:main.

Sequence Diagram

sequenceDiagram
  participant User as User/CLI
  participant Validate as Validation
  participant PDFExt as PDF Extractor
  participant Gemini as Gemini API
  participant JSONParse as JSON Parser
  participant Output as Output Handler
  User->>Validate: analyze(resume, language, role, output)
  Validate->>Validate: check file exists
  Validate->>Validate: check language supported
  Validate->>Validate: check API key set
  Validate->>PDFExt: extract_pdf_text(resume)
  PDFExt->>User: plain text content
  Validate->>Gemini: call gemini-2.0-flash-lite
  Gemini->>User: JSON response
  User->>JSONParse: extract_json_from_response()
  JSONParse->>User: parsed JSON object
  User->>Output: write to file or stdout
  Output->>User: success or error exit
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit hops through CLI land,
With Typer's grace, resume in hand—
PDFs parsed, Gemini called,
JSON extracted, neatly installed!
Level up! The CLI stands tall. 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add CLI for resume analysis via levelup command' directly and clearly describes the main change: adding a new CLI interface for resume analysis.
Linked Issues check ✅ Passed The PR closes issue #2 ('add cli'). The code changes implement a complete CLI feature with the levelup command for resume analysis, meeting the objective.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the CLI feature: new CLI module, dependency addition, and console script entry point are all in-scope.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cli

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@levelup/cli.py`:
- Around line 47-48: The current greedy regex that sets block = match.group(0)
if match else None can over-match; replace it with an iterative JSON boundary
detection: find the first '{' in raw (return None if none), then iterate from
that start index to each subsequent '}' building candidate substrings, try
json.loads(candidate) for each, and when a candidate successfully parses to a
dict return that result; if none parse return None. Update the logic that
assigns block/result to use this approach (references: variable raw, block and
the parsing branch that currently uses re.search) so trailing braces in raw no
longer corrupt the extracted JSON.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a0fe4e7b-58f1-4322-8582-9a6a68b3e3f5

📥 Commits

Reviewing files that changed from the base of the PR and between 7f9ee77 and ba9631c.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • levelup/cli.py
  • pyproject.toml

Comment thread levelup/cli.py
Comment on lines +47 to +48
match = re.search(r"\{[\s\S]*\}", raw)
block = match.group(0) if match else None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Greedy regex may over-match if trailing text contains braces.

The pattern \{[\s\S]*\} matches from the first { to the last } in the string. If the LLM response includes trailing text with braces (e.g., {"score": 90} Note: see {docs}), this captures invalid content and causes parsing to fail or return corrupted data.

Consider using a balanced-brace approach or iteratively trying to parse progressively smaller substrings:

Proposed fix: iterative JSON boundary detection
 def _extract_json(raw: str) -> dict | None:
     fence = re.search(r"```(?:json)?\s*({[\s\S]*?})\s*```", raw, re.IGNORECASE)
     if fence:
         block = fence.group(1)
     else:
-        match = re.search(r"\{[\s\S]*\}", raw)
-        block = match.group(0) if match else None
+        # Find first '{' and try parsing from there to each subsequent '}'
+        start = raw.find("{")
+        if start == -1:
+            return None
+        block = None
+        for i, ch in enumerate(raw[start:], start):
+            if ch == "}":
+                candidate = raw[start : i + 1]
+                try:
+                    result = json.loads(candidate)
+                    if isinstance(result, dict):
+                        return result
+                except json.JSONDecodeError:
+                    continue
+        return None
     if not block:
         return None
     try:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@levelup/cli.py` around lines 47 - 48, The current greedy regex that sets
block = match.group(0) if match else None can over-match; replace it with an
iterative JSON boundary detection: find the first '{' in raw (return None if
none), then iterate from that start index to each subsequent '}' building
candidate substrings, try json.loads(candidate) for each, and when a candidate
successfully parses to a dict return that result; if none parse return None.
Update the logic that assigns block/result to use this approach (references:
variable raw, block and the parsing branch that currently uses re.search) so
trailing braces in raw no longer corrupt the extracted JSON.

@MuhammedSenn MuhammedSenn merged commit 5e5514c into main May 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add cli

2 participants