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.
Files changed (151) hide show
  1. package/AGENTS.md +186 -0
  2. package/LICENSE +21 -0
  3. package/Makefile +136 -0
  4. package/README.md +356 -0
  5. package/bin/chameleon.js +136 -0
  6. package/chameleon.bat +286 -0
  7. package/chameleon.sh +353 -0
  8. package/package.json +39 -0
  9. package/profiles/github_repos.json +1266 -0
  10. package/requirements.txt +5 -0
  11. package/scan.sh +12 -0
  12. package/score.sh +12 -0
  13. package/scripts/__init__.py +1 -0
  14. package/scripts/__main_tui__.py +11 -0
  15. package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
  16. package/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  17. package/scripts/__pycache__/__main_tui__.cpython-313.pyc +0 -0
  18. package/scripts/__pycache__/ats_ghost.cpython-313.pyc +0 -0
  19. package/scripts/__pycache__/cache.cpython-313.pyc +0 -0
  20. package/scripts/__pycache__/config.cpython-312.pyc +0 -0
  21. package/scripts/__pycache__/config.cpython-313.pyc +0 -0
  22. package/scripts/__pycache__/cover_letter.cpython-313.pyc +0 -0
  23. package/scripts/__pycache__/db.cpython-312.pyc +0 -0
  24. package/scripts/__pycache__/db.cpython-313.pyc +0 -0
  25. package/scripts/__pycache__/fonts.cpython-312.pyc +0 -0
  26. package/scripts/__pycache__/fonts.cpython-313.pyc +0 -0
  27. package/scripts/__pycache__/ghost.cpython-313.pyc +0 -0
  28. package/scripts/__pycache__/job_matcher.cpython-312.pyc +0 -0
  29. package/scripts/__pycache__/job_matcher.cpython-313.pyc +0 -0
  30. package/scripts/__pycache__/question.cpython-313.pyc +0 -0
  31. package/scripts/__pycache__/render.cpython-313.pyc +0 -0
  32. package/scripts/__pycache__/review.cpython-313.pyc +0 -0
  33. package/scripts/__pycache__/setup.cpython-312.pyc +0 -0
  34. package/scripts/__pycache__/tailor_brief.cpython-312.pyc +0 -0
  35. package/scripts/__pycache__/tailor_brief.cpython-313.pyc +0 -0
  36. package/scripts/__pycache__/tailor_cv.cpython-312.pyc +0 -0
  37. package/scripts/__pycache__/tailor_cv.cpython-313.pyc +0 -0
  38. package/scripts/__pycache__/tailor_prompts.cpython-312.pyc +0 -0
  39. package/scripts/__pycache__/tailor_prompts.cpython-313.pyc +0 -0
  40. package/scripts/__pycache__/tui_app.cpython-312.pyc +0 -0
  41. package/scripts/__pycache__/tui_app.cpython-313.pyc +0 -0
  42. package/scripts/ats_ghost.py +170 -0
  43. package/scripts/cache.py +63 -0
  44. package/scripts/cli.py +109 -0
  45. package/scripts/config.py +376 -0
  46. package/scripts/cover_letter.py +105 -0
  47. package/scripts/db.py +331 -0
  48. package/scripts/fonts.py +186 -0
  49. package/scripts/ghost.py +88 -0
  50. package/scripts/job_matcher.py +636 -0
  51. package/scripts/job_scanner/__init__.py +8 -0
  52. package/scripts/job_scanner/__main__.py +6 -0
  53. package/scripts/job_scanner/__pycache__/__init__.cpython-312.pyc +0 -0
  54. package/scripts/job_scanner/__pycache__/__init__.cpython-313.pyc +0 -0
  55. package/scripts/job_scanner/__pycache__/__main__.cpython-312.pyc +0 -0
  56. package/scripts/job_scanner/__pycache__/__main__.cpython-313.pyc +0 -0
  57. package/scripts/job_scanner/__pycache__/base.cpython-312.pyc +0 -0
  58. package/scripts/job_scanner/__pycache__/base.cpython-313.pyc +0 -0
  59. package/scripts/job_scanner/__pycache__/browser.cpython-312.pyc +0 -0
  60. package/scripts/job_scanner/__pycache__/browser.cpython-313.pyc +0 -0
  61. package/scripts/job_scanner/__pycache__/cli.cpython-313.pyc +0 -0
  62. package/scripts/job_scanner/__pycache__/registry.cpython-312.pyc +0 -0
  63. package/scripts/job_scanner/__pycache__/registry.cpython-313.pyc +0 -0
  64. package/scripts/job_scanner/__pycache__/scanner.cpython-312.pyc +0 -0
  65. package/scripts/job_scanner/__pycache__/scanner.cpython-313.pyc +0 -0
  66. package/scripts/job_scanner/base.py +197 -0
  67. package/scripts/job_scanner/browser.py +244 -0
  68. package/scripts/job_scanner/cli.py +6 -0
  69. package/scripts/job_scanner/parsers/__init__.py +24 -0
  70. package/scripts/job_scanner/parsers/__pycache__/__init__.cpython-312.pyc +0 -0
  71. package/scripts/job_scanner/parsers/__pycache__/__init__.cpython-313.pyc +0 -0
  72. package/scripts/job_scanner/parsers/__pycache__/adzuna.cpython-312.pyc +0 -0
  73. package/scripts/job_scanner/parsers/__pycache__/adzuna.cpython-313.pyc +0 -0
  74. package/scripts/job_scanner/parsers/__pycache__/arc.cpython-312.pyc +0 -0
  75. package/scripts/job_scanner/parsers/__pycache__/arc.cpython-313.pyc +0 -0
  76. package/scripts/job_scanner/parsers/__pycache__/crypto_careers.cpython-312.pyc +0 -0
  77. package/scripts/job_scanner/parsers/__pycache__/crypto_careers.cpython-313.pyc +0 -0
  78. package/scripts/job_scanner/parsers/__pycache__/dailyremote.cpython-312.pyc +0 -0
  79. package/scripts/job_scanner/parsers/__pycache__/dailyremote.cpython-313.pyc +0 -0
  80. package/scripts/job_scanner/parsers/__pycache__/flexjobs.cpython-313.pyc +0 -0
  81. package/scripts/job_scanner/parsers/__pycache__/himalayas.cpython-312.pyc +0 -0
  82. package/scripts/job_scanner/parsers/__pycache__/himalayas.cpython-313.pyc +0 -0
  83. package/scripts/job_scanner/parsers/__pycache__/hn_hiring.cpython-312.pyc +0 -0
  84. package/scripts/job_scanner/parsers/__pycache__/hn_hiring.cpython-313.pyc +0 -0
  85. package/scripts/job_scanner/parsers/__pycache__/indeed.cpython-313.pyc +0 -0
  86. package/scripts/job_scanner/parsers/__pycache__/jobicy.cpython-312.pyc +0 -0
  87. package/scripts/job_scanner/parsers/__pycache__/jobicy.cpython-313.pyc +0 -0
  88. package/scripts/job_scanner/parsers/__pycache__/jooble.cpython-312.pyc +0 -0
  89. package/scripts/job_scanner/parsers/__pycache__/jooble.cpython-313.pyc +0 -0
  90. package/scripts/job_scanner/parsers/__pycache__/linkedin.cpython-313.pyc +0 -0
  91. package/scripts/job_scanner/parsers/__pycache__/opentoworkremote.cpython-313.pyc +0 -0
  92. package/scripts/job_scanner/parsers/__pycache__/relocate.cpython-312.pyc +0 -0
  93. package/scripts/job_scanner/parsers/__pycache__/relocate.cpython-313.pyc +0 -0
  94. package/scripts/job_scanner/parsers/__pycache__/remote_rocketship.cpython-313.pyc +0 -0
  95. package/scripts/job_scanner/parsers/__pycache__/remoteok.cpython-312.pyc +0 -0
  96. package/scripts/job_scanner/parsers/__pycache__/remoteok.cpython-313.pyc +0 -0
  97. package/scripts/job_scanner/parsers/__pycache__/remotive.cpython-312.pyc +0 -0
  98. package/scripts/job_scanner/parsers/__pycache__/remotive.cpython-313.pyc +0 -0
  99. package/scripts/job_scanner/parsers/__pycache__/web3_career.cpython-313.pyc +0 -0
  100. package/scripts/job_scanner/parsers/__pycache__/wellfound.cpython-313.pyc +0 -0
  101. package/scripts/job_scanner/parsers/__pycache__/weworkremotely.cpython-312.pyc +0 -0
  102. package/scripts/job_scanner/parsers/__pycache__/weworkremotely.cpython-313.pyc +0 -0
  103. package/scripts/job_scanner/parsers/__pycache__/ycomb.cpython-313.pyc +0 -0
  104. package/scripts/job_scanner/parsers/adzuna.py +81 -0
  105. package/scripts/job_scanner/parsers/arc.py +65 -0
  106. package/scripts/job_scanner/parsers/crypto_careers.py +88 -0
  107. package/scripts/job_scanner/parsers/dailyremote.py +91 -0
  108. package/scripts/job_scanner/parsers/flexjobs.py +91 -0
  109. package/scripts/job_scanner/parsers/himalayas.py +47 -0
  110. package/scripts/job_scanner/parsers/hn_hiring.py +94 -0
  111. package/scripts/job_scanner/parsers/indeed.py +98 -0
  112. package/scripts/job_scanner/parsers/jobicy.py +60 -0
  113. package/scripts/job_scanner/parsers/jooble.py +68 -0
  114. package/scripts/job_scanner/parsers/linkedin.py +140 -0
  115. package/scripts/job_scanner/parsers/opentoworkremote.py +53 -0
  116. package/scripts/job_scanner/parsers/relocate.py +95 -0
  117. package/scripts/job_scanner/parsers/remote_rocketship.py +90 -0
  118. package/scripts/job_scanner/parsers/remoteok.py +63 -0
  119. package/scripts/job_scanner/parsers/remotive.py +57 -0
  120. package/scripts/job_scanner/parsers/web3_career.py +92 -0
  121. package/scripts/job_scanner/parsers/wellfound.py +94 -0
  122. package/scripts/job_scanner/parsers/weworkremotely.py +60 -0
  123. package/scripts/job_scanner/parsers/ycomb.py +83 -0
  124. package/scripts/job_scanner/registry.py +65 -0
  125. package/scripts/job_scanner/scanner.py +162 -0
  126. package/scripts/question.py +107 -0
  127. package/scripts/render.py +110 -0
  128. package/scripts/review.py +89 -0
  129. package/scripts/setup.py +139 -0
  130. package/scripts/tailor_brief.py +379 -0
  131. package/scripts/tailor_cv.py +746 -0
  132. package/scripts/tailor_prompts.py +101 -0
  133. package/scripts/tui/__init__.py +1 -0
  134. package/scripts/tui/__pycache__/__init__.cpython-312.pyc +0 -0
  135. package/scripts/tui/__pycache__/__init__.cpython-313.pyc +0 -0
  136. package/scripts/tui/__pycache__/app.cpython-312.pyc +0 -0
  137. package/scripts/tui/__pycache__/app.cpython-313.pyc +0 -0
  138. package/scripts/tui/__pycache__/helpers.cpython-312.pyc +0 -0
  139. package/scripts/tui/__pycache__/helpers.cpython-313.pyc +0 -0
  140. package/scripts/tui/__pycache__/screens.cpython-312.pyc +0 -0
  141. package/scripts/tui/__pycache__/screens.cpython-313.pyc +0 -0
  142. package/scripts/tui/__pycache__/widgets.cpython-312.pyc +0 -0
  143. package/scripts/tui/__pycache__/widgets.cpython-313.pyc +0 -0
  144. package/scripts/tui/app.py +1547 -0
  145. package/scripts/tui/helpers.py +65 -0
  146. package/scripts/tui/screens.py +642 -0
  147. package/scripts/tui/widgets.py +276 -0
  148. package/scripts/tui_app.py +25 -0
  149. package/scripts/wizard.py +109 -0
  150. package/tui.bat +50 -0
  151. 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