AI Job Search는 Claude Code(앤트로픽의 터미널용 AI 코딩 도구) 위에 올려 쓰는 구직 자동화 프레임워크입니다. 레포를 fork한 뒤 내 프로필을 채워 넣으면, Claude가 ① 채용공고가 나와 맞는지 평가하고, ② 그 자리에 맞춰 CV를 다듬고, ③ 커버레터를 쓰고, ④ 면접 준비까지 해줍니다.
사용자가 외우면 되는 명령은 사실상 3개뿐입니다. /setup(프로필 입력 인터뷰), /scrape(여러 구직 포털 검색), /apply <url>(공고 1건에 대해 전체 지원 워크플로 실행). 나머지는 전부 Claude Code의 슬래시 명령 + 스킬 + 서브에이전트로 짜여 있습니다. (※ /scrape는 기술적으로 .claude/skills/job-scraper/의 스킬 트리거 키워드이며, 실제 .claude/commands/에는 setup.md·apply.md·expand.md·reset.md 4개의 슬래시 명령 파일만 존재합니다.)
중요한 설계 결정 하나: 핵심 워크플로(자기 프로필 작성·적합도 평가·작성자-검토자 파이프라인)는 언어·국가에 무관합니다. 포털 검색 도구만 덴마크 시장(Jobindex·Jobnet·Jobdanmark·Akademikernes Jobbank)에 맞춰져 있고, 이건 "내 나라 포털로 교체하라"는 예시(어댑터)로 설계됐습니다.
혼자 구직할 때 우리는 공고를 읽고(적합한가?), CV를 그 자리에 맞게 고치고, 커버레터를 새로 쓰고, 회사를 조사하고, 면접 답변을 연습합니다. 지루하고 반복적이지만 매번 "맞춤"이 필요한 일이죠.
AI Job Search는 이걸 두 인격으로 나눕니다. 작성자(Drafter)가 초안을 쓰면, 곧바로 검토자(Reviewer)가 회사를 조사하고 "이 문장은 약하다, 이 키워드가 빠졌다"고 비평합니다. 그 피드백을 반영해 고친 뒤, PDF로 실제 컴파일해 눈으로 확인하고서야 결과를 내놓습니다.
저장소 구성은 TypeScript(포털 검색 CLI) + Python(연봉 조회) + LaTeX(CV·커버레터 템플릿)의 세 언어로 이뤄져 있고, 라이선스는 MIT입니다. 코드 자체보다 "Claude Code를 어떻게 실무 워크플로로 조립하는가"를 보여주는 레퍼런스로서의 가치가 큽니다.
2025~2026년은 "AI에게 코딩을 시키는" 단계를 넘어, AI 에이전트를 일상 업무 자동화에 붙이는 사례가 폭발한 시기입니다. AI Job Search는 그 대표 예입니다 — 누구나 겪는 고통(구직)을 Claude Code 워크플로 하나로 엮어, "에이전트로 내 삶의 잡일을 자동화한다"는 흐름을 정확히 보여줍니다.
| 비교 항목 | AI Job Search | ChatGPT에 CV 붙여넣기(수동) | 유료 구직 SaaS |
|---|---|---|---|
| 프로필 영속성 | CLAUDE.md에 1회 입력 후 재사용 | 매번 다시 붙여넣기 | 계정에 저장 |
| 품질 검증 | 검토자 에이전트 + PDF 육안검사 | 사용자가 직접 | 대부분 자동 생성만 |
| 경력 날조 방지 | 명시적 금지 규칙 + 체크리스트 | 모델 임의 | 제품마다 상이 |
| 포털 검색 | 로컬 CLI로 직접 스크래핑 | 없음 | 내장(유료) |
| 산출물 형식 | LaTeX → 인쇄용 PDF | 텍스트/워드 | 웹/PDF |
| 비용·소유권 | 무료·내 데이터 로컬 보관 | 무료 | 구독료·데이터 위탁 |
| 커스터마이즈 | 전부 오픈(프롬프트·스킬 수정) | 제한적 | 닫힌 제품 |
AI에 CV를 맡길 때 가장 큰 위험은 없는 경력·기술을 그럴듯하게 지어내는 것입니다. 이 레포는 CLAUDE.md의 검증 체크리스트와 /apply 단계마다 "모든 주장은 실제 프로필과 대조, 날조 금지"를 반복 명시합니다. 검토자 에이전트조차 "사실이 아닌 보강 제안 금지" 규칙을 받습니다.
LaTeX는 페이지 나눔이 예측 불가라, 소스가 멀쩡해도 PDF가 깨지기 일쑤(직함만 페이지 하단에 고아처럼 남거나, 커버레터가 2페이지로 넘침)입니다. 그래서 /apply는 PDF를 실제 컴파일(lualatex/xelatex)하고 Read 도구로 육안 검사하는 단계를 "절대 건너뛰지 말 것"으로 못 박습니다.
수동 ChatGPT가 "한 번 써주고 끝"이라면, AI Job Search는 작성 → 비평 → 수정 → 인쇄 검수로 이어지는 작은 편집국을 통째로 자동화합니다. 결과물 한 장이 아니라 "좋은 결과물을 만드는 절차"를 코드로 박제했다는 점이 핵심입니다.
이 레포는 "하나의 앱"이 아니라 Claude Code를 지휘자로 두고 여러 도구를 엮은 워크플로입니다. 그래서 스택도 역할별 네 층으로 나뉩니다.
| 구성 | 역할 |
|---|---|
| CLAUDE.md | 지원자 프로필 + 워크플로 규칙 + 검증 체크리스트. Claude가 항상 읽는 "프로젝트 헌법" |
| .claude/commands/ (슬래시 명령) | setup.md·apply.md·expand.md·reset.md — 각 명령이 "Step 0~6"으로 짜인 결정론적 프롬프트 프로그램 |
| .claude/skills/ (스킬) | job-application-assistant(핵심), job-scraper(검색), upskill(역량 보강). SKILL.md + 번호 레퍼런스 |
| .claude/agents/ (서브에이전트) | gemini-research-expert.md — 회사 조사 전담. /apply는 추가로 general-purpose 검토자 에이전트를 동적으로 띄움 |
| .claude/settings.local.json | Claude Code 권한 설정(허용 도구·명령) |
| 기술 | 역할 |
|---|---|
| Bun | JS/TS 런타임 겸 패키지 매니저·테스트 러너. bun install·bun test로 구동 |
| @bunli/core, @bunli/utils | Bun 기반 CLI 프레임워크. cli.ts가 진입점, helpers.ts에 로직 |
| node-html-parser | HTML 파싱(보조). 단, 핵심 카드 파싱은 정규식으로 직접 처리 |
| zod | 런타임 스키마 검증(입력 인자·응답 형태 보증) |
| 4개 포털 어댑터 | jobindex-search · jobnet-search · jobdanmark-search · jobbank-search (각 SKILL.md + cli/ + tests/) |
| 기술 | 역할 |
|---|---|
| moderncv (banking 스타일) | CV 템플릿(cv/main_example.tex). lualatex로 컴파일(구형 pdflatex는 fontawesome5 오류) |
| custom cover.cls + fontspec | 커버레터 전용 LaTeX 클래스. xelatex로 컴파일(fontspec 필요) |
| OpenFonts (Lato · Raleway) | 커버레터 본문 폰트. 레포에 ttf/otf 동봉(재현성 확보) |
| needspace / enlargethispage | 고아 직함·페이지 넘침을 막는 LaTeX 페이지 나눔 제어 패키지 |
| 기술 | 역할 |
|---|---|
| salary_lookup.py | 회사명으로 연봉 벤치마크 조회. 덴마크어 문자 정규화 + 법인 접미사 제거 + 다단계 퍼지 매칭 |
| tools/convert_salary_excel.py | 연봉 엑셀 → salary_data.json 변환(BYO 데이터) |
| 표준 라이브러리만 | json·re·argparse·unicodedata — 외부 의존성 0(이식성↑) |
| 파일 | 역할 |
|---|---|
| job_scraper/seen_jobs.json | 이미 본 공고 기록 → 실행마다 중복 제거(dedup) |
| job_search_tracker.csv | 지원 현황 추적 스프레드시트(지원한 회사+직무) |
| .claude/skills/job-application-assistant/01~07 | 프로필·행동검사·문체·평가틀·CV·커버레터·면접 — 7개 지식 모듈 |
/setup이 한 번 채워 둔 프로필을 /scrape와 /apply가 계속 재사용합니다. 즉 프로필이 "영속 컨텍스트"가 되어, 매번 내 경력을 다시 설명할 필요가 없습니다.
이 루프의 묘미는 "검토자가 신선한 눈(fresh context)으로 본다"는 점과, 토큰 절약을 위해 초안을 파일이 아니라 프롬프트에 그대로 실어 넘긴다는 점입니다. 검토자는 LaTeX 구조(05·06)는 일부러 안 읽고 내용 비평에만 집중합니다 — 역할 분리가 명확합니다.
지원자 정보를 매번 입력하지 않고 CLAUDE.md + 번호 레퍼런스에 1회 고정합니다. 모든 명령이 같은 출처를 참조하므로 CV·커버레터·면접 답변의 사실이 한 곳에서 관리되고 서로 모순되지 않습니다.
한 모델이 쓰고 채점까지 하면 자기 글에 관대해집니다. 그래서 새 컨텍스트의 검토자를 띄워 회사 조사 + 비평을 맡기고, 결과를 작성자가 반영합니다. "쓰는 자"와 "보는 자"를 분리한 편집 모델입니다.
스킬 본체(SKILL.md)는 얇게 두고, 상세 지식을 01-candidate-profile.md~07-interview-prep.md로 쪼개 그 단계에 필요한 것만 읽습니다. 컨텍스트 토큰을 아끼면서 깊은 지식을 제공하는 Agent Skills 정석입니다.
apply.md는 "Step 0~6을 정확히 순서대로, 건너뛰지 말 것"이라는 의사코드입니다. 토큰 효율 규칙(읽은 파일 재읽기 금지 등)까지 명문화해, LLM의 자유도를 줄이고 재현 가능한 절차로 만듭니다.
두 종류의 검증이 강제됩니다. ① 사실 검증: 모든 주장은 실제 프로필과 대조, 회사 정보는 WebFetch로 재확인(검토자 조사도 액면 신뢰 금지). ② 형식 검증: PDF를 실제로 컴파일해 페이지 수·고아 직함·폰트까지 눈으로 확인.
평가·작성·면접 로직은 어디서나 동일하고, 나라마다 다른 것(구직 포털)만 .agents/skills/<portal>-search/ 어댑터로 분리했습니다. 내 나라 포털용 도구를 같은 구조로 만들어 끼우면 됩니다.
포털 CLI의 helpers.ts는 단순 fetch가 아니라 지수 백오프 + 지터(jitter) 재시도를 구현합니다. 429(요청 과다)·5xx(서버 오류)면 대기 시간을 2배씩 늘리며 최대 6회 재시도합니다.
// jobindex-search/cli/src/helpers.ts (요약)
const maxRetries = 6; let delay = 500;
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, { headers: {...}, redirect: "follow" });
if (response.status === 429 || response.status >= 500) {
if (attempt === maxRetries) throw new Error(...);
const jitter = Math.floor(Math.random() * 500); // 동시요청 충돌 분산
await new Promise(r => setTimeout(r, delay + jitter));
delay = Math.min(delay * 2, 5000); // 지수 증가, 상한 5초
continue;
}
if (response.status === 404) throw new Error("Job not found");
return response.text();
}
.agents/skills/*/cli/(TypeScript)와 salary_lookup.py에, AI가 따를 지식·규칙은 .claude/의 Markdown에, 사용자가 채우는 결과물은 cv/·cover_letters/·documents/에 모여 있습니다. 폴더 구조만 봐도 "이 레포는 코드 + 프롬프트 + 데이터를 조립한 워크플로"라는 정체성이 드러납니다..claude/commands/apply.md는 자연어로 쓴 의사코드입니다. "Step 0~6을 순서대로, 건너뛰지 말 것", "이미 읽은 파일은 다시 읽지 말 것" 같은 토큰 효율 규칙과 분기 조건까지 들어 있습니다. AI 에이전트를 "재현 가능한 절차"로 길들이는 좋은 교본입니다.
내 반복 업무(예: 주간 보고서 작성)를 /report 슬래시 명령으로 만들어 보세요. 입력 파싱 → 자료 수집 → 초안 → 검증의 단계를 apply.md처럼 번호로 명문화하는 게 핵심입니다.
한 에이전트가 초안을 쓰고, 새 컨텍스트의 다른 에이전트가 비평하는 구조를 실제로 볼 수 있습니다. 특히 검토자 출력 형식을 Part A(기계 적용 가능한 JSON edit) + Part B(판단형 서술)로 나눈 것이 영리합니다 — 자동 적용과 사람/AI 판단을 분리합니다.
아무 글(블로그 초안 등)에 대해 "작성자 → 검토자 → 수정" 루프를 직접 짜 보세요. 검토자에게 old_string/new_string/reason JSON으로 답하게 하면, 그 결과를 그대로 Edit에 먹일 수 있습니다.
SKILL.md의 frontmatter(name·description·allowed-tools)와 번호 레퍼런스(01~07) 분리가 교과서적입니다. description이 "어떤 키워드에 이 스킬이 켜지는가"를 촘촘히 나열한 것을 보세요 — 스킬이 올바르게 트리거되게 하는 비결입니다.
helpers.ts 한 파일에 실무 스크래핑의 핵심이 모여 있습니다: 지수 백오프 + 지터 재시도, 적절한 User-Agent/Accept 헤더, HTML 엔티티 디코딩, 그리고 "라이브러리가 못 푸는 HTML은 정규식으로"라는 현실적 판단까지.
아무 공개 목록 페이지를 골라 Bun으로 작은 스크래퍼를 만들어 보세요. 먼저 node-html-parser로 시도하고, 깨지는 부분만 정규식으로 바꿔 보면 "왜 이 레포가 정규식을 택했는지" 체감됩니다.
moderncv(CV)·custom cover.cls(커버레터)를 lualatex/xelatex로 빌드하는 파이프라인과, 그 결과 PDF를 AI가 직접 열어 페이지 나눔을 점검하는 루프를 배웁니다. "생성물의 시각적 품질을 LLM이 스스로 검수"하는 드문 사례입니다.
moderncv로 내 CV를 한 장 만들고 lualatex로 컴파일해 보세요. 일부러 내용을 늘려 2페이지를 넘긴 뒤, \needspace·\enlargethispage로 고아 직함을 잡는 연습을 합니다.
salary_lookup.py는 회사명 매칭의 현실 문제(대소문자, 법인 접미사 A/S·ApS, 덴마크 문자 ø/æ/å)를 정규화 → 부분일치 → 영문화 → 단어 겹침 점수의 다단계로 푼 좋은 예제입니다. 외부 의존성 0(표준 라이브러리만)이라 그대로 읽기 좋습니다.
코드부터 보지 말고 README → CLAUDE.md → apply.md → helpers.ts/salary_lookup.py 순서를 권합니다. "이 워크플로가 무엇을 보장하려 하는가"(검증·날조 금지)를 먼저 이해해야, 개별 코드의 선택이 납득됩니다.
| 항목 | 요구사항 |
|---|---|
| AI 런타임 | Claude Code (CLI) — 이 프레임워크의 지휘자 |
| Python | 3.10+ (연봉 조회·엑셀 변환 도구) |
| Bun | 구직 포털 CLI 도구 실행/설치용 |
| LaTeX | TeX Live 또는 MiKTeX — CV/커버레터 PDF 컴파일(lualatex·xelatex) |
| 연봉 데이터 | 선택. salary_data.json이 없으면 연봉 단계는 자동 생략 |
| 라이선스 | MIT (상업·수정·재배포 자유) |
# 1) 포크 후 클론
gh repo fork MadsLorentzen/ai-job-search --clone
cd ai-job-search
# 2) 포털 검색 도구 설치 (4개 어댑터)
cd .agents/skills/jobbank-search/cli && bun install && cd ../../../..
cd .agents/skills/jobdanmark-search/cli && bun install && cd ../../../..
cd .agents/skills/jobindex-search/cli && bun install && cd ../../../..
cd .agents/skills/jobnet-search/cli && bun install && cd ../../../..
# 3) 프로필 입력 (Claude Code 안에서)
claude
/setup # 대화형 인터뷰 또는 기존 CV 임포트
/scrape # 포털 검색 → 적합도순 매칭
/scrape data science # 특정 분야로 좁히기
/apply https://jobindex.dk/job/1234567 # URL로 지원 워크플로
/apply <공고 전문을 그대로 붙여넣기> # 차단되는 포털 대비
/setup --section search # 검색 쿼리만 다시 설정
기본 포함된 4개 CLI는 덴마크 구직 시장 전용입니다. 한국 등 다른 시장에서 /scrape를 그대로 쓰면 결과가 없습니다. 단, /apply는 공고 URL/텍스트만 있으면 어디서나 동작하므로, 스크래핑 없이 "공고 붙여넣기 → 지원서 생성"만 써도 충분히 유용합니다.
포크 후 /setup으로 내 프로필을 채우고, 관심 공고의 전문을 복사해 /apply <붙여넣기>를 실행하세요. 적합도 평가 → 초안 → 검토 → PDF까지 한 사이클을 끝내 보며 워크플로의 전모를 체감합니다.
apply.md의 Step 3 검토자 프롬프트를 복사해, 내 CV 초안에 대해 단독으로 돌려 보세요. Part A(JSON edit) / Part B(서술) 출력 형식이 왜 유용한지 — 자동 적용 vs 판단 적용 — 직접 확인합니다.
.agents/skills/jobindex-search/의 cli.ts·helpers.ts·SKILL.md 구조를 본떠, 내가 자주 쓰는 구직 사이트용 검색 CLI를 1개 작성하세요. fetch + 백오프 + 결과 파싱(JobCard 형태)을 그대로 재사용하면 됩니다.
공개 연봉 통계를 엑셀로 모아 tools/convert_salary_excel.py로 salary_data.json을 만들고, python salary_lookup.py "회사명" --json이 동작하게 하세요. 그 뒤 /apply 평가 단계에 연봉 지표가 자동으로 끼어드는지 확인합니다.
"공고 → 맞춤 문서" 패턴은 구직 외에도 통합니다. RFP 응답서, 연구비 지원서, 입학 지원서 등 한 도메인을 골라 CLAUDE.md(프로필)·평가틀·템플릿·검증 체크리스트를 갈아끼우고, 작성자-검토자 루프를 그대로 재사용해 보세요. 프레임워크의 일반성을 시험하는 과제입니다.
| 주차 | 주제 | 학습 내용 / 실습 |
|---|---|---|
| 1주차 | Claude Code 기본기 | 슬래시 명령·스킬·서브에이전트 개념. 내 반복 업무용 /명령 1개를 Step 번호로 작성. SKILL.md frontmatter·description 익히기 |
| 2주차 | 멀티 에이전트 + 토큰 효율 | 작성자-검토자 루프 직접 구현. 새 컨텍스트 검토자에 초안을 인라인 전달, Part A/B 출력 설계. "읽은 파일 재읽기 금지" 같은 토큰 절약 규칙 적용 |
| 3주차 | TypeScript/Bun + 회복력 스크래핑 | Bun CLI(@bunli/core) 만들기. 지수 백오프+지터, 헤더 설정, HTML 엔티티 디코딩, 정규식 vs DOM 파서 비교. zod로 입력 검증 |
| 4주차 | LaTeX 자동화 + 자기검증 | moderncv·fontspec로 CV/커버레터 빌드. lualatex/xelatex 차이, 페이지 나눔 제어(needspace). AI가 PDF를 열어 검수하는 루프 + 환각 방지 체크리스트 |
핵심은 특정 기술이 아니라 "AI 에이전트를 신뢰 가능한 워크플로로 조립하는 법"입니다. 명령을 절차로 못 박고(재현성), 역할을 나누고(작성/검토), 결과를 강제로 검증(날조·형식)하는 세 가지가, 어떤 에이전트 프로젝트에도 그대로 옮겨갑니다.
.claude/commands/*.md에 정의하는 재사용 워크플로. 여기선 /setup·/apply·/scrape처럼 "Step 번호로 짠 프롬프트 프로그램"으로 쓰인다.bun install·bun test로 포털 CLI를 구동한다.07-interview-prep.md의 핵심.