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/chameleon.bat
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal enabledelayedexpansion
|
|
3
|
+
|
|
4
|
+
REM Chameleon CLI — unified entry point for Windows
|
|
5
|
+
set "CHAMELEON_DIR=%~dp0"
|
|
6
|
+
set "VENV_PYTHON=%CHAMELEON_DIR%.venv\Scripts\python.exe"
|
|
7
|
+
|
|
8
|
+
REM Resolve Python
|
|
9
|
+
set "PYTHON_BIN="
|
|
10
|
+
if exist "%VENV_PYTHON%" (
|
|
11
|
+
set "PYTHON_BIN=%VENV_PYTHON%"
|
|
12
|
+
) else (
|
|
13
|
+
where python >nul 2>&1 && set "PYTHON_BIN=python"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if "%PYTHON_BIN%"=="" (
|
|
17
|
+
echo [!] No Python found. Install Python 3.10+ and run: make install-tools
|
|
18
|
+
exit /b 1
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if "%~1"=="" goto :help
|
|
22
|
+
if /I "%~1"=="help" goto :help
|
|
23
|
+
if /I "%~1"=="--help" goto :help
|
|
24
|
+
if /I "%~1"=="-h" goto :help
|
|
25
|
+
if /I "%~1"=="--version" goto :version
|
|
26
|
+
if /I "%~1"=="-v" goto :version
|
|
27
|
+
|
|
28
|
+
if /I "%~1"=="tailor" goto :tailor
|
|
29
|
+
if /I "%~1"=="tailor-cv" goto :tailor
|
|
30
|
+
if /I "%~1"=="score" goto :score
|
|
31
|
+
if /I "%~1"=="score-cv" goto :score
|
|
32
|
+
if /I "%~1"=="match" goto :score
|
|
33
|
+
if /I "%~1"=="scan" goto :scan
|
|
34
|
+
if /I "%~1"=="scan-jobs" goto :scan
|
|
35
|
+
if /I "%~1"=="cover" goto :cover
|
|
36
|
+
if /I "%~1"=="cover-letter" goto :cover
|
|
37
|
+
if /I "%~1"=="render" goto :render
|
|
38
|
+
if /I "%~1"=="init" goto :init
|
|
39
|
+
if /I "%~1"=="init-cv" goto :init
|
|
40
|
+
if /I "%~1"=="tui" goto :tui
|
|
41
|
+
if /I "%~1"=="setup" goto :setup
|
|
42
|
+
if /I "%~1"=="install" goto :setup
|
|
43
|
+
|
|
44
|
+
echo Unknown command: %~1
|
|
45
|
+
echo Run 'chameleon help' for usage.
|
|
46
|
+
exit /b 1
|
|
47
|
+
|
|
48
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
49
|
+
REM TAILOR
|
|
50
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
51
|
+
:tailor
|
|
52
|
+
shift
|
|
53
|
+
set "JD_INPUT="
|
|
54
|
+
set "COMPANY="
|
|
55
|
+
set "TITLE="
|
|
56
|
+
set "EXTRA_ARGS="
|
|
57
|
+
|
|
58
|
+
:tailor_parse
|
|
59
|
+
if "%~1"=="" goto :tailor_run
|
|
60
|
+
if /I "%~1"=="--company" (
|
|
61
|
+
set "COMPANY=%~2"
|
|
62
|
+
shift & shift
|
|
63
|
+
goto :tailor_parse
|
|
64
|
+
)
|
|
65
|
+
if /I "%~1"=="--title" (
|
|
66
|
+
set "TITLE=%~2"
|
|
67
|
+
shift & shift
|
|
68
|
+
goto :tailor_parse
|
|
69
|
+
)
|
|
70
|
+
if /I "%~1"=="--no-review" (
|
|
71
|
+
set "EXTRA_ARGS=!EXTRA_ARGS! --no-review"
|
|
72
|
+
shift
|
|
73
|
+
goto :tailor_parse
|
|
74
|
+
)
|
|
75
|
+
if /I "%~1"=="--no-render" (
|
|
76
|
+
set "EXTRA_ARGS=!EXTRA_ARGS! --no-render"
|
|
77
|
+
shift
|
|
78
|
+
goto :tailor_parse
|
|
79
|
+
)
|
|
80
|
+
if /I "%~1"=="--json" (
|
|
81
|
+
set "EXTRA_ARGS=!EXTRA_ARGS! --json"
|
|
82
|
+
shift
|
|
83
|
+
goto :tailor_parse
|
|
84
|
+
)
|
|
85
|
+
if /I "%~1"=="--cv" (
|
|
86
|
+
set "EXTRA_ARGS=!EXTRA_ARGS! --cv %~2"
|
|
87
|
+
shift & shift
|
|
88
|
+
goto :tailor_parse
|
|
89
|
+
)
|
|
90
|
+
if "%~1"=="-" (
|
|
91
|
+
echo Unknown option: %~2
|
|
92
|
+
exit /b 1
|
|
93
|
+
)
|
|
94
|
+
set "JD_INPUT=%~1"
|
|
95
|
+
shift
|
|
96
|
+
goto :tailor_parse
|
|
97
|
+
|
|
98
|
+
:tailor_run
|
|
99
|
+
if "%JD_INPUT%"=="" (
|
|
100
|
+
echo Error: No job description provided.
|
|
101
|
+
echo Usage: chameleon tailor ^<jd_text_or_url_or_file^> [--company X] [--title Y]
|
|
102
|
+
exit /b 1
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
REM Check if input is a URL and crawl it
|
|
106
|
+
echo %JD_INPUT% | findstr /R "^https*://" >nul 2>&1
|
|
107
|
+
if %errorlevel%==0 (
|
|
108
|
+
echo [*] Crawling URL: %JD_INPUT%
|
|
109
|
+
for /f "delims=" %%i in ('"%PYTHON_BIN%" -c "import sys; import httpx; r=httpx.get('%JD_INPUT%',timeout=30,follow_redirects=True,headers={'User-Agent':'Mozilla/5.0'}); text=r.text; import re; text=re.sub(r'<script[^>]*>.*?</script>','',text,flags=re.DOTALL|re.IGNORECASE); text=re.sub(r'<style[^>]*>.*?</style>','',text,flags=re.DOTALL|re.IGNORECASE); text=re.sub(r'<[^>]+>',' ',text); import html; text=html.unescape(text); text=re.sub(r'\s+',' ',text).strip(); print(text[:10000])" 2^>nul') do set "JD_TEXT=%%i"
|
|
110
|
+
if "!JD_TEXT!"=="" (
|
|
111
|
+
echo Error: Failed to crawl URL
|
|
112
|
+
exit /b 1
|
|
113
|
+
)
|
|
114
|
+
goto :tailor_exec
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
REM Check if input is a file
|
|
118
|
+
if exist "%JD_INPUT%" (
|
|
119
|
+
set /p JD_TEXT=<"%JD_INPUT%"
|
|
120
|
+
) else (
|
|
121
|
+
set "JD_TEXT=%JD_INPUT%"
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
:tailor_exec
|
|
125
|
+
cd /d "%CHAMELEON_DIR%"
|
|
126
|
+
"%PYTHON_BIN%" -m scripts.tailor_cv "%JD_TEXT%" --company "%COMPANY%" --title "%TITLE%" %EXTRA_ARGS%
|
|
127
|
+
exit /b %errorlevel%
|
|
128
|
+
|
|
129
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
130
|
+
REM SCORE
|
|
131
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
132
|
+
:score
|
|
133
|
+
shift
|
|
134
|
+
set "JD_INPUT="
|
|
135
|
+
set "EXTRA_ARGS="
|
|
136
|
+
|
|
137
|
+
:score_parse
|
|
138
|
+
if "%~1"=="" goto :score_run
|
|
139
|
+
if /I "%~1"=="--cv" (
|
|
140
|
+
set "EXTRA_ARGS=!EXTRA_ARGS! --cv %~2"
|
|
141
|
+
shift & shift
|
|
142
|
+
goto :score_parse
|
|
143
|
+
)
|
|
144
|
+
if /I "%~1"=="--json" (
|
|
145
|
+
set "EXTRA_ARGS=!EXTRA_ARGS! --json"
|
|
146
|
+
shift
|
|
147
|
+
goto :score_parse
|
|
148
|
+
)
|
|
149
|
+
set "JD_INPUT=%~1"
|
|
150
|
+
shift
|
|
151
|
+
goto :score_parse
|
|
152
|
+
|
|
153
|
+
:score_run
|
|
154
|
+
if "%JD_INPUT%"=="" (
|
|
155
|
+
echo Error: No job description provided.
|
|
156
|
+
echo Usage: chameleon score ^<jd_text_or_url_or_file^> [--cv path] [--json]
|
|
157
|
+
exit /b 1
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
cd /d "%CHAMELEON_DIR%"
|
|
161
|
+
"%PYTHON_BIN%" -m scripts.job_matcher "%JD_INPUT%" %EXTRA_ARGS%
|
|
162
|
+
exit /b %errorlevel%
|
|
163
|
+
|
|
164
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
165
|
+
REM SCAN
|
|
166
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
167
|
+
:scan
|
|
168
|
+
shift
|
|
169
|
+
cd /d "%CHAMELEON_DIR%"
|
|
170
|
+
set "SCAN_ARGS="
|
|
171
|
+
:scan_parse
|
|
172
|
+
if "%~1"=="" goto :scan_run
|
|
173
|
+
set "SCAN_ARGS=!SCAN_ARGS! %~1"
|
|
174
|
+
shift
|
|
175
|
+
goto :scan_parse
|
|
176
|
+
:scan_run
|
|
177
|
+
"%PYTHON_BIN%" -m scripts.job_scanner.scanner %SCAN_ARGS%
|
|
178
|
+
exit /b %errorlevel%
|
|
179
|
+
|
|
180
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
181
|
+
REM COVER
|
|
182
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
183
|
+
:cover
|
|
184
|
+
shift
|
|
185
|
+
echo [*] Cover letter generation requires an AI assistant.
|
|
186
|
+
echo Use: opencode then /cover-letter ^<url-or-text^>
|
|
187
|
+
exit /b 0
|
|
188
|
+
|
|
189
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
190
|
+
REM RENDER
|
|
191
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
192
|
+
:render
|
|
193
|
+
shift
|
|
194
|
+
if "%~1"=="" (
|
|
195
|
+
echo Error: No YAML path provided.
|
|
196
|
+
echo Usage: chameleon render ^<yaml_path^>
|
|
197
|
+
exit /b 1
|
|
198
|
+
)
|
|
199
|
+
cd /d "%CHAMELEON_DIR%"
|
|
200
|
+
"%PYTHON_BIN%" scripts/render.py %~1
|
|
201
|
+
exit /b %errorlevel%
|
|
202
|
+
|
|
203
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
204
|
+
REM INIT
|
|
205
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
206
|
+
:init
|
|
207
|
+
shift
|
|
208
|
+
echo [*] CV initialization requires an AI assistant.
|
|
209
|
+
echo Use: opencode then /init-cv %~1
|
|
210
|
+
exit /b 0
|
|
211
|
+
|
|
212
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
213
|
+
REM TUI
|
|
214
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
215
|
+
:tui
|
|
216
|
+
shift
|
|
217
|
+
cd /d "%CHAMELEON_DIR%"
|
|
218
|
+
call tui.bat %*
|
|
219
|
+
exit /b %errorlevel%
|
|
220
|
+
|
|
221
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
222
|
+
REM SETUP
|
|
223
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
224
|
+
:setup
|
|
225
|
+
cd /d "%CHAMELEON_DIR%"
|
|
226
|
+
if not exist "%VENV_PYTHON%" (
|
|
227
|
+
python -m venv .venv
|
|
228
|
+
)
|
|
229
|
+
"%VENV_PYTHON%" -m scripts.setup %*
|
|
230
|
+
exit /b %errorlevel%
|
|
231
|
+
|
|
232
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
233
|
+
REM HELP
|
|
234
|
+
REM ════════════════════════════════════════════════════════════════════════
|
|
235
|
+
:help
|
|
236
|
+
echo Chameleon CLI — AI Resume Tailor
|
|
237
|
+
echo.
|
|
238
|
+
echo Usage:
|
|
239
|
+
echo chameleon tailor ^<jd_text_or_url_or_file^> [options] Tailor CV for a job
|
|
240
|
+
echo chameleon score ^<jd_text_or_url_or_file^> [options] Score job match
|
|
241
|
+
echo chameleon scan [options] Scan job platforms
|
|
242
|
+
echo chameleon cover ^<jd_text_or_url_or_file^> [options] Generate cover letter
|
|
243
|
+
echo chameleon render ^<yaml_path^> Render YAML to PDF
|
|
244
|
+
echo chameleon init ^<pdf_or_yaml_path^> Import master CV
|
|
245
|
+
echo chameleon tui Launch the TUI
|
|
246
|
+
echo chameleon setup Install all dependencies
|
|
247
|
+
echo chameleon help Show this help
|
|
248
|
+
echo.
|
|
249
|
+
echo TAILOR options:
|
|
250
|
+
echo --company ^<name^> Company name (auto-detected if omitted)
|
|
251
|
+
echo --title ^<role^> Role title (auto-detected if omitted)
|
|
252
|
+
echo --no-review Skip AI review pass
|
|
253
|
+
echo --no-render Skip PDF rendering
|
|
254
|
+
echo --cv ^<path^> Master CV path
|
|
255
|
+
echo --json Output JSON instead of text
|
|
256
|
+
echo.
|
|
257
|
+
echo SCORE options:
|
|
258
|
+
echo --cv ^<path^> CV path to score against
|
|
259
|
+
echo --json Output JSON
|
|
260
|
+
echo.
|
|
261
|
+
echo SCAN options:
|
|
262
|
+
echo -q, --query ^<query^> Search keywords
|
|
263
|
+
echo -p, --platforms ^<list^> Comma-separated platforms
|
|
264
|
+
echo -a, --all Scan all platforms
|
|
265
|
+
echo --tier1 Tier 1 platforms only
|
|
266
|
+
echo -n, --limit ^<n^> Max jobs per platform (default: 25)
|
|
267
|
+
echo -o, --output ^<file^> Save results to JSON file
|
|
268
|
+
echo --json Output JSON
|
|
269
|
+
echo.
|
|
270
|
+
echo INPUT:
|
|
271
|
+
echo ^<text^> Job description text in quotes
|
|
272
|
+
echo ^<url^> Job posting URL (will be crawled)
|
|
273
|
+
echo ^<file^> Path to a text file with the JD
|
|
274
|
+
echo.
|
|
275
|
+
echo EXAMPLES:
|
|
276
|
+
echo chameleon tailor "Senior Rust Engineer at Acme..." --company Acme
|
|
277
|
+
echo chameleon tailor https://jobs.example.com/senior-rust
|
|
278
|
+
echo chameleon score "Python developer, Django, PostgreSQL..."
|
|
279
|
+
echo chameleon scan -q "rust engineer" --tier1 --json
|
|
280
|
+
echo chameleon render templates\david_acme_rust_engineer_cv.yaml
|
|
281
|
+
echo.
|
|
282
|
+
exit /b 0
|
|
283
|
+
|
|
284
|
+
:version
|
|
285
|
+
echo Chameleon CLI v1.0
|
|
286
|
+
exit /b 0
|
package/chameleon.sh
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Chameleon CLI — unified entry point for tailoring, scoring, scanning, cover letters
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
CHAMELEON_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
6
|
+
VENV_PYTHON="$CHAMELEON_DIR/.venv/bin/python3"
|
|
7
|
+
|
|
8
|
+
# ── Resolve venv python ──────────────────────────────────────────────────
|
|
9
|
+
find_python() {
|
|
10
|
+
if [[ -f "$VENV_PYTHON" ]]; then
|
|
11
|
+
echo "$VENV_PYTHON"
|
|
12
|
+
elif command -v python3 &>/dev/null; then
|
|
13
|
+
echo "python3"
|
|
14
|
+
elif command -v python &>/dev/null; then
|
|
15
|
+
echo "python"
|
|
16
|
+
else
|
|
17
|
+
echo ""
|
|
18
|
+
fi
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
PYTHON_BIN="$(find_python)"
|
|
22
|
+
if [[ -z "$PYTHON_BIN" ]]; then
|
|
23
|
+
echo "[!] No Python found. Install Python 3.10+ and run: make install-tools" >&2
|
|
24
|
+
exit 1
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# ── URL crawler: fetch job description from a URL ────────────────────────
|
|
28
|
+
crawl_url() {
|
|
29
|
+
local url="$1"
|
|
30
|
+
"$PYTHON_BIN" -c "
|
|
31
|
+
import sys
|
|
32
|
+
try:
|
|
33
|
+
import httpx
|
|
34
|
+
except ImportError:
|
|
35
|
+
import urllib.request
|
|
36
|
+
import re
|
|
37
|
+
import html as html_mod
|
|
38
|
+
req = urllib.request.Request('$url', headers={'User-Agent': 'Mozilla/5.0 (compatible; ChameleonBot/1.0)'})
|
|
39
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
40
|
+
raw = resp.read().decode('utf-8', errors='replace')
|
|
41
|
+
text = re.sub(r'<script[^>]*>.*?</script>', '', raw, flags=re.DOTALL | re.IGNORECASE)
|
|
42
|
+
text = re.sub(r'<style[^>]*>.*?</style>', '', text, flags=re.DOTALL | re.IGNORECASE)
|
|
43
|
+
text = re.sub(r'<[^>]+>', ' ', text)
|
|
44
|
+
text = html_mod.unescape(text)
|
|
45
|
+
text = re.sub(r'\s+', ' ', text).strip()
|
|
46
|
+
print(text[:10000])
|
|
47
|
+
sys.exit(0)
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
resp = httpx.get('$url', timeout=30, follow_redirects=True, headers={
|
|
51
|
+
'User-Agent': 'Mozilla/5.0 (compatible; ChameleonBot/1.0)'
|
|
52
|
+
})
|
|
53
|
+
resp.raise_for_status()
|
|
54
|
+
from html.parser import HTMLParser
|
|
55
|
+
class TextExtractor(HTMLParser):
|
|
56
|
+
def __init__(self):
|
|
57
|
+
super().__init__()
|
|
58
|
+
self.parts = []
|
|
59
|
+
self.skip = False
|
|
60
|
+
def handle_starttag(self, tag, attrs):
|
|
61
|
+
if tag in ('script', 'style'):
|
|
62
|
+
self.skip = True
|
|
63
|
+
def handle_endtag(self, tag):
|
|
64
|
+
if tag in ('script', 'style'):
|
|
65
|
+
self.skip = False
|
|
66
|
+
def handle_data(self, data):
|
|
67
|
+
if not self.skip:
|
|
68
|
+
self.parts.append(data)
|
|
69
|
+
te = TextExtractor()
|
|
70
|
+
te.feed(resp.text)
|
|
71
|
+
text = ' '.join(te.parts)
|
|
72
|
+
import re as _re
|
|
73
|
+
text = _re.sub(r'\s+', ' ', text).strip()
|
|
74
|
+
print(text[:10000])
|
|
75
|
+
except Exception as e:
|
|
76
|
+
print(f'Error crawling URL: {e}', file=sys.stderr)
|
|
77
|
+
sys.exit(1)
|
|
78
|
+
"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# ── Read stdin if available ──────────────────────────────────────────────
|
|
82
|
+
read_stdin() {
|
|
83
|
+
if [[ ! -t 0 ]]; then
|
|
84
|
+
cat
|
|
85
|
+
fi
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# ── Help ─────────────────────────────────────────────────────────────────
|
|
89
|
+
show_help() {
|
|
90
|
+
cat <<'EOF'
|
|
91
|
+
Chameleon CLI — AI Resume Tailor
|
|
92
|
+
|
|
93
|
+
Usage:
|
|
94
|
+
chameleon tailor <jd_text_or_url_or_file> [options] Tailor CV for a job
|
|
95
|
+
chameleon score <jd_text_or_url_or_file> [options] Score job match
|
|
96
|
+
chameleon scan [options] Scan job platforms
|
|
97
|
+
chameleon cover <jd_text_or_url_or_file> [options] Generate cover letter
|
|
98
|
+
chameleon render <yaml_path> Render YAML to PDF
|
|
99
|
+
chameleon init <pdf_or_yaml_path> Import master CV
|
|
100
|
+
chameleon setup Install all dependencies
|
|
101
|
+
chameleon tui Launch the TUI
|
|
102
|
+
chameleon help Show this help
|
|
103
|
+
|
|
104
|
+
TAILOR options:
|
|
105
|
+
--company <name> Company name (auto-detected if omitted)
|
|
106
|
+
--title <role> Role title (auto-detected if omitted)
|
|
107
|
+
--no-review Skip AI review pass
|
|
108
|
+
--no-render Skip PDF rendering
|
|
109
|
+
--cv <path> Master CV path (default: from config)
|
|
110
|
+
--json Output JSON instead of text
|
|
111
|
+
|
|
112
|
+
SCORE options:
|
|
113
|
+
--cv <path> CV path to score against
|
|
114
|
+
--json Output JSON
|
|
115
|
+
|
|
116
|
+
SCAN options:
|
|
117
|
+
-q, --query <query> Search keywords
|
|
118
|
+
-p, --platforms <list> Comma-separated platforms
|
|
119
|
+
-a, --all Scan all platforms
|
|
120
|
+
--tier1 Tier 1 platforms only (API/RSS)
|
|
121
|
+
-n, --limit <n> Max jobs per platform (default: 25)
|
|
122
|
+
-o, --output <file> Save results to JSON file
|
|
123
|
+
--json Output JSON
|
|
124
|
+
--list-platforms List available platforms
|
|
125
|
+
|
|
126
|
+
COVER options:
|
|
127
|
+
--resume <path> Resume YAML to ground the letter
|
|
128
|
+
--greeting <name> Custom greeting name
|
|
129
|
+
|
|
130
|
+
INPUT:
|
|
131
|
+
<text> Job description text in quotes
|
|
132
|
+
<url> Job posting URL (will be crawled)
|
|
133
|
+
<file> Path to a text file with the JD
|
|
134
|
+
Piped stdin Cat a file or pipe text
|
|
135
|
+
|
|
136
|
+
EXAMPLES:
|
|
137
|
+
./chameleon tailor "Senior Rust Engineer at Acme..." --company Acme
|
|
138
|
+
./chameleon tailor https://jobs.example.com/senior-rust
|
|
139
|
+
./chameleon score "Python developer, Django, PostgreSQL..."
|
|
140
|
+
./chameleon scan -q "rust engineer" --tier1 --json
|
|
141
|
+
./chameleon cover https://example.com/job --resume templates/david_cv.yaml
|
|
142
|
+
cat jd.txt | ./chameleon tailor --company Acme --title Engineer
|
|
143
|
+
./chameleon render templates/david_acme_rust_engineer_cv.yaml
|
|
144
|
+
|
|
145
|
+
EOF
|
|
146
|
+
exit 0
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
# ── Main dispatch ────────────────────────────────────────────────────────
|
|
150
|
+
if [[ $# -lt 1 ]]; then
|
|
151
|
+
show_help
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
COMMAND="$1"
|
|
155
|
+
shift
|
|
156
|
+
|
|
157
|
+
case "$COMMAND" in
|
|
158
|
+
tailor|tailor-cv)
|
|
159
|
+
JD_INPUT=""
|
|
160
|
+
COMPANY=""
|
|
161
|
+
TITLE=""
|
|
162
|
+
NO_REVIEW=""
|
|
163
|
+
NO_RENDER=""
|
|
164
|
+
CV_PATH=""
|
|
165
|
+
AS_JSON=""
|
|
166
|
+
|
|
167
|
+
while [[ $# -gt 0 ]]; do
|
|
168
|
+
case "$1" in
|
|
169
|
+
--company) COMPANY="$2"; shift 2 ;;
|
|
170
|
+
--title) TITLE="$2"; shift 2 ;;
|
|
171
|
+
--no-review) NO_REVIEW="yes"; shift ;;
|
|
172
|
+
--no-render) NO_RENDER="yes"; shift ;;
|
|
173
|
+
--cv) CV_PATH="$2"; shift 2 ;;
|
|
174
|
+
--json) AS_JSON="yes"; shift ;;
|
|
175
|
+
-*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
176
|
+
*) JD_INPUT="$1"; shift ;;
|
|
177
|
+
esac
|
|
178
|
+
done
|
|
179
|
+
|
|
180
|
+
if [[ -z "$JD_INPUT" ]]; then
|
|
181
|
+
JD_INPUT="$(read_stdin)"
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
if [[ -z "$JD_INPUT" ]]; then
|
|
185
|
+
echo "Error: No job description provided. Pass text, URL, file path, or pipe via stdin." >&2
|
|
186
|
+
echo "Usage: chameleon tailor <jd_text_or_url_or_file> [--company X] [--title Y]" >&2
|
|
187
|
+
exit 1
|
|
188
|
+
fi
|
|
189
|
+
|
|
190
|
+
if [[ "$JD_INPUT" =~ ^https?:// ]]; then
|
|
191
|
+
echo "[*] Crawling URL: $JD_INPUT" >&2
|
|
192
|
+
JD_TEXT="$(crawl_url "$JD_INPUT")"
|
|
193
|
+
if [[ -z "$JD_TEXT" ]]; then
|
|
194
|
+
echo "Error: Failed to crawl URL" >&2
|
|
195
|
+
exit 1
|
|
196
|
+
fi
|
|
197
|
+
elif [[ -f "$JD_INPUT" ]]; then
|
|
198
|
+
JD_TEXT="$(cat "$JD_INPUT")"
|
|
199
|
+
else
|
|
200
|
+
JD_TEXT="$JD_INPUT"
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
cd "$CHAMELEON_DIR"
|
|
204
|
+
set -- "$JD_TEXT"
|
|
205
|
+
[[ -n "$COMPANY" ]] && set -- "$@" "--company" "$COMPANY"
|
|
206
|
+
[[ -n "$TITLE" ]] && set -- "$@" "--title" "$TITLE"
|
|
207
|
+
[[ -n "$NO_REVIEW" ]] && set -- "$@" "--no-review"
|
|
208
|
+
[[ -n "$NO_RENDER" ]] && set -- "$@" "--no-render"
|
|
209
|
+
[[ -n "$AS_JSON" ]] && set -- "$@" "--json"
|
|
210
|
+
exec "$PYTHON_BIN" -m scripts.tailor_cv "$@"
|
|
211
|
+
;;
|
|
212
|
+
|
|
213
|
+
score|score-cv|match)
|
|
214
|
+
JD_INPUT=""
|
|
215
|
+
CV_PATH=""
|
|
216
|
+
AS_JSON=""
|
|
217
|
+
|
|
218
|
+
while [[ $# -gt 0 ]]; do
|
|
219
|
+
case "$1" in
|
|
220
|
+
--cv) CV_PATH="$2"; shift 2 ;;
|
|
221
|
+
--json) AS_JSON="yes"; shift ;;
|
|
222
|
+
-*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
223
|
+
*) JD_INPUT="$1"; shift ;;
|
|
224
|
+
esac
|
|
225
|
+
done
|
|
226
|
+
|
|
227
|
+
if [[ -z "$JD_INPUT" ]]; then
|
|
228
|
+
JD_INPUT="$(read_stdin)"
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
if [[ -z "$JD_INPUT" ]]; then
|
|
232
|
+
echo "Error: No job description provided." >&2
|
|
233
|
+
echo "Usage: chameleon score <jd_text_or_url_or_file> [--cv <path>] [--json]" >&2
|
|
234
|
+
exit 1
|
|
235
|
+
fi
|
|
236
|
+
|
|
237
|
+
if [[ "$JD_INPUT" =~ ^https?:// ]]; then
|
|
238
|
+
echo "[*] Crawling URL: $JD_INPUT" >&2
|
|
239
|
+
JD_TEXT="$(crawl_url "$JD_INPUT")"
|
|
240
|
+
if [[ -z "$JD_TEXT" ]]; then
|
|
241
|
+
echo "Error: Failed to crawl URL" >&2
|
|
242
|
+
exit 1
|
|
243
|
+
fi
|
|
244
|
+
elif [[ -f "$JD_INPUT" ]]; then
|
|
245
|
+
JD_TEXT="$(cat "$JD_INPUT")"
|
|
246
|
+
else
|
|
247
|
+
JD_TEXT="$JD_INPUT"
|
|
248
|
+
fi
|
|
249
|
+
|
|
250
|
+
cd "$CHAMELEON_DIR"
|
|
251
|
+
set -- "$JD_TEXT"
|
|
252
|
+
[[ -n "$CV_PATH" ]] && set -- "$@" "--cv" "$CV_PATH"
|
|
253
|
+
[[ -n "$AS_JSON" ]] && set -- "$@" "--json"
|
|
254
|
+
exec "$PYTHON_BIN" -m scripts.job_matcher "$@"
|
|
255
|
+
;;
|
|
256
|
+
|
|
257
|
+
scan|scan-jobs)
|
|
258
|
+
cd "$CHAMELEON_DIR"
|
|
259
|
+
exec "$PYTHON_BIN" -m scripts.job_scanner.scanner "$@"
|
|
260
|
+
;;
|
|
261
|
+
|
|
262
|
+
cover|cover-letter)
|
|
263
|
+
JD_INPUT=""
|
|
264
|
+
RESUME_PATH=""
|
|
265
|
+
|
|
266
|
+
while [[ $# -gt 0 ]]; do
|
|
267
|
+
case "$1" in
|
|
268
|
+
--resume) RESUME_PATH="$2"; shift 2 ;;
|
|
269
|
+
-*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
270
|
+
*) JD_INPUT="$1"; shift ;;
|
|
271
|
+
esac
|
|
272
|
+
done
|
|
273
|
+
|
|
274
|
+
if [[ -z "$JD_INPUT" ]]; then
|
|
275
|
+
JD_INPUT="$(read_stdin)"
|
|
276
|
+
fi
|
|
277
|
+
|
|
278
|
+
if [[ -z "$JD_INPUT" ]]; then
|
|
279
|
+
echo "Error: No job description provided." >&2
|
|
280
|
+
echo "Usage: chameleon cover <jd_text_or_url_or_file> [--resume <path>]" >&2
|
|
281
|
+
exit 1
|
|
282
|
+
fi
|
|
283
|
+
|
|
284
|
+
if [[ "$JD_INPUT" =~ ^https?:// ]]; then
|
|
285
|
+
echo "[*] Crawling URL: $JD_INPUT" >&2
|
|
286
|
+
JD_TEXT="$(crawl_url "$JD_INPUT")"
|
|
287
|
+
elif [[ -f "$JD_INPUT" ]]; then
|
|
288
|
+
JD_TEXT="$(cat "$JD_INPUT")"
|
|
289
|
+
else
|
|
290
|
+
JD_TEXT="$JD_INPUT"
|
|
291
|
+
fi
|
|
292
|
+
|
|
293
|
+
# Save extracted text and show instructions
|
|
294
|
+
COVER_DIR="$CHAMELEON_DIR/output/cover_letters"
|
|
295
|
+
mkdir -p "$COVER_DIR"
|
|
296
|
+
COVER_FILE="$COVER_DIR/cover_$(date +%Y%m%d_%H%M%S).txt"
|
|
297
|
+
echo "$JD_TEXT" > "$COVER_FILE"
|
|
298
|
+
echo "[*] Job description saved to: $COVER_FILE" >&2
|
|
299
|
+
echo "[*] To generate a cover letter, run in opencode:" >&2
|
|
300
|
+
echo " opencode" >&2
|
|
301
|
+
echo " /cover-letter --resume ${RESUME_PATH:-<resume_yaml>}" >&2
|
|
302
|
+
;;
|
|
303
|
+
|
|
304
|
+
render)
|
|
305
|
+
if [[ $# -lt 1 ]]; then
|
|
306
|
+
echo "Error: No YAML path provided." >&2
|
|
307
|
+
echo "Usage: chameleon render <yaml_path>" >&2
|
|
308
|
+
exit 1
|
|
309
|
+
fi
|
|
310
|
+
cd "$CHAMELEON_DIR"
|
|
311
|
+
exec "$PYTHON_BIN" scripts/render.py "$@"
|
|
312
|
+
;;
|
|
313
|
+
|
|
314
|
+
init|init-cv)
|
|
315
|
+
if [[ $# -lt 1 ]]; then
|
|
316
|
+
echo "Error: No file path provided." >&2
|
|
317
|
+
echo "Usage: chameleon init <pdf_or_yaml_path>" >&2
|
|
318
|
+
exit 1
|
|
319
|
+
fi
|
|
320
|
+
echo "[*] CV initialization requires an AI assistant (use /init-cv in opencode)."
|
|
321
|
+
echo " opencode"
|
|
322
|
+
echo " /init-cv $1"
|
|
323
|
+
;;
|
|
324
|
+
|
|
325
|
+
tui)
|
|
326
|
+
cd "$CHAMELEON_DIR"
|
|
327
|
+
exec ./tui.sh "$@"
|
|
328
|
+
;;
|
|
329
|
+
|
|
330
|
+
setup|install)
|
|
331
|
+
cd "$CHAMELEON_DIR"
|
|
332
|
+
if [[ ! -f "$VENV_PYTHON" ]]; then
|
|
333
|
+
echo "[*] Creating virtual environment..." >&2
|
|
334
|
+
"$PYTHON_BIN" -m venv .venv 2>/dev/null || python3 -m venv .venv 2>/dev/null || true
|
|
335
|
+
fi
|
|
336
|
+
exec "$VENV_PYTHON" -m scripts.setup "$@"
|
|
337
|
+
;;
|
|
338
|
+
|
|
339
|
+
help|--help|-h)
|
|
340
|
+
show_help
|
|
341
|
+
;;
|
|
342
|
+
|
|
343
|
+
--version|-v)
|
|
344
|
+
echo "Chameleon CLI v1.0"
|
|
345
|
+
exit 0
|
|
346
|
+
;;
|
|
347
|
+
|
|
348
|
+
*)
|
|
349
|
+
echo "Unknown command: $COMMAND" >&2
|
|
350
|
+
echo "Run 'chameleon help' for usage." >&2
|
|
351
|
+
exit 1
|
|
352
|
+
;;
|
|
353
|
+
esac
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chameleon-cv",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AI-powered resume tailor — tailor your CV to any job posting with ATS-optimized PDFs",
|
|
5
|
+
"keywords": ["resume", "cv", "tailor", "ats", "job-search", "ai", "rendercv"],
|
|
6
|
+
"homepage": "https://github.com/HautlyS/chameleon#readme",
|
|
7
|
+
"bugs": { "url": "https://github.com/HautlyS/chameleon/issues" },
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"author": "chameleon",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/HautlyS/chameleon.git"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"chameleon": "bin/chameleon.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin/",
|
|
19
|
+
"scripts/",
|
|
20
|
+
"profiles/",
|
|
21
|
+
"chameleon.sh",
|
|
22
|
+
"chameleon.bat",
|
|
23
|
+
"tui.sh",
|
|
24
|
+
"tui.bat",
|
|
25
|
+
"scan.sh",
|
|
26
|
+
"score.sh",
|
|
27
|
+
"Makefile",
|
|
28
|
+
"requirements.txt",
|
|
29
|
+
"open.json",
|
|
30
|
+
"AGENTS.md"
|
|
31
|
+
],
|
|
32
|
+
"engines": { "node": ">=18" },
|
|
33
|
+
"scripts": {
|
|
34
|
+
"postinstall": "node bin/chameleon.js setup --auto",
|
|
35
|
+
"chameleon": "node bin/chameleon.js",
|
|
36
|
+
"setup": "node bin/chameleon.js setup",
|
|
37
|
+
"test": "node bin/chameleon.js test"
|
|
38
|
+
}
|
|
39
|
+
}
|