CUJA discovers relevant jobs, tracks them in a local queue, and fills application forms in a visible Chrome window. It always stops before Submit, leaving the final review and submission to you.
- Web UI (recommended): chat-driven discovery, queue management, application filling, status cards, schedules, and configuration.
- CLI: direct commands for scripting discovery and application runs.
CUJA supports Linux and WSL. Install:
- Python 3.11 or newer, including the
venvmodule - Node.js 18 or newer and npm
- Git and curl
- Google Chrome or Chromium for visible application filling
- Azure CLI with access
to Microsoft TRAPI, or a running
copilot-apiproxy
Application filling also requires a running ResuMix server. Discovery, queue management, and status views do not.
git clone https://github.com/demonking-mw/cuja.git
cd cuja
./setup.shsetup.sh is safe to re-run. It:
- Checks the required tool versions.
- Clones the agent framework to
../prototyperwhen it is not already there. - Creates
venv/and installs the Python and Playwright dependencies. - Installs the frontend dependencies with
npm ci. - Creates the ignored
profile.yamlfromprofile.example.yaml.
To use an existing Prototyper checkout in another location:
CUJA_PROTOTYPER_DIR=/path/to/prototyper ./setup.shReplace the example values in profile.yaml, especially personal, legal,
education, screening_answers, and job_preferences. This file is ignored by
Git and should never be committed.
The default model backend is Microsoft TRAPI:
az loginAlternatively, start a GitHub Copilot proxy in a separate terminal:
npx copilot-api@latest start \
--account-type enterprise \
--port 4141 \
--rate-limit 6 \
--waitThen create config.local.yaml:
copilot_api:
enabled: trueLocal configuration is deep-merged over config.yaml and is ignored by Git.
From the repository root, run:
./run_ui.shWait for both Backend ready. and the Vite ready message, then open:
Keep that terminal running. Press Ctrl-C once to stop both servers.
If port 8000 or 3000 is occupied, choose free ports in the same command:
CUJA_BACKEND_PORT=18000 CUJA_FRONTEND_PORT=13000 ./run_ui.shOpen the frontend URL printed by the launcher. Its API proxy automatically uses the selected backend port.
Run ResuMix according to its setup
guide; CUJA expects its API at http://localhost:5001. Add the local ResuMix user
ID to config.local.yaml:
resumix:
uid: "your-resumix-user-id"Provide the password only at runtime:
export CUJA_RESUMIX_PWD='your-local-resumix-password'If the variable is absent, CLI runs prompt for it. CUJA never stores the password in its configuration.
With browser.auto_launch: true (the default), CUJA opens a visible Chrome
window when an application run starts. The default profile at
/tmp/cuja-chrome-profile has no saved logins.
For a persistent dedicated profile, add this to config.local.yaml:
browser:
user_data_dir: "/home/you/.cuja-chrome"You can instead launch Chrome yourself and let CUJA attach to it:
google-chrome \
--remote-debugging-port=9222 \
--user-data-dir="$HOME/.cuja-chrome"Set browser.auto_launch: false when a manually started browser should be
required. Sign in to job sites in that dedicated window before filling forms.
- Type a request such as
Find five entry-level backend roles posted this week. - Use Discovery to search from the preferences in
profile.yaml. - Use Status to inspect the queue and Apply All to fill queued jobs.
- Watch progress cards and handle items marked
needs_user. - Review every filled browser tab and its
artifacts/<job_id>/diff_card.txt, then submit manually.
The UI persists conversations and job state in data/jobs.db.
Run these commands from the repository root with ./venv/bin/python:
# Discover and inspect
./venv/bin/python discover.py --search
./venv/bin/python discover.py --explore "https://boards.greenhouse.io/stripe" \
--prompt "the five most recent backend roles"
./venv/bin/python discover.py --add "https://example.com/jobs/123"
./venv/bin/python discover.py --status
# Fill queued applications
./venv/bin/python apply.py --count 5
./venv/bin/python apply.py --ids 12,15,23
./venv/bin/python apply.py --retry-errors
# Extract and queue links from pasted text
printf '%s\n' "https://example.com/jobs/123" | \
./venv/bin/python paste_links.py
# Teach CUJA from corrections (finish input with Ctrl-D)
./venv/bin/python learn.pyBoth interfaces share data/jobs.db, profile.yaml, and the same configuration.
Committed defaults live in config.yaml. Put machine-specific values and
overrides in the ignored config.local.yaml:
resumix:
uid: "your-resumix-user-id"
browser:
user_data_dir: "/home/you/.cuja-chrome"
copilot_api:
enabled: falseImportant settings:
| Section | Setting | Purpose |
|---|---|---|
resumix |
base_url, uid |
Resume-tailoring API and local user |
browser |
cdp_endpoint, auto_launch, user_data_dir |
Visible Chrome connection |
models |
role-specific deployment IDs | Models used for planning and extraction |
answer_engine |
match_threshold |
Screening-answer similarity threshold |
orchestrator |
max_concurrent_tabs |
Maximum simultaneous application tabs |
base_claude or ai_switch cannot be imported
Re-run ./setup.sh. If Prototyper lives elsewhere, use
CUJA_PROTOTYPER_DIR=/path/to/prototyper for both setup and launch.
Port 8000 or 3000 is already in use
Stop the existing process, or launch with CUJA_BACKEND_PORT and
CUJA_FRONTEND_PORT as shown above. The launcher exits instead of attaching to
an unknown process.
The UI opens but model requests fail
For TRAPI, run az login and confirm your account has endpoint access. For
Copilot, confirm http://localhost:4141 is reachable and
copilot_api.enabled: true is present in config.local.yaml.
Application filling reports a failed preflight
Confirm Chrome CDP responds at http://localhost:9222/json/version, ResuMix
responds at http://localhost:5001/health, and resumix.uid is configured.
Chrome opens without your logins
The default profile is intentionally disposable. Configure a dedicated
browser.user_data_dir, open it once, and sign in before an application run.
Web UI / CLI
|
+-- discovery -> web search -> fit scoring -> data/jobs.db
|
`-- apply -> visible Chrome (CDP) -> page classification -> JD extraction
-> ResuMix tailoring -> field mapping -> ATS adapter / vision
-> diff card -> review in open browser tab
- Greenhouse uses a DOM-first adapter.
- Workday and unknown career sites can use the computer-use planner.
- The answer engine matches screening questions to profile answers with local embeddings and escalates sensitive or uncertain fields.
- Generated screenshots, tailored resumes, and diff cards stay under the
ignored
artifacts/directory.
apply.py, discover.py CLI entry points
setup.sh, run_ui.sh one-time setup and web UI launcher
config.yaml committed defaults
profile.example.yaml safe profile template
src/ discovery, browser, ATS, answer, and orchestration code
ui/backend/ FastAPI, SSE, agent tools, scheduler, persistence
ui/frontend/ React and Vite interface
prompts/ model prompt templates
data/ local SQLite state (ignored)
artifacts/ per-job evidence and diff cards (ignored)
- Never submits: CUJA fills forms and stops before submission.
- Human review required: always verify every field and attachment.
- No credential entry: login walls and captchas are left for you.
- Local secrets stay ignored:
.env,profile.yaml,config.local.yaml,data/,logs/, andartifacts/are not committed. - Job pages are untrusted: page content is treated as data and framed against prompt injection.
- External processing: profile, resume, job, and form context may be sent to
the model endpoints configured in
config.yaml; review those services' data policies before use.
