# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Auto-Read on Start **세션 시작 시 반드시 수행:** 1. `README.md`를 Read 도구로 읽어 프로젝트 전체 구조 파악 2. 작업 대상 파일의 기존 코드 먼저 확인 ``` 필수 읽기: README.md → 작업 대상 파일 ``` --- ## Critical Rules **절대 지켜야 할 규칙:** | 규칙 | 이유 | |------|------| | 배포 전 `npm run dev` 로컬 테스트 | 프로덕션 장애 방지 | | D1 스키마 변경 시 마이그레이션 SQL 별도 작성 | 기존 데이터 보존 | | Secrets(BOT_TOKEN, API_KEY 등) 코드에 하드코딩 금지 | 보안 | | `wrangler.toml`의 ID 값 변경 금지 | 리소스 연결 유지 | | Function Calling 도구 추가 시 `tools` 배열 + `executeFunctionCall()` 동시 수정 | 불일치 방지 | **위험한 작업:** - `wrangler d1 execute` 직접 실행 (production) → 반드시 확인 요청 - `user_deposits`, `deposit_transactions` 테이블 직접 수정 → 금전 관련, 주의 --- ## Documentation Rules **작업 완료 후 문서 자동 업데이트:** ### 트리거 조건 | 변경 유형 | 업데이트 대상 | |-----------|---------------| | `src/*.ts` 파일 수정 | CLAUDE.md (Core Services, Key Patterns) | | 새 Function Calling 도구 추가 | 양쪽 (README: 지원 기능 테이블, CLAUDE: tools 목록) | | `schema.sql` 변경 | 양쪽 (Data Layer 섹션) | | `wrangler.toml` 환경변수 추가 | 양쪽 (Configuration 섹션) | | 외부 API 연동 추가/변경 | 양쪽 (External Integrations) | | 봇 명령어 추가 | 양쪽 (Commands 섹션) | ### 업데이트 체크리스트 ``` [ ] CLAUDE.md - 기술 상세 (개발자용) [ ] README.md - 사용자 가이드 (배포/운영자용) [ ] 다이어그램/플로우 수정 필요 여부 [ ] wrangler.toml 주석 업데이트 ``` --- ## Commands ```bash npm run dev # 로컬 개발 (wrangler dev) npm run deploy # Cloudflare Workers 배포 npm run db:init # D1 스키마 초기화 (production) ⚠️ 주의 npm run db:init:local # D1 스키마 초기화 (local) npm run tail # Workers 로그 스트리밍 ``` **Secrets 설정:** ```bash wrangler secret put BOT_TOKEN # Telegram Bot Token wrangler secret put WEBHOOK_SECRET # Webhook 검증용 wrangler secret put OPENAI_API_KEY # OpenAI API 키 ``` **Webhook 설정:** ```bash curl https://telegram-summary-bot.kappa-d8e.workers.dev/setup-webhook curl https://telegram-summary-bot.kappa-d8e.workers.dev/webhook-info ``` --- ## Code Style & Conventions ### TypeScript - **Strict mode**: `tsconfig.json`에서 strict 활성화 - **타입 정의**: `types.ts`에 인터페이스 집중 관리 - **any 사용 금지**: 불가피한 경우 주석으로 이유 명시 ### 에러 핸들링 ```typescript // 패턴: try-catch + 사용자 친화적 메시지 try { // 작업 } catch (error) { console.error('[ServiceName] 작업 실패:', error); return '죄송합니다. 일시적인 오류가 발생했습니다.'; } ``` ### 로깅 규칙 ```typescript console.log('[ServiceName] 동작 설명'); // 정상 동작 console.error('[ServiceName] 에러 설명:', error); // 에러 // wrangler tail로 확인 가능 ``` ### 네이밍 - 파일: `kebab-case.ts` (예: `openai-service.ts`) - 함수: `camelCase` (예: `executeFunctionCall`) - 상수: `UPPER_SNAKE_CASE` (예: `SUMMARY_THRESHOLD`) - 타입/인터페이스: `PascalCase` (예: `TelegramUpdate`) --- ## Testing **현재 테스트 스크립트 없음** - 수동 테스트 필수 ### 로컬 테스트 절차 ```bash # 1. 로컬 D1 초기화 (최초 1회) npm run db:init:local # 2. 로컬 서버 실행 npm run dev # 3. 다른 터미널에서 테스트 요청 curl -X POST http://localhost:8787/webhook \ -H "Content-Type: application/json" \ -H "X-Telegram-Bot-Api-Secret-Token: test-secret" \ -d '{"message":{"chat":{"id":123},"text":"테스트"}}' ``` ### 배포 후 확인 ```bash # 로그 스트리밍 npm run tail # Webhook 상태 확인 curl https://telegram-summary-bot.kappa-d8e.workers.dev/webhook-info ``` --- ## Troubleshooting ### 자주 발생하는 에러 | 증상 | 원인 | 해결 | |------|------|------| | `D1_ERROR: no such table` | 스키마 미적용 | `npm run db:init` 실행 | | `401 Unauthorized` (OpenAI) | API 키 만료/잘못됨 | `wrangler secret put OPENAI_API_KEY` | | Webhook 응답 없음 | Secret Token 불일치 | `WEBHOOK_SECRET` 재설정 후 `/setup-webhook` | | Function Calling 무한 루프 | tool_choice 설정 오류 | `tool_choice: "auto"` 확인 | | 프로필 업데이트 안됨 | 메시지 20개 미만 | `/context`로 버퍼 수 확인 | | Email Worker 파싱 실패 | SMS 형식 변경 | `index.ts`의 정규식 패턴 확인 | | **AI가 도구 호출 안 함** | 키워드 미인식 | 시스템 프롬프트 + 도구 description에 키워드 추가 | | **예치금 최소 금액 제한** | Agent 프롬프트 문제 | Deposit Agent 프롬프트 수정 (OpenAI API) | ### 디버깅 명령어 ```bash # D1 데이터 직접 조회 (로컬) wrangler d1 execute telegram-conversations --local --command "SELECT * FROM users LIMIT 5" # D1 데이터 직접 조회 (production) ⚠️ 주의 wrangler d1 execute telegram-conversations --command "SELECT * FROM users LIMIT 5" ``` --- ## Architecture **Message Flow:** ``` Telegram Webhook → Security Validation → Command/Message Router ↓ ┌──────────────────────────┴──────────────────────────┐ ↓ ↓ Command Handler AI Response Generator (commands.ts) (openai-service.ts) ↓ Function Calling (8개) (weather, search, time, calc, docs, domain, suggest_domains, deposit) ↓ Profile System (summary-service.ts) ``` **Core Services:** | 파일 | 역할 | 주요 함수 | |------|------|----------| | `index.ts` | Worker 진입점, Email Handler | `fetch()`, `email()` | | `openai-service.ts` | AI 응답 + Function Calling | `generateResponse()`, `executeFunctionCall()` | | `summary-service.ts` | 프로필 시스템 | `updateSummary()`, `getConversationContext()` | | `deposit-agent.ts` | 예치금 에이전트 (Assistants API) | `callDepositAgent()`, `executeDepositFunction()` | | `security.ts` | Webhook 보안 | `validateWebhook()`, `checkRateLimit()` | | `commands.ts` | 봇 명령어 | `handleCommand()` | | `telegram.ts` | Telegram API | `sendMessage()`, `sendTypingAction()` | **Function Calling Tools (8개):** | 도구 | 함수명 | 외부 API | 트리거 키워드 | |------|--------|----------|---------------| | 날씨 | `get_weather` | wttr.in | 날씨 | | 검색 | `search_web` | Brave Search | ~란, ~뭐야 (한글→영문 자동 번역) | | 시간 | `get_current_time` | 내장 | 몇 시, 시간 | | 계산 | `calculate` | 내장 | 계산, +, -, *, / | | 문서 | `lookup_docs` | Context7 | 문서, 사용법, API | | 도메인 | `manage_domain` | 코드 직접 처리 → Namecheap | 도메인, 네임서버, WHOIS | | 도메인 추천 | `suggest_domains` | GPT + Namecheap | **도메인 추천, 도메인 제안, 도메인 아이디어** | | 예치금 | `manage_deposit` | Deposit Agent → D1 | **입금, 충전, 잔액, 계좌, 송금** | **Data Layer (D1 SQLite):** | 테이블 | 용도 | 주요 컬럼 | |--------|------|----------| | `users` | 사용자 | telegram_id, username | | `message_buffer` | 대화 기록 | user_id, role, content | | `summaries` | 프로필 | user_id, generation, summary | | `user_deposits` | 예치금 계정 | user_id, balance | | `deposit_transactions` | 거래 내역 | user_id, amount, status | | `bank_notifications` | SMS 파싱 | depositor_name, amount, bank | | `user_domains` | 도메인 소유권 | user_id, domain, verified (등록 시 자동 추가) | **AI Fallback:** OpenAI 미설정 시 Workers AI (Llama 3.1 8B) 자동 전환 ### 동적 도구 로딩 **목적:** 토큰 절약 + AI 선택 정확도 향상 ``` 사용자 메시지 → 키워드 패턴 매칭 → 관련 도구만 선택 → AI 호출 ``` **카테고리 분류:** | 카테고리 | 도구 | 감지 패턴 | |----------|------|-----------| | domain | manage_domain, suggest_domains | 도메인, 네임서버, whois, .com | | deposit | manage_deposit | 입금, 충전, 잔액, 계좌 | | weather | get_weather | 날씨, 기온, 비, 눈 | | search | search_web, lookup_docs | 검색, 찾아, 뭐야, 가격 | | utility | get_current_time, calculate | (항상 포함) | **폴백:** 패턴 매칭 없으면 전체 도구 사용 **로그:** `[ToolSelector] 카테고리: domain, utility / 선택된 도구: manage_domain, ...` --- ## Key Patterns ### Function Calling 추가 방법 ```typescript // 1. openai-service.ts의 tools 배열에 추가 const tools = [ // ... 기존 도구들 { type: "function", function: { name: "new_tool", description: "도구 설명", parameters: { /* JSON Schema */ } } } ]; // 2. executeFunctionCall()에 케이스 추가 case 'new_tool': return await executeNewTool(args); ``` ### 프로필 시스템 흐름 (3개 요약 통합 방식) ``` 메시지 수신 → message_buffer 저장 (최대 19개) ↓ 20개 도달 ┌──────────────┴──────────────┐ ↓ ↓ 기존 요약 3개 조회 사용자 발언만 추출 ↓ ↓ └──────────→ OpenAI 통합 분석 ←┘ ↓ summaries 테이블 저장 (generation++) ↓ 구버전 삭제 (최근 3개만 유지) ``` **통합 분석 방식:** - 기존: 최신 요약 1개만 참조하여 업데이트 - 변경: **모든 요약 (최대 3개) + 새 메시지 → AI가 통합 분석** ### Context Enrichment ```typescript // getConversationContext() 반환값 구조 { previousSummary: Summary | null, // 최신 요약 (호환성) summaries: Summary[], // 전체 요약 (최대 3개, 최신순) recentMessages: BufferedMessage[], totalMessages: number, } ``` **시스템 프롬프트에 통합:** ``` ## 사용자 프로필 (3개 버전 통합) [v1] 초기 프로필 내용... [v2] 업데이트된 프로필... [v3] 최신 프로필... 최신 버전을 우선시하되, 이전 버전 맥락도 고려 ``` ### AI 시스템 프롬프트 (`summary-service.ts`) ``` - 날씨, 시간, 계산 요청은 제공된 도구를 사용하세요. - 최신 정보, 실시간 데이터, 현재 가격, 뉴스 등은 search_web 도구로 검색하세요. - 예치금, 입금, 충전, 잔액, 계좌 관련 요청은 반드시 manage_deposit 도구를 사용하세요. - 도메인 추천, 도메인 제안, 도메인 아이디어 요청은 반드시 suggest_domains 도구를 사용하세요. - 기타 도메인 관련 요청(조회, 등록, 네임서버 등)은 manage_domain 도구를 사용하세요. - manage_deposit, manage_domain, suggest_domains 도구 결과는 그대로 전달하세요. ``` **중요:** 메인 AI가 도구를 호출하지 않고 직접 답변하는 경우: 1. 시스템 프롬프트에 해당 키워드 추가 (`summary-service.ts:252-254`) 2. 도구 description에 키워드 명시 (`openai-service.ts` tools 배열) ### 검색 한글→영문 자동 번역 ``` "판골린 VPN" → GPT-4o-mini 번역 → "Pangolin VPN" → Brave Search ``` **동작:** - 한글 포함 검색어 감지 (`/[가-힣]/`) - GPT-4o-mini로 영문 번역 (외래어/기술용어 원어 복원) - 번역된 쿼리로 검색 - 결과에 원본+번역 표시: `검색 결과: 판골린 VPN (→ Pangolin VPN)` **로그:** `[search_web] 번역: "판골린 VPN" → "Pangolin VPN"` ### Deposit Agent 프롬프트 수정 방법 ```bash # Vault에서 API 키 조회 curl -s -H "X-Vault-Token: hvs.xxx" https://vault.anvil.it.com/v1/secret/data/openai # Assistant 프롬프트 업데이트 curl -X POST 'https://api.openai.com/v1/assistants/asst_XMoVGU7ZwRpUPI6PHGvRNm8E' \ -H 'Authorization: Bearer sk-xxx' \ -H 'OpenAI-Beta: assistants=v2' \ -d @update-agent.json ``` --- ## Configuration `wrangler.toml` 환경변수: | 변수 | 기본값 | 설명 | |------|--------|------| | `SUMMARY_THRESHOLD` | 20 | 프로필 업데이트 주기 (메시지 수) | | `MAX_SUMMARIES_PER_USER` | 3 | 유지할 프로필 버전 수 | | `DOMAIN_OWNER_ID` | - | 도메인 관리 권한 Telegram ID | | `DEPOSIT_AGENT_ID` | - | 예치금 관리 Assistant ID | | `DEPOSIT_ADMIN_ID` | - | 예치금 관리 권한 Telegram ID | | `BANK_API_SECRET` | - | 입금 알림 API 인증 키 (wrangler secret) | | `BRAVE_API_KEY` | - | Brave Search API 키 (wrangler secret) | | `DEPOSIT_API_SECRET` | - | Deposit API 인증 키 (namecheap-api용, wrangler secret) | --- ## External Integrations | 서비스 | 용도 | 엔드포인트 | 주의사항 | |--------|------|-----------|----------| | Context7 | 문서 조회 | context7.com API | - | | Deposit Agent | 예치금 관리 | OpenAI Assistants | `asst_XMoVGU7ZwRpUPI6PHGvRNm8E` | | Namecheap API | 도메인 백엔드 | namecheap-api.anvil.it.com | 날짜: MM/DD/YYYY → ISO 변환 | | WHOIS API | WHOIS 조회 | whois-api-eight.vercel.app | ccSLD 미지원 | | wttr.in | 날씨 | wttr.in | - | | Brave Search | 검색 | api.search.brave.com | Free AI 플랜 (2,000/월) | | Vault | API 키 관리 | vault.anvil.it.com | - | | Gmail | 입금 SMS 수신 | deposit.anvil@gmail.com | Apps Script 연동 | | Apps Script | Gmail → Worker 연동 | script.google.com | 1분마다 실행, message_id 중복 방지 | --- ## Deposit System **자동 매칭 흐름:** ``` [시나리오 1: 사용자 먼저] "홍길동 50000원 입금" → bank_notifications 검색 ↓ 매칭 O → confirmed + 잔액↑ | 매칭 X → pending [시나리오 2: SMS 먼저 - Gmail → Apps Script → Worker] 은행 SMS → Gmail(deposit.anvil@gmail.com) → Apps Script (1분마다) ↓ POST /api/bank-notification ↓ 파싱 → bank_notifications 저장 ↓ deposit_transactions 검색 (pending) ↓ 매칭 O → confirmed + 잔액↑ + 사용자/관리자 알림 매칭 X → 저장만 + 관리자 알림 ``` **알림 시스템:** | 이벤트 | 사용자 알림 | 관리자 알림 | |--------|-------------|-------------| | 자동 매칭 성공 | ✅ 입금액 + 현재 잔액 | ✅ 입금 정보 + 매칭 완료 | | 매칭 대기 (SMS만) | - | ✅ 입금 정보 + 대기 상태 | **Gmail → Worker 연동:** - Gmail 계정: `deposit.anvil@gmail.com` - Apps Script: 1분마다 `is:unread 입금` 검색 → Worker API 호출 - 중복 방지: Gmail message_id 기반 **API 엔드포인트:** ``` POST /api/bank-notification Content-Type: application/json { "content": "[Web발신]\n하나,01/16, 23:30\n427******27104\n입금5원\n황병하", "messageId": "19bc737b3415596a", "secret": "BANK_API_SECRET 값" } ``` **입금 계좌:** 하나은행 427-910018-27104 (주식회사 아이언클래드) - Vault 경로: `secret/companies/ironclad-corp` **Deposit Agent 핵심 규칙:** ``` 1. 금액 제한 없음: 1원도 입금 가능 2. 입금 신고 시 반드시 입금자명 + 금액 확인 (빠지면 물어보기) 3. "계좌번호 주세요" → get_account_info 호출 4. 자연어 금액 인식: "만원"→10000, "5천원"→5000, "삼만오천원"→35000 5. 즉시 실행: 입금자명+금액 있으면 확인 없이 바로 request_deposit 호출 6. 간편 취소: "취소해줘" → 최근 pending 자동 선택 7. 동시 요청 허용: 기존 pending 있어도 새 입금 신고 가능 ``` **Deposit Agent 응답 포맷:** ``` 잔액 조회: "현재 잔액: 10,000원" 입금 성공: "입금 확인! 5,000원 충전. 잔액: 15,000원" 입금 대기: "입금 요청 등록! 은행 확인 후 자동 충전됩니다." 거래 내역: "#5: 입금 10원 ✓ (01/17)" (✓확인, ⏳대기, ✗취소) ``` **Deposit Agent 도구:** | 함수 | 설명 | 권한 | |------|------|------| | `get_balance` | 잔액 조회 | 모든 사용자 | | `get_account_info` | 입금 계좌 안내 | 모든 사용자 | | `request_deposit` | 입금 신고 | 모든 사용자 | | `get_transactions` | 거래 내역 | 모든 사용자 | | `cancel_transaction` | 입금 취소 | 모든 사용자 | | `get_pending_list` | 대기 목록 | 관리자 | | `confirm_deposit` | 입금 확인 | 관리자 | | `reject_deposit` | 입금 거절 | 관리자 | --- ## Domain System **아키텍처 변경 (2025-01):** Agent 기반 → 코드 직접 처리 | 구분 | 이전 (Agent) | 현재 (코드) | |------|-------------|-------------| | 의도 파악 | Domain Agent | 메인 AI (action 파라미터) | | API 호출 | Agent Function Calling | `executeDomainAction()` | | 응답 형식 | Agent 생성 (불안정) | 코드 고정 (100% 일관성) | | 비용 | Agent 호출당 ~$0.01 | 없음 | **manage_domain 도구 파라미터:** ```typescript { action: 'register' | 'check' | 'whois' | 'list' | 'info' | 'get_ns' | 'set_ns' | 'price', domain?: string, // 대상 도메인 nameservers?: string[], // set_ns용 tld?: string // price용 } ``` **action별 처리:** | action | 설명 | 권한 | |--------|------|------| | `list` | 내 도메인 목록 | 소유자 | | `info` | 도메인 상세정보 | 소유자 | | `get_ns` | 네임서버 조회 | 공개 | | `set_ns` | 네임서버 변경 | 소유자 | | `check` | 가용성 확인 + 가격 | 공개 | | `whois` | WHOIS 조회 | 공개 | | `price` | TLD 가격 | 공개 | | `register` | 등록 확인 페이지 | 사용자 | ### 도메인 등록 흐름 ``` 사용자: "example.com 등록해줘" ↓ 메인 AI: manage_domain(action="register", domain="example.com") ↓ executeDomainAction(): 1. check_domains API → 가용성 확인 2. get_price API → 가격 조회 3. DB 조회 → 현재 잔액 확인 4. 고정 형식 응답 생성 ↓ ┌─────────────────────┬─────────────────────┐ │ 잔액 충분 시 │ 잔액 부족 시 │ ├─────────────────────┼─────────────────────┤ │ 📋 도메인 등록 확인 │ 📋 도메인 등록 확인 │ │ • 도메인: example.com│ • 도메인: example.com│ │ • 가격: 15,000원 │ • 가격: 15,000원 │ │ • 현재 잔액: ✓ │ • 현재 잔액: ⚠️ 부족 │ │ • 등록 기간: 1년 │ • 부족 금액: X원 │ │ 📌 등록자 정보 │ │ │ ⚠️ 취소/환불 불가 │ 💳 입금 계좌 │ │ │ 하나은행 427-... │ │ '확인' 입력 요청 │ 입금 안내 │ └─────────────────────┴─────────────────────┘ ``` ### 도메인 추천 기능 (`suggest_domains`) **별도 구현된 코드 레벨 도구** ``` 사용자: "커피숍 도메인 추천해줘" ↓ 1. GPT-4o-mini: 키워드 기반 창의적 도메인 15개 생성 ↓ 2. Namecheap API: check_domains로 가용성 일괄 확인 ↓ 3. 등록 가능 도메인 < 10개? → 1-2 반복 (최대 3회) ↓ 4. Namecheap API: TLD별 가격 조회 ↓ 5. 결과 포맷팅 (등록 가능한 것만 표시, 10개 목표) ``` **특징:** - 등록 가능 도메인만 표시 (이미 등록된 도메인 미표시) - 10개 미만 시 자동 재시도 (최대 3회) - 이전에 체크한 도메인은 제외하고 새로 생성 **Namecheap API:** - 엔드포인트: `namecheap-api.anvil.it.com` - 가격 정책: Namecheap 원가 + 13%, 매일 환율 업데이트 - 권한 체크: `user_domains` 테이블 `verified=1` **등록자 정보:** - 현재: 서비스 기본 정보만 지원 (일본 주소) - WHOIS Guard 자동 적용 (개인정보 비공개) - 추후: 사용자 본인 정보로 등록 옵션 추가 예정 **Deposit API (namecheap-api용):** ``` GET /api/deposit/balance?telegram_id=xxx # 잔액 조회 POST /api/deposit/deduct # 잔액 차감 { telegram_id, amount, reason } Header: X-API-Key: DEPOSIT_API_SECRET ```