chameleon-cv 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +186 -0
- package/LICENSE +21 -0
- package/Makefile +136 -0
- package/README.md +356 -0
- package/bin/chameleon.js +136 -0
- package/chameleon.bat +286 -0
- package/chameleon.sh +353 -0
- package/package.json +39 -0
- package/profiles/github_repos.json +1266 -0
- package/requirements.txt +5 -0
- package/scan.sh +12 -0
- package/score.sh +12 -0
- package/scripts/__init__.py +1 -0
- package/scripts/__main_tui__.py +11 -0
- package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/__pycache__/__main_tui__.cpython-313.pyc +0 -0
- package/scripts/__pycache__/ats_ghost.cpython-313.pyc +0 -0
- package/scripts/__pycache__/cache.cpython-313.pyc +0 -0
- package/scripts/__pycache__/config.cpython-312.pyc +0 -0
- package/scripts/__pycache__/config.cpython-313.pyc +0 -0
- package/scripts/__pycache__/cover_letter.cpython-313.pyc +0 -0
- package/scripts/__pycache__/db.cpython-312.pyc +0 -0
- package/scripts/__pycache__/db.cpython-313.pyc +0 -0
- package/scripts/__pycache__/fonts.cpython-312.pyc +0 -0
- package/scripts/__pycache__/fonts.cpython-313.pyc +0 -0
- package/scripts/__pycache__/ghost.cpython-313.pyc +0 -0
- package/scripts/__pycache__/job_matcher.cpython-312.pyc +0 -0
- package/scripts/__pycache__/job_matcher.cpython-313.pyc +0 -0
- package/scripts/__pycache__/question.cpython-313.pyc +0 -0
- package/scripts/__pycache__/render.cpython-313.pyc +0 -0
- package/scripts/__pycache__/review.cpython-313.pyc +0 -0
- package/scripts/__pycache__/setup.cpython-312.pyc +0 -0
- package/scripts/__pycache__/tailor_brief.cpython-312.pyc +0 -0
- package/scripts/__pycache__/tailor_brief.cpython-313.pyc +0 -0
- package/scripts/__pycache__/tailor_cv.cpython-312.pyc +0 -0
- package/scripts/__pycache__/tailor_cv.cpython-313.pyc +0 -0
- package/scripts/__pycache__/tailor_prompts.cpython-312.pyc +0 -0
- package/scripts/__pycache__/tailor_prompts.cpython-313.pyc +0 -0
- package/scripts/__pycache__/tui_app.cpython-312.pyc +0 -0
- package/scripts/__pycache__/tui_app.cpython-313.pyc +0 -0
- package/scripts/ats_ghost.py +170 -0
- package/scripts/cache.py +63 -0
- package/scripts/cli.py +109 -0
- package/scripts/config.py +376 -0
- package/scripts/cover_letter.py +105 -0
- package/scripts/db.py +331 -0
- package/scripts/fonts.py +186 -0
- package/scripts/ghost.py +88 -0
- package/scripts/job_matcher.py +636 -0
- package/scripts/job_scanner/__init__.py +8 -0
- package/scripts/job_scanner/__main__.py +6 -0
- package/scripts/job_scanner/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/job_scanner/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/job_scanner/__pycache__/__main__.cpython-312.pyc +0 -0
- package/scripts/job_scanner/__pycache__/__main__.cpython-313.pyc +0 -0
- package/scripts/job_scanner/__pycache__/base.cpython-312.pyc +0 -0
- package/scripts/job_scanner/__pycache__/base.cpython-313.pyc +0 -0
- package/scripts/job_scanner/__pycache__/browser.cpython-312.pyc +0 -0
- package/scripts/job_scanner/__pycache__/browser.cpython-313.pyc +0 -0
- package/scripts/job_scanner/__pycache__/cli.cpython-313.pyc +0 -0
- package/scripts/job_scanner/__pycache__/registry.cpython-312.pyc +0 -0
- package/scripts/job_scanner/__pycache__/registry.cpython-313.pyc +0 -0
- package/scripts/job_scanner/__pycache__/scanner.cpython-312.pyc +0 -0
- package/scripts/job_scanner/__pycache__/scanner.cpython-313.pyc +0 -0
- package/scripts/job_scanner/base.py +197 -0
- package/scripts/job_scanner/browser.py +244 -0
- package/scripts/job_scanner/cli.py +6 -0
- package/scripts/job_scanner/parsers/__init__.py +24 -0
- package/scripts/job_scanner/parsers/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/adzuna.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/adzuna.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/arc.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/arc.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/crypto_careers.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/crypto_careers.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/dailyremote.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/dailyremote.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/flexjobs.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/himalayas.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/himalayas.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/hn_hiring.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/hn_hiring.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/indeed.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/jobicy.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/jobicy.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/jooble.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/jooble.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/linkedin.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/opentoworkremote.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/relocate.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/relocate.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/remote_rocketship.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/remoteok.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/remoteok.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/remotive.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/remotive.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/web3_career.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/wellfound.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/weworkremotely.cpython-312.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/weworkremotely.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/__pycache__/ycomb.cpython-313.pyc +0 -0
- package/scripts/job_scanner/parsers/adzuna.py +81 -0
- package/scripts/job_scanner/parsers/arc.py +65 -0
- package/scripts/job_scanner/parsers/crypto_careers.py +88 -0
- package/scripts/job_scanner/parsers/dailyremote.py +91 -0
- package/scripts/job_scanner/parsers/flexjobs.py +91 -0
- package/scripts/job_scanner/parsers/himalayas.py +47 -0
- package/scripts/job_scanner/parsers/hn_hiring.py +94 -0
- package/scripts/job_scanner/parsers/indeed.py +98 -0
- package/scripts/job_scanner/parsers/jobicy.py +60 -0
- package/scripts/job_scanner/parsers/jooble.py +68 -0
- package/scripts/job_scanner/parsers/linkedin.py +140 -0
- package/scripts/job_scanner/parsers/opentoworkremote.py +53 -0
- package/scripts/job_scanner/parsers/relocate.py +95 -0
- package/scripts/job_scanner/parsers/remote_rocketship.py +90 -0
- package/scripts/job_scanner/parsers/remoteok.py +63 -0
- package/scripts/job_scanner/parsers/remotive.py +57 -0
- package/scripts/job_scanner/parsers/web3_career.py +92 -0
- package/scripts/job_scanner/parsers/wellfound.py +94 -0
- package/scripts/job_scanner/parsers/weworkremotely.py +60 -0
- package/scripts/job_scanner/parsers/ycomb.py +83 -0
- package/scripts/job_scanner/registry.py +65 -0
- package/scripts/job_scanner/scanner.py +162 -0
- package/scripts/question.py +107 -0
- package/scripts/render.py +110 -0
- package/scripts/review.py +89 -0
- package/scripts/setup.py +139 -0
- package/scripts/tailor_brief.py +379 -0
- package/scripts/tailor_cv.py +746 -0
- package/scripts/tailor_prompts.py +101 -0
- package/scripts/tui/__init__.py +1 -0
- package/scripts/tui/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/tui/__pycache__/__init__.cpython-313.pyc +0 -0
- package/scripts/tui/__pycache__/app.cpython-312.pyc +0 -0
- package/scripts/tui/__pycache__/app.cpython-313.pyc +0 -0
- package/scripts/tui/__pycache__/helpers.cpython-312.pyc +0 -0
- package/scripts/tui/__pycache__/helpers.cpython-313.pyc +0 -0
- package/scripts/tui/__pycache__/screens.cpython-312.pyc +0 -0
- package/scripts/tui/__pycache__/screens.cpython-313.pyc +0 -0
- package/scripts/tui/__pycache__/widgets.cpython-312.pyc +0 -0
- package/scripts/tui/__pycache__/widgets.cpython-313.pyc +0 -0
- package/scripts/tui/app.py +1547 -0
- package/scripts/tui/helpers.py +65 -0
- package/scripts/tui/screens.py +642 -0
- package/scripts/tui/widgets.py +276 -0
- package/scripts/tui_app.py +25 -0
- package/scripts/wizard.py +109 -0
- package/tui.bat +50 -0
- package/tui.sh +59 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# Chameleon — Resume Tailor
|
|
2
|
+
|
|
3
|
+
Chameleon is an AI project that works with Claude Code, Codex, and OpenCode. It tailors a master resume YAML to a specific job posting, while persisting the intermediate job analysis locally for later reuse. It uses internal agents to analyze the job description, apply changes to YAML, and score a final CV.
|
|
4
|
+
|
|
5
|
+
## Project Goal
|
|
6
|
+
|
|
7
|
+
Given a job posting URL or pasted job description, produce a tailored, ATS-optimized PDF resume derived from a master YAML file. Each tailored resume is saved as a separate YAML under `templates/` alongside the master.
|
|
8
|
+
|
|
9
|
+
## Five Interfaces
|
|
10
|
+
|
|
11
|
+
| Interface | Technology | Entry Point | Best For |
|
|
12
|
+
|-----------|-----------|-------------|----------|
|
|
13
|
+
| **TUI** | Textual (Python) | `./tui.sh` or `python3 -m scripts.tui_app` | Interactive job browsing + tailoring with live diffs |
|
|
14
|
+
| **CLI** | Python argparse | `python3 -m scripts.tailor_cv <jd_text>` | Headless / CI / batch processing |
|
|
15
|
+
| **AI Assistant** | Skills (SKILL.md) | `/chameleon`, `/tailor-cv`, etc. | In-chat tailoring with subagent delegation |
|
|
16
|
+
| **Telegram Bot** | TypeScript (grammY) | `make bot-telegram-dev` | Remote tailoring + job alerts via Telegram |
|
|
17
|
+
| **WhatsApp Bot** | TypeScript (Express) | `make bot-whatsapp-dev` | CV management + job alerts via WhatsApp |
|
|
18
|
+
|
|
19
|
+
See [docs/TUI.md](docs/TUI.md) for full TUI keybinding reference.
|
|
20
|
+
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for component architecture and data flow.
|
|
21
|
+
See [chameleon-bot/README.md](chameleon-bot/README.md) for bot integration setup.
|
|
22
|
+
|
|
23
|
+
## Technology Stack
|
|
24
|
+
|
|
25
|
+
- **RenderCV** — YAML → PDF renderer (Typst backend, no LaTeX required)
|
|
26
|
+
- Install: `make install-tools`
|
|
27
|
+
- Render: `make render FILE=<file>.yaml`
|
|
28
|
+
- Output: PDF, Markdown, HTML, PNG in `output/`
|
|
29
|
+
- **Textual** — Python TUI framework for the terminal UI
|
|
30
|
+
- **SQLite** — Persistent storage for jobs, tailored CVs, scan records
|
|
31
|
+
- **AI assistant skills** — `/chameleon`, `/tailor-cv`, `/score-cv`, `/cover-letter`, `/question`, and `/init-cv` are user-invocable command skills
|
|
32
|
+
- **Subagents** — `analyze-job-posting`, `update-cv-with-job-posting`, and `score-cv-match` run in isolated contexts
|
|
33
|
+
- **OpenCode config** — `opencode.json` at project root registers all slash commands, skill paths, and permissions
|
|
34
|
+
|
|
35
|
+
## Skills vs Agents
|
|
36
|
+
|
|
37
|
+
| Type | What it is | When it runs | Context |
|
|
38
|
+
|------|-----------|-------------|---------|
|
|
39
|
+
| **Command skill** | Instructions in `SKILL.md`, optionally with `disable-model-invocation: true` | Usually when user types `/skill-name` | Shared with main conversation |
|
|
40
|
+
| **Subagent** | Isolated Claude instance with own system prompt | Spawned by the skill | Own isolated context — returns summary to main |
|
|
41
|
+
|
|
42
|
+
**`/chameleon`, `/tailor-cv`, `/score-cv`, `/cover-letter`, `/question`, and `/init-cv`** are command skills: user-triggered, not auto-invoked. They orchestrate the workflow and delegate work to subagents when needed.
|
|
43
|
+
|
|
44
|
+
**`analyze-job-posting`, `update-cv-with-job-posting`, and `score-cv-match`** are subagents: spawned by the skill, run in isolation, return structured output. Isolation keeps large intermediate context (raw HTML, full YAML processing, saved analysis JSON, extracted CV evidence) out of the main thread.
|
|
45
|
+
|
|
46
|
+
## OpenCode Integration
|
|
47
|
+
|
|
48
|
+
The project provides `opencode.json` that registers all 9 slash commands and points to both `.claude/skills/` and `.agents/skills/` directories. All standard tools are pre-authorized (bash, read, edit, glob, grep, task, webfetch, websearch, question, todowrite, skill).
|
|
49
|
+
|
|
50
|
+
## Codex Delegation
|
|
51
|
+
|
|
52
|
+
For Codex, keep the same two-agent split. When the user explicitly wants delegation or subagents, use `spawn_agent` so the raw JD text and YAML editing work stay out of the main thread.
|
|
53
|
+
|
|
54
|
+
- Reuse `.claude/agents/analyze-job-posting.md` as the prompt boundary for the analysis subagent. It should receive only the raw JD text and return the structured analysis fields documented below.
|
|
55
|
+
- Reuse `.claude/agents/update-cv-with-job-posting.md` as the prompt boundary for the editing subagent. It should receive only the structured analysis plus the resolved master YAML path.
|
|
56
|
+
- Reuse `.claude/agents/score-cv-match.md` as the prompt boundary for the scoring subagent. It should receive only the saved analysis JSON, extracted CV evidence, and the fixed scoring rubric.
|
|
57
|
+
- Do the orchestration, CV selection, rendering, and user-facing reporting in the main thread.
|
|
58
|
+
- Do not delegate if the user is only asking questions about the repo or workflow. Delegate when performing an actual tailoring run and isolation helps control context size.
|
|
59
|
+
|
|
60
|
+
## Skill Workflow (`/chameleon`)
|
|
61
|
+
|
|
62
|
+
1. Fetch the job posting URL or read pasted text
|
|
63
|
+
2. Run `analyze-job-posting` on the raw JD text
|
|
64
|
+
3. Save the analysis JSON to `output/job_analyses/<analysis_id>__<company_slug>__<role_slug>.json`
|
|
65
|
+
4. Resolve the source CV
|
|
66
|
+
5. Run `update-cv-with-job-posting` with the saved analysis plus the resolved CV path
|
|
67
|
+
6. Render the tailored YAML and report the generated PDF together with the saved analysis path and analysis ID
|
|
68
|
+
7. Follow the argument handling, file naming, storage, and error rules in `.claude/skills/chameleon/SKILL.md`
|
|
69
|
+
|
|
70
|
+
## Tailor Workflow (`/tailor-cv`)
|
|
71
|
+
|
|
72
|
+
1. Resolve the saved analysis artifact from `output/job_analyses/` by explicit path or analysis ID
|
|
73
|
+
2. Resolve the source CV
|
|
74
|
+
3. Run `update-cv-with-job-posting` with the saved analysis plus the resolved CV path
|
|
75
|
+
4. Render the tailored YAML and report the generated PDF
|
|
76
|
+
5. Follow the argument handling and error rules in `.claude/skills/tailor-cv/SKILL.md`
|
|
77
|
+
|
|
78
|
+
## Score Workflow (`/score-cv`)
|
|
79
|
+
|
|
80
|
+
1. Resolve the saved analysis artifact from `output/job_analyses/` by explicit path or analysis ID
|
|
81
|
+
2. Read the tailored CV YAML and extract structured evidence in the workflow context
|
|
82
|
+
3. Run `score-cv-match` with the saved analysis, extracted CV evidence, and fixed rubric
|
|
83
|
+
4. Report the final score, breakdown, and missing requirements
|
|
84
|
+
5. Follow the argument handling and error rules in `.claude/skills/score-cv/SKILL.md`
|
|
85
|
+
|
|
86
|
+
## CV Initialization Workflow (`/init-cv`)
|
|
87
|
+
|
|
88
|
+
Used when setting up for the first time or when the user provides an updated source resume.
|
|
89
|
+
|
|
90
|
+
1. Accept a PDF or YAML as input
|
|
91
|
+
2. Parse PDF input into RenderCV YAML or validate YAML input
|
|
92
|
+
3. Save the resulting master CV under `templates/`
|
|
93
|
+
4. Render it and confirm the output succeeds
|
|
94
|
+
5. Follow the overwrite, schema, layout, and validation rules in `.claude/skills/init-cv/SKILL.md`
|
|
95
|
+
|
|
96
|
+
## Cover Letter Workflow (`/cover-letter`)
|
|
97
|
+
|
|
98
|
+
Used when the user wants a concise, first-person cover letter tailored to a specific role from a job URL or pasted job description and a real resume YAML, especially when length, tone, greeting, or sign-off constraints matter.
|
|
99
|
+
|
|
100
|
+
1. Accept a job URL or pasted job description
|
|
101
|
+
2. Resolve the source resume from `--resume`, `--cv`, or a clearly relevant tailored YAML from the current thread; if multiple reasonable YAMLs exist and no source is clear, ask the user which one to use instead of guessing
|
|
102
|
+
3. Extract the employer name, role title, product or protocol names, mission or problem-space signals, and the strongest matching resume evidence
|
|
103
|
+
4. Default to exactly 2 paragraphs, start with `Hey,`, end with `Regards,` and `David`, and write in simple, direct first-person language grounded in the selected resume and job description
|
|
104
|
+
5. Lead with the strongest fit and one concrete impact detail when possible; do not turn the letter into a stack list, ATS keyword dump, or generic enthusiasm
|
|
105
|
+
6. Run a final `humanizer` pass while preserving the exact requested structure, greeting, and sign-off
|
|
106
|
+
7. Return only the final cover letter text
|
|
107
|
+
|
|
108
|
+
## Question Workflow (`/question`)
|
|
109
|
+
|
|
110
|
+
Used when the user wants a concise, first-person answer to an application or screening question, grounded in a real resume YAML and specific role context already provided in the thread or via a fresh job description.
|
|
111
|
+
|
|
112
|
+
1. Treat the main argument as the exact question to answer
|
|
113
|
+
2. Accept an optional job URL or pasted job description, or reuse clear role, JD, or tailored-YAML context already present in the thread when it is relevant to the same application
|
|
114
|
+
3. Resolve the strongest available source resume from `--resume`, `--cv`, or the current thread; if multiple reasonable YAMLs exist and no source is clear, ask the user which one to use instead of guessing
|
|
115
|
+
4. Extract what the question is really testing, the strongest role-specific angle, and the best matching resume evidence
|
|
116
|
+
5. Default to exactly 1 paragraph, answer the question directly in the first sentence, and write in simple, direct first-person language grounded in the selected resume, thread context, and job description
|
|
117
|
+
6. Use assertive framing when supported, prefer one strong impact detail over a stack list, and avoid turning the answer into a cover letter, resume summary, or ATS keyword dump
|
|
118
|
+
7. Run a final `humanizer` pass while preserving the exact requested structure and any requested simplicity or casualness
|
|
119
|
+
8. Return only the final answer text
|
|
120
|
+
|
|
121
|
+
## Editing Rules (apply to all agents)
|
|
122
|
+
|
|
123
|
+
These rules are absolute and must never be violated:
|
|
124
|
+
|
|
125
|
+
- **Never fabricate experience.** Only reword or reorder what already exists in the master YAML. Do not invent companies, roles, dates, metrics, or skills.
|
|
126
|
+
- **Preserve all facts.** Company names, job titles, locations, and dates are immutable. Only highlight bullets may be rewritten.
|
|
127
|
+
- **Match the JD's language.** If the JD says "CI/CD pipelines" and the master says "continuous integration", use the JD's phrasing.
|
|
128
|
+
- **Never edit the master YAML during a tailor run.** Always write a new file to `templates/` with the `<username>_<company>_<role>_cv.yaml` naming convention, where `<username>` comes from the selected master CV filename without the trailing `_cv`.
|
|
129
|
+
- **Length constraint.** Keep tailored resumes to 2 pages maximum, ideally. Remove lower-impact bullets before adding new ones if length is at risk.
|
|
130
|
+
- **Persist before reuse.** The `/chameleon` skill saves job analyses under `output/job_analyses/` as JSON before any later tailoring or scoring run can consume them.
|
|
131
|
+
- **Validate before reporting.** The `/chameleon` and `/tailor-cv` skills run `make render` on the tailored YAML after the agent saves it, and confirm it succeeds before reporting the PDF path to the user.
|
|
132
|
+
|
|
133
|
+
## RenderCV YAML Rules
|
|
134
|
+
|
|
135
|
+
- Section keys become section titles (e.g., `technical_skills` → "Technical Skills")
|
|
136
|
+
- Entry types are auto-detected by their fields:
|
|
137
|
+
- `company` field → ExperienceEntry
|
|
138
|
+
- `institution` field → EducationEntry
|
|
139
|
+
- `name` field → NormalEntry
|
|
140
|
+
- `label` field → OneLineEntry
|
|
141
|
+
- One entry type per section — do not mix types
|
|
142
|
+
- Project entries must use plain text names, not Markdown links, and must not include `start_date`, `end_date`, or `date`
|
|
143
|
+
- Markdown is supported inside `highlights` bullets: `**bold**`, `*italic*`, `[links](url)`
|
|
144
|
+
- Do not use `settings.bold_keywords` in this repo. Leave it absent or empty so global auto-bolding does not bleed into fixed sections like certifications.
|
|
145
|
+
- Default classic-theme typography in this repo uses `design.typography.line_spacing: 0.8em`, `design.typography.font_size.body/headline/connections: 9.5pt`, and `design.sections.space_between_regular_entries: 0.2cm` to keep dense resumes closer to two pages without changing section order.
|
|
146
|
+
|
|
147
|
+
## Subagents
|
|
148
|
+
|
|
149
|
+
### `analyze-job-posting`
|
|
150
|
+
|
|
151
|
+
Runs in an isolated context. Responsible solely for extracting structured signal from a job description. No file I/O. Output:
|
|
152
|
+
- `required_skills`, `preferred_skills`
|
|
153
|
+
- `responsibilities`, `ats_keywords`
|
|
154
|
+
- `positioning_signals`, `summary_angle`
|
|
155
|
+
- `seniority`, `role_title`, `company_name`
|
|
156
|
+
|
|
157
|
+
Its output is persisted by the caller as a JSON artifact under `output/job_analyses/`.
|
|
158
|
+
|
|
159
|
+
### `update-cv-with-job-posting`
|
|
160
|
+
|
|
161
|
+
Runs in an isolated context. Receives job analysis + master YAML path. Reads master YAML, writes tailored YAML to `templates/<username>_<company>_<role>_cv.yaml`, and reports the saved path. Does not render — the `/chameleon` skill handles rendering.
|
|
162
|
+
|
|
163
|
+
Only edits: `summary`, `experience` highlights, clearing `settings.bold_keywords` when present, and `skills` section order.
|
|
164
|
+
Never touches: `projects`, `education`, `languages`, certifications, publications, or any other fixed sections.
|
|
165
|
+
|
|
166
|
+
### `score-cv-match`
|
|
167
|
+
|
|
168
|
+
Runs in an isolated context. Receives a saved job analysis JSON artifact, extracted CV evidence, and a fixed scoring rubric. Returns a grounded `0-100` score with a category breakdown, matched evidence, and missing requirements. It does not read or write files.
|
|
169
|
+
|
|
170
|
+
Summary guidance for tailor runs:
|
|
171
|
+
- The summary must read like a resume summary, not a recruiter recommendation, and must not just mirror the JD's stack.
|
|
172
|
+
- Lead with the strongest positioning signal supported by the master CV, such as product mindset, customer impact, ownership, or cross-functional fluency.
|
|
173
|
+
- Use only the most important JD keywords in the summary; keep broader technology coverage in the skills section.
|
|
174
|
+
- The first sentence should establish seniority and the strongest relevant strengths without using reviewer language such as `strong fit`, `should be shortlisted`, or `this candidate`.
|
|
175
|
+
- Connect expertise to customer, business, or real-world impact when the master CV supports that framing.
|
|
176
|
+
- If the master CV and JD support it, surface grounded motivation for the problem space, product, or mission instead of writing a purely technology-led summary.
|
|
177
|
+
- Keep the first paragraph focused on technical positioning and strongest relevant strengths.
|
|
178
|
+
- Use the second paragraph for motivation plus customer, business, or world impact, and anchor it with at least one concrete metric pulled from the master CV when one is available.
|
|
179
|
+
- Write resume summaries in third-person-neutral resume style focused on experience and strengths. Do not use `I`, and avoid pronoun-led phrasing when a direct skills-first sentence is cleaner.
|
|
180
|
+
- Keep the summary to at most 2 paragraphs.
|
|
181
|
+
|
|
182
|
+
## Reference Documentation
|
|
183
|
+
|
|
184
|
+
- YAML structure: https://docs.rendercv.com/user_guide/yaml_input_structure/
|
|
185
|
+
- Entry types: https://docs.rendercv.com/user_guide/yaml_input_structure/cv/
|
|
186
|
+
- Design options: https://docs.rendercv.com/user_guide/yaml_input_structure/design/
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Alecrim
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/Makefile
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
SHELL := /bin/bash
|
|
2
|
+
VENV := .venv
|
|
3
|
+
PY := python3
|
|
4
|
+
PIP := pip3
|
|
5
|
+
|
|
6
|
+
ifeq ($(OS),Windows_NT)
|
|
7
|
+
PY := python
|
|
8
|
+
PIP := pip
|
|
9
|
+
VENV_PYTHON := $(VENV)\Scripts\python.exe
|
|
10
|
+
VENV_PIP := $(VENV)\Scripts\pip.exe
|
|
11
|
+
COPY := copy /Y
|
|
12
|
+
MKDIR := mkdir
|
|
13
|
+
else
|
|
14
|
+
VENV_PYTHON := $(VENV)/bin/python3
|
|
15
|
+
VENV_PIP := $(VENV)/bin/pip3
|
|
16
|
+
COPY := cp
|
|
17
|
+
MKDIR := mkdir -p
|
|
18
|
+
endif
|
|
19
|
+
|
|
20
|
+
install-tools:
|
|
21
|
+
$(PY) -m venv $(VENV)
|
|
22
|
+
$(VENV_PIP) install --quiet "rendercv[full]"
|
|
23
|
+
$(MAKE) launcher
|
|
24
|
+
@echo "rendercv installed."
|
|
25
|
+
|
|
26
|
+
# Create the documented launcher from the maintained source script.
|
|
27
|
+
launcher:
|
|
28
|
+
ifeq ($(OS),Windows_NT)
|
|
29
|
+
$(COPY) chameleon.bat chameleon.cmd
|
|
30
|
+
else
|
|
31
|
+
cp chameleon.sh chameleon
|
|
32
|
+
chmod +x chameleon
|
|
33
|
+
endif
|
|
34
|
+
|
|
35
|
+
# Full setup — install everything needed (venv + deps + Playwright + Chromium)
|
|
36
|
+
setup: launcher
|
|
37
|
+
@$(VENV_PYTHON) -m scripts.setup
|
|
38
|
+
|
|
39
|
+
# Install Playwright browser automation (for LinkedIn/Indeed/Wellfound)
|
|
40
|
+
install-playwright:
|
|
41
|
+
$(PIP) install --quiet playwright
|
|
42
|
+
$(PYTHON) -m playwright install chromium
|
|
43
|
+
@echo "Playwright + Chromium installed. Blocked job sites (LinkedIn, Indeed, etc.) will now work."
|
|
44
|
+
|
|
45
|
+
install-deps:
|
|
46
|
+
$(PIP) install --quiet -r requirements.txt
|
|
47
|
+
@echo "Python deps installed."
|
|
48
|
+
|
|
49
|
+
test:
|
|
50
|
+
$(PYTHON) -m pytest tests/ -v --tb=short $(ARGS)
|
|
51
|
+
|
|
52
|
+
test-coverage:
|
|
53
|
+
$(PYTHON) -m pytest tests/ -v --tb=short --cov=scripts $(ARGS)
|
|
54
|
+
|
|
55
|
+
# Usage: make render FILE=templates/david_alecrim_cv.yaml
|
|
56
|
+
render:
|
|
57
|
+
@test -n "$(FILE)" || (echo "Usage: make render FILE=<path>"; exit 1)
|
|
58
|
+
$(PYTHON) scripts/render.py $(FILE)
|
|
59
|
+
|
|
60
|
+
# ── CLI Targets ──────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
# Tailor CV for a job (text, URL, or file)
|
|
63
|
+
# Usage: make tailor JD="Senior Rust Engineer at Acme..." COMPANY=Acme TITLE=Engineer
|
|
64
|
+
# Usage: make tailor JD=https://jobs.example.com/senior-rust
|
|
65
|
+
# Usage: make tailor JD=jd.txt
|
|
66
|
+
tailor:
|
|
67
|
+
@test -n "$(JD)" || (echo "Usage: make tailor JD=<text_or_url_or_file> [COMPANY=X] [TITLE=Y]"; exit 1)
|
|
68
|
+
./chameleon tailor "$(JD)" $(if $(COMPANY),--company "$(COMPANY)") $(if $(TITLE),--title "$(TITLE)")
|
|
69
|
+
|
|
70
|
+
# Score a job against profile
|
|
71
|
+
# Usage: make score JD="Python developer, Django..."
|
|
72
|
+
score:
|
|
73
|
+
@test -n "$(JD)" || (echo "Usage: make score JD=<text_or_url_or_file>"; exit 1)
|
|
74
|
+
./chameleon score "$(JD)" $(if $(CV),--cv "$(CV)")
|
|
75
|
+
|
|
76
|
+
# Scan job platforms
|
|
77
|
+
# Usage: make scan QUERY="rust engineer" PLATFORMS=remoteok,hn_hiring
|
|
78
|
+
# Usage: make scan-all
|
|
79
|
+
scan:
|
|
80
|
+
./chameleon scan $(if $(QUERY),-q "$(QUERY)") $(if $(PLATFORMS),-p "$(PLATFORMS)") $(if $(LIMIT),-n $(LIMIT))
|
|
81
|
+
|
|
82
|
+
scan-all:
|
|
83
|
+
./chameleon scan --all $(if $(QUERY),-q "$(QUERY)")
|
|
84
|
+
|
|
85
|
+
scan-tier1:
|
|
86
|
+
./chameleon scan --tier1 $(if $(QUERY),-q "$(QUERY)")
|
|
87
|
+
|
|
88
|
+
# List available scanner platforms
|
|
89
|
+
list-platforms:
|
|
90
|
+
./chameleon scan --list-platforms
|
|
91
|
+
|
|
92
|
+
# ── Bot Integration ──────────────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
# Run the RSS scanner bridge
|
|
95
|
+
bot-bridge:
|
|
96
|
+
$(PYTHON) chameleon-bot/run_bridge.py --rss
|
|
97
|
+
|
|
98
|
+
# Run one scan cycle
|
|
99
|
+
bot-scan:
|
|
100
|
+
$(PYTHON) chameleon-bot/run_bridge.py --scan $(ARGS)
|
|
101
|
+
|
|
102
|
+
# Install Telegram bot dependencies
|
|
103
|
+
bot-telegram-setup:
|
|
104
|
+
cd chameleon-bot/opencode-telegram-bot && npm install
|
|
105
|
+
|
|
106
|
+
# Build Telegram bot
|
|
107
|
+
bot-telegram-build:
|
|
108
|
+
cd chameleon-bot/opencode-telegram-bot && npm run build
|
|
109
|
+
|
|
110
|
+
# Run Telegram bot (dev mode)
|
|
111
|
+
bot-telegram-dev:
|
|
112
|
+
cd chameleon-bot/opencode-telegram-bot && npm run dev
|
|
113
|
+
|
|
114
|
+
# Install WhatsApp bot dependencies
|
|
115
|
+
bot-whatsapp-setup:
|
|
116
|
+
cd chameleon-bot/whatsapp-bot && npm install
|
|
117
|
+
|
|
118
|
+
# Build WhatsApp bot
|
|
119
|
+
bot-whatsapp-build:
|
|
120
|
+
cd chameleon-bot/whatsapp-bot && npm run build
|
|
121
|
+
|
|
122
|
+
# Run WhatsApp bot (dev mode)
|
|
123
|
+
bot-whatsapp-dev:
|
|
124
|
+
cd chameleon-bot/whatsapp-bot && npm run dev
|
|
125
|
+
|
|
126
|
+
# Start full bot stack via Docker Compose
|
|
127
|
+
bot-up:
|
|
128
|
+
docker compose -f chameleon-bot/docker-compose.yaml up -d
|
|
129
|
+
|
|
130
|
+
# Stop full bot stack
|
|
131
|
+
bot-down:
|
|
132
|
+
docker compose -f chameleon-bot/docker-compose.yaml down
|
|
133
|
+
|
|
134
|
+
# View bot logs
|
|
135
|
+
bot-logs:
|
|
136
|
+
docker compose -f chameleon-bot/docker-compose.yaml logs -f
|