algokit-mcp 1.0.1 → 1.0.3

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 (102) hide show
  1. package/README.md +284 -24
  2. package/dist/api/boj-scraper.js +1 -1
  3. package/dist/api/boj-scraper.js.map +1 -1
  4. package/dist/api/programmers-scraper.d.ts +0 -5
  5. package/dist/api/programmers-scraper.d.ts.map +1 -1
  6. package/dist/api/programmers-scraper.js +59 -90
  7. package/dist/api/programmers-scraper.js.map +1 -1
  8. package/dist/index.js +38 -35
  9. package/dist/index.js.map +1 -1
  10. package/dist/tools/boj/analyze-code-submission-boj.d.ts +67 -0
  11. package/dist/tools/boj/analyze-code-submission-boj.d.ts.map +1 -0
  12. package/dist/tools/boj/analyze-code-submission-boj.js +91 -0
  13. package/dist/tools/boj/analyze-code-submission-boj.js.map +1 -0
  14. package/dist/tools/boj/analyze-problem-boj.d.ts +48 -0
  15. package/dist/tools/boj/analyze-problem-boj.d.ts.map +1 -0
  16. package/dist/tools/boj/analyze-problem-boj.js +52 -0
  17. package/dist/tools/boj/analyze-problem-boj.js.map +1 -0
  18. package/dist/tools/boj/fetch-problem-content-boj.d.ts +49 -0
  19. package/dist/tools/boj/fetch-problem-content-boj.d.ts.map +1 -0
  20. package/dist/tools/boj/fetch-problem-content-boj.js +95 -0
  21. package/dist/tools/boj/fetch-problem-content-boj.js.map +1 -0
  22. package/dist/tools/boj/generate-hint-boj.d.ts +42 -0
  23. package/dist/tools/boj/generate-hint-boj.d.ts.map +1 -0
  24. package/dist/tools/boj/generate-hint-boj.js +82 -0
  25. package/dist/tools/boj/generate-hint-boj.js.map +1 -0
  26. package/dist/tools/boj/generate-review-template-boj.d.ts +48 -0
  27. package/dist/tools/boj/generate-review-template-boj.d.ts.map +1 -0
  28. package/dist/tools/boj/generate-review-template-boj.js +52 -0
  29. package/dist/tools/boj/generate-review-template-boj.js.map +1 -0
  30. package/dist/tools/boj/get-problem-boj.d.ts +22 -0
  31. package/dist/tools/boj/get-problem-boj.d.ts.map +1 -0
  32. package/dist/tools/boj/get-problem-boj.js +89 -0
  33. package/dist/tools/boj/get-problem-boj.js.map +1 -0
  34. package/dist/tools/boj/search-problems-boj.d.ts +42 -0
  35. package/dist/tools/boj/search-problems-boj.d.ts.map +1 -0
  36. package/dist/tools/boj/search-problems-boj.js +143 -0
  37. package/dist/tools/boj/search-problems-boj.js.map +1 -0
  38. package/dist/tools/boj/search-tags-boj.d.ts +22 -0
  39. package/dist/tools/boj/search-tags-boj.d.ts.map +1 -0
  40. package/dist/tools/boj/search-tags-boj.js +70 -0
  41. package/dist/tools/boj/search-tags-boj.js.map +1 -0
  42. package/dist/tools/get-problem-boj.d.ts +22 -0
  43. package/dist/tools/get-problem-boj.d.ts.map +1 -0
  44. package/dist/tools/get-problem-boj.js +89 -0
  45. package/dist/tools/get-problem-boj.js.map +1 -0
  46. package/dist/tools/programmers/analyze-code-submission-programmers.d.ts +60 -0
  47. package/dist/tools/programmers/analyze-code-submission-programmers.d.ts.map +1 -0
  48. package/dist/tools/programmers/analyze-code-submission-programmers.js +85 -0
  49. package/dist/tools/programmers/analyze-code-submission-programmers.js.map +1 -0
  50. package/dist/tools/programmers/analyze-problem-programmers.d.ts +48 -0
  51. package/dist/tools/programmers/analyze-problem-programmers.d.ts.map +1 -0
  52. package/dist/tools/programmers/analyze-problem-programmers.js +53 -0
  53. package/dist/tools/programmers/analyze-problem-programmers.js.map +1 -0
  54. package/dist/tools/programmers/fetch-problem-content-programmers.d.ts +46 -0
  55. package/dist/tools/programmers/fetch-problem-content-programmers.d.ts.map +1 -0
  56. package/dist/tools/programmers/fetch-problem-content-programmers.js +76 -0
  57. package/dist/tools/programmers/fetch-problem-content-programmers.js.map +1 -0
  58. package/dist/tools/programmers/generate-hint-programmers.d.ts +42 -0
  59. package/dist/tools/programmers/generate-hint-programmers.d.ts.map +1 -0
  60. package/dist/tools/programmers/generate-hint-programmers.js +80 -0
  61. package/dist/tools/programmers/generate-hint-programmers.js.map +1 -0
  62. package/dist/tools/programmers/generate-review-template-programmers.d.ts +48 -0
  63. package/dist/tools/programmers/generate-review-template-programmers.d.ts.map +1 -0
  64. package/dist/tools/programmers/generate-review-template-programmers.js +53 -0
  65. package/dist/tools/programmers/generate-review-template-programmers.js.map +1 -0
  66. package/dist/tools/programmers/get-problem-programmers.d.ts +53 -0
  67. package/dist/tools/programmers/get-problem-programmers.d.ts.map +1 -0
  68. package/dist/tools/programmers/get-problem-programmers.js +163 -0
  69. package/dist/tools/programmers/get-problem-programmers.js.map +1 -0
  70. package/dist/tools/programmers/search-problems-programmers.d.ts +73 -0
  71. package/dist/tools/programmers/search-problems-programmers.d.ts.map +1 -0
  72. package/dist/tools/programmers/search-problems-programmers.js +173 -0
  73. package/dist/tools/programmers/search-problems-programmers.js.map +1 -0
  74. package/dist/tools/search-problems-boj.d.ts +42 -0
  75. package/dist/tools/search-problems-boj.d.ts.map +1 -0
  76. package/dist/tools/search-problems-boj.js +143 -0
  77. package/dist/tools/search-problems-boj.js.map +1 -0
  78. package/dist/tools/search-tags-boj.d.ts +22 -0
  79. package/dist/tools/search-tags-boj.d.ts.map +1 -0
  80. package/dist/tools/search-tags-boj.js +70 -0
  81. package/dist/tools/search-tags-boj.js.map +1 -0
  82. package/dist/utils/agent-skill-installer.d.ts +9 -0
  83. package/dist/utils/agent-skill-installer.d.ts.map +1 -0
  84. package/dist/utils/agent-skill-installer.js +94 -0
  85. package/dist/utils/agent-skill-installer.js.map +1 -0
  86. package/dist/utils/claudecode-skill-installer.d.ts +6 -0
  87. package/dist/utils/claudecode-skill-installer.d.ts.map +1 -0
  88. package/dist/utils/claudecode-skill-installer.js +70 -0
  89. package/dist/utils/claudecode-skill-installer.js.map +1 -0
  90. package/dist/utils/skill-installer.d.ts +6 -0
  91. package/dist/utils/skill-installer.d.ts.map +1 -0
  92. package/dist/utils/skill-installer.js +70 -0
  93. package/dist/utils/skill-installer.js.map +1 -0
  94. package/dist/utils/tier-converter.d.ts.map +1 -1
  95. package/dist/utils/tier-converter.js +3 -0
  96. package/dist/utils/tier-converter.js.map +1 -1
  97. package/package.json +3 -4
  98. package/skills/algo:code-review/SKILL.md +134 -0
  99. package/skills/algo:fetch/SKILL.md +102 -0
  100. package/skills/algo:hint/SKILL.md +111 -0
  101. package/skills/algo:review/SKILL.md +123 -0
  102. package/skills/algo:search/SKILL.md +122 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AlgoKit-mcp
2
2
 
3
- **백준(BOJ)과 프로그래머스 알고리즘 문제 학습을 돕는 MCP 서버 + Skill**
3
+ **AI 에이전트와 함께하는 알고리즘 학습 MCP 툴킷 (백준, 프로그래머스 지원)**
4
4
 
5
5
  AI 기반 힌트 생성, 문제 분석, 복습 템플릿 제공으로 효율적인 알고리즘 학습을 지원합니다.
6
6
 
@@ -12,9 +12,117 @@ AI 기반 힌트 생성, 문제 분석, 복습 템플릿 제공으로 효율적
12
12
  - 📝 **복습 템플릿**: 자동 생성되는 마크다운 복습 문서
13
13
  - 🧪 **코드 분석**: 제출 코드의 시간/공간 복잡도 분석 및 개선 제안
14
14
 
15
+ ## 플랫폼별 설정 경로
16
+
17
+ | 항목 | Claude Code | Codex | Gemini CLI |
18
+ |------|-------------|-------|------------|
19
+ | **Skills 기능명** | Skills | Agent Skills | Agent Skills |
20
+ | **Skills 호출** | `/skill-name` | `$skill-name` or 자동 | 모델 자율 판단 |
21
+ | **MCP 설정 (프로젝트)** | `.mcp.json` | `.codex/config.toml` | `.gemini/settings.json` |
22
+ | **MCP 설정 (전역)** | `~/.claude.json` | `~/.codex/config.toml` | `~/.gemini/settings.json` |
23
+ | **Skills (프로젝트)** | `.claude/skills/` | `.codex/skills/` | `.gemini/skills/` |
24
+ | **Skills (전역)** | `~/.claude/skills/` | `~/.codex/skills/` | `~/.gemini/skills/` |
25
+
26
+ > Skills는 MCP 서버 첫 기동 시 감지된 플랫폼에 자동 설치됩니다.
27
+
28
+ ---
29
+
15
30
  ## 설치
16
31
 
17
- ### Claude Code (추천)
32
+ > **Skills 자동 설치**: MCP 서버 첫 기동 시 설치된 AI 에이전트를 자동으로 감지하여 Skills를 설치합니다.
33
+
34
+ ### Claude Code
35
+
36
+ #### 자동 설치
37
+
38
+ ```bash
39
+ # 전역 설정 (모든 프로젝트에서 사용)
40
+ claude mcp add --scope user algokit -- npx -y algokit-mcp
41
+
42
+ # 프로젝트 설정 (.mcp.json 생성)
43
+ claude mcp add --scope project algokit -- npx -y algokit-mcp
44
+
45
+
46
+ ```
47
+
48
+ #### 수동 설치
49
+
50
+ `~/.claude.json` (전역) 편집:
51
+
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "algokit": {
56
+ "command": "npx",
57
+ "args": ["-y", "algokit-mcp"]
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ 또는 프로젝트 루트에 `.mcp.json` 생성 ([예시](.claude/examples/claude-code-config.json)):
64
+
65
+ ```json
66
+ {
67
+ "mcpServers": {
68
+ "algokit": {
69
+ "command": "npx",
70
+ "args": ["-y", "algokit-mcp"]
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ #### Skills 위치
77
+
78
+ | 등록 방식 | Skills 위치 |
79
+ |----------|------------|
80
+ | `--scope user` (전역) | `~/.claude/skills/` |
81
+ | `--scope project` (`.mcp.json` 있음) | `<프로젝트>/.claude/skills/` |
82
+
83
+
84
+ ---
85
+
86
+ ### Codex
87
+
88
+ #### 자동 설치
89
+
90
+ ```bash
91
+ # 전역 설치
92
+ codex mcp add algokit -- npx -y algokit-mcp
93
+ ```
94
+
95
+ #### 수동 설치
96
+
97
+ `~/.codex/config.toml` (전역) 또는 `.codex/config.toml` (프로젝트) 편집 ([예시](.claude/examples/codex-config.toml)):
98
+
99
+ ```toml
100
+ [mcp.servers.algokit]
101
+ command = "npx"
102
+ args = ["-y", "algokit-mcp"]
103
+ ```
104
+
105
+ #### Skills 위치
106
+
107
+ | 등록 방식 | Skills 위치 |
108
+ |----------|------------|
109
+ | 프로젝트 (`.codex/` 있음) | `<프로젝트>/.codex/skills/` |
110
+ | 전역 (`~/.codex/` 있음) | `~/.codex/skills/` |
111
+
112
+ ---
113
+
114
+ ### Gemini CLI
115
+
116
+ #### 자동 설치
117
+
118
+ ```bash
119
+ # 전역 설치
120
+ gemini mcp add algokit -- npx -y algokit-mcp
121
+ ```
122
+
123
+ #### 수동 설치
124
+
125
+ `~/.gemini/settings.json` (전역) 또는 `.gemini/settings.json` (프로젝트) 편집 ([예시](.claude/examples/gemini-config.json)):
18
126
 
19
127
  ```json
20
128
  {
@@ -27,15 +135,22 @@ AI 기반 힌트 생성, 문제 분석, 복습 템플릿 제공으로 효율적
27
135
  }
28
136
  ```
29
137
 
138
+ #### Skills 위치
139
+
140
+ | 등록 방식 | Skills 위치 |
141
+ |----------|------------|
142
+ | 프로젝트 (`.gemini/` 있음) | `<프로젝트>/.gemini/skills/` |
143
+ | 전역 (`~/.gemini/` 있음) | `~/.gemini/skills/` |
144
+
30
145
  ## MCP 도구
31
146
 
32
147
  ### 백준 (BOJ)
33
148
 
34
149
  | 도구 | 설명 |
35
150
  |------|------|
36
- | `search_problems` | 문제 검색 (티어, 태그, 키워드) |
37
- | `get_problem` | 문제 상세 조회 |
38
- | `search_tags` | 알고리즘 태그 검색 |
151
+ | `search_problems_boj` | 문제 검색 (티어, 태그, 키워드) |
152
+ | `get_problem_boj` | 문제 상세 조회 |
153
+ | `search_tags_boj` | 알고리즘 태그 검색 |
39
154
  | `fetch_problem_content_boj` | 문제 본문 스크래핑 |
40
155
  | `analyze_problem_boj` | 문제 분석 및 힌트 가이드 |
41
156
  | `generate_hint_boj` | 3단계 힌트 생성 |
@@ -46,45 +161,190 @@ AI 기반 힌트 생성, 문제 분석, 복습 템플릿 제공으로 효율적
46
161
 
47
162
  | 도구 | 설명 |
48
163
  |------|------|
49
- | `search_programmers_problems` | 문제 검색 (난이도, 카테고리) |
50
- | `get_programmers_problem` | 문제 상세 조회 |
164
+ | `search_problems_programmers` | 문제 검색 (난이도, 카테고리) |
165
+ | `get_problem_programmers` | 문제 상세 조회 |
51
166
  | `fetch_problem_content_programmers` | 문제 본문 스크래핑 |
52
167
  | `analyze_problem_programmers` | 문제 분석 |
53
168
  | `generate_hint_programmers` | 힌트 생성 |
54
169
  | `generate_review_template_programmers` | 복습 템플릿 |
55
170
  | `analyze_code_submission_programmers` | 코드 분석 |
56
171
 
57
- ## MCP Skill (Claude Code 전용)
172
+ ## Skills
58
173
 
59
- **Skill 이름**: `algo:`
174
+ **Skill 이름**: `algo:` (BOJ/프로그래머스 자동 선택)
60
175
 
61
- 플랫폼(BOJ/프로그래머스)을 자동으로 선택하여 자연스러운 학습 경험을 제공합니다.
176
+ Claude Code, Codex, Gemini CLI 모두 동일한 `algo:` Skills를 지원합니다.
177
+ MCP 서버 첫 기동 시 감지된 에이전트에 자동으로 설치되며, 수동으로도 설치할 수 있습니다.
62
178
 
63
- ### 설치 방법
179
+ ### 수동 설치
64
180
 
65
181
  ```bash
66
- # Skill 파일 복사 (프로젝트별)
67
- cp skills/* /your/project/.claude/skills/
182
+ # Claude Code
183
+ cp -r skills/* ~/.claude/skills/ # 전역
184
+ cp -r skills/* .claude/skills/ # 프로젝트
185
+
186
+ # Codex
187
+ cp -r skills/* ~/.codex/skills/ # 전역
188
+ cp -r skills/* .codex/skills/ # 프로젝트
68
189
 
69
- # 또는 전역 설정
70
- cp skills/* ~/.claude/skills/
190
+ # Gemini CLI
191
+ cp -r skills/* ~/.gemini/skills/ # 전역
192
+ cp -r skills/* .gemini/skills/ # 프로젝트
71
193
  ```
72
194
 
73
195
  ### 사용 예시
74
196
 
197
+ #### `algo:search` - 문제 검색
198
+
199
+ ```
200
+ You: "골드 난이도 DP 문제 추천해줘"
201
+
202
+ Claude: "백준에서 골드 티어의 DP 문제를 검색했습니다.
203
+
204
+ **추천 문제 3개:**
205
+ 1. 🥇 [G5] 12865번 - 평범한 배낭 (✅ 16,234명)
206
+ 2. 🥇 [G4] 9251번 - LCS (✅ 13,892명)
207
+ 3. 🥇 [G3] 1932번 - 정수 삼각형 (✅ 18,234명)
208
+
209
+ 어떤 문제부터 시작하시겠어요?"
75
210
  ```
76
- You: "백준 골드 난이도 DP 문제 3개 추천해줘"
77
- Claude: [자동으로 BOJ search_problems 호출]
78
211
 
79
- You: "이 문제 분석해줘: https://www.acmicpc.net/problem/1927"
80
- Claude: [URL 파싱 → analyze_problem_boj 자동 호출]
212
+ ```
213
+ You: "https://www.acmicpc.net/problem/1927"
214
+
215
+ Claude: "백준 1927번 '최소 힙' 문제를 조회했습니다.
216
+
217
+ **문제 정보:**
218
+ - 난이도: 🥈 Silver I
219
+ - 분류: 자료구조, 우선순위 큐
220
+ - 해결: 28,456명 | 정답률: 47.2%
221
+
222
+ 본문을 보시겠어요? (/algo:fetch)"
223
+ ```
224
+
225
+ #### `algo:hint` - 단계별 힌트
226
+
227
+ ```
228
+ You: "1927번 힌트 줘"
229
+
230
+ Claude: "**💡 Level 1 힌트 (접근 방향)**
231
+
232
+ 이 문제는 **우선순위 큐(Priority Queue)** 자료구조를 사용합니다.
233
+ Python의 `heapq` 모듈 또는 C++의 `priority_queue`를 활용하면
234
+ O(log N) 시간에 최솟값을 찾을 수 있어요.
235
+
236
+ 더 구체적인 힌트가 필요하신가요?"
237
+ ```
238
+
239
+ ```
240
+ You: "더 구체적으로 알려줘"
241
+
242
+ Claude: "**💡 Level 2 힌트 (구현 방법)**
243
+
244
+ 1. **최소 힙** 사용 (Python: heapq, C++: priority_queue<int, greater<int>>)
245
+ 2. x가 0이 아니면 → `heappush(x)`
246
+ 3. x가 0이면 → `heappop()` (빈 경우 0 출력)
247
+
248
+ 시간복잡도: O(N log N)
249
+ 공간복잡도: O(N)
250
+
251
+ 코드 예시를 보시겠어요? (/algo:hint 정답)"
252
+ ```
253
+
254
+ #### `algo:code-review` - 코드 분석
255
+
256
+ ```
257
+ You: "1927번 내 코드 분석해줘"
258
+ You: [Python 코드 붙여넣기]
259
+
260
+ Claude: "코드를 분석했습니다! ✨
261
+
262
+ **✅ 좋은 점:**
263
+ - heapq 모듈을 올바르게 사용
264
+ - 시간복잡도 O(N log N) 달성
265
+
266
+ **⚠️ 개선 가능:**
267
+ 1. `sys.stdin.readline()` 사용 → 입력 속도 개선 (30% 빠름)
268
+ 2. 빈 힙 체크를 `if heap:` 대신 `if len(heap):`로 명시
269
+
270
+ **예상 성능:**
271
+ - 현재: 약 300ms
272
+ - 개선 후: 약 200ms
273
+
274
+ 수정 코드를 보여드릴까요?"
275
+ ```
81
276
 
82
- You: "프로그래머스 Level 2 문제 찾아줘"
83
- Claude: [search_programmers_problems 자동 호출]
277
+ #### `algo:review` - 복습 템플릿
84
278
 
85
- You: "힌트 더 줘"
86
- Claude: [이전 대화 분석 → 적절한 Level 힌트 제공]
87
279
  ```
280
+ You: "1927번 복습 문서 만들어줘"
281
+
282
+ Claude: "복습 템플릿을 생성했습니다! 📝
283
+
284
+ # 백준 1927번 - 최소 힙
285
+
286
+ ## 문제 요약
287
+ 최솟값을 빠르게 찾는 자료구조 구현
288
+
289
+ ## 핵심 알고리즘
290
+ - **우선순위 큐** (최소 힙)
291
+ - 삽입: O(log N), 삭제: O(log N)
292
+
293
+ ## 풀이 과정
294
+ 1. heapq 모듈 import
295
+ 2. 입력에 따라 push/pop 수행
296
+ ...
297
+
298
+ 이 템플릿을 파일로 저장하시겠어요?"
299
+ ```
300
+
301
+ ---
302
+
303
+ ## 학습 워크플로우
304
+
305
+ ### 새 문제 접근하기
306
+
307
+ ```
308
+ 1. You: "골드 3~5 DP 문제 추천"
309
+ → Claude: [문제 3개 추천]
310
+
311
+ 2. You: "1932번 본문 보여줘"
312
+ → Claude: [문제 본문 표시]
313
+
314
+ 3. You: "힌트 줘"
315
+ → Claude: [Level 1 힌트]
316
+
317
+ 4. You: "더 구체적으로"
318
+ → Claude: [Level 2 힌트]
319
+ ```
320
+
321
+ ### 풀이 후 복습하기
322
+
323
+ ```
324
+ 1. You: "1932번 내 코드 분석해줘" + [코드]
325
+ → Claude: [시간복잡도, 개선점 제시]
326
+
327
+ 2. You: "복습 문서 만들어줘"
328
+ → Claude: [마크다운 템플릿 생성]
329
+ ```
330
+
331
+ ---
332
+
333
+ ## 사용 방법
334
+
335
+ ### 일반 사용 (자연스러운 대화)
336
+ ```
337
+ You: "골드 DP 문제 추천해줘"
338
+ → Claude가 맥락 파악 → MCP 도구 자동 호출 → 결과 제공
339
+ ```
340
+
341
+ ### Skill 사용 (명시적 호출, 권장)
342
+ ```
343
+ You: "/algo:search 골드 DP 문제 추천해줘"
344
+ → Skill이 의도 명확히 파악 → 더 정확한 MCP 도구 선택
345
+ ```
346
+
347
+ **차이점**: 둘 다 MCP 도구를 호출하지만, Skill 사용 시 더 명시적이고 정확한 응답을 받을 수 있습니다.
88
348
 
89
349
  ## 개발
90
350
 
@@ -131,7 +391,7 @@ MIT
131
391
 
132
392
  <div align="center">
133
393
 
134
- **[GitHub](https://github.com/itsme-shawn/AlgoKit-mcp)** • **[Issues](https://github.com/itsme-shawn/algoKit-mcp/issues)** • **[NPM](https://www.npmjs.com/package/algokit-mcp-server)**
394
+ **[GitHub](https://github.com/itsme-shawn/AlgoKit-mcp)** • **[Issues](https://github.com/itsme-shawn/algoKit-mcp/issues)** • **[NPM](https://www.npmjs.com/package/algokit-mcp)**
135
395
 
136
396
 
137
397
  </div>
@@ -79,7 +79,7 @@ export class BOJScraper {
79
79
  // 캐시 확인
80
80
  const cached = this.cache.get(problemId);
81
81
  if (cached !== undefined) {
82
- console.log(`[BOJScraper] 캐시 히트: 문제 ${problemId}`);
82
+ process.stderr.write(`[BOJScraper] 캐시 히트: 문제 ${problemId}\n`);
83
83
  return cached;
84
84
  }
85
85
  // Rate Limiting (백업 보호, 캐시 미스 시에만)
@@ -1 +1 @@
1
- {"version":3,"file":"boj-scraper.js","sourceRoot":"","sources":["../../src/api/boj-scraper.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"boj-scraper.js","sourceRoot":"","sources":["../../src/api/boj-scraper.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,6 @@ export declare class ProgrammersScrapeError extends Error {
11
11
  * 프로그래머스 스크래퍼
12
12
  */
13
13
  export declare class ProgrammersScraper {
14
- private browserPool;
15
14
  private rateLimiter;
16
15
  private searchCache;
17
16
  private problemCache;
@@ -25,10 +24,6 @@ export declare class ProgrammersScraper {
25
24
  * @throws {ProgrammersScrapeError}
26
25
  */
27
26
  searchProblems(options?: ProgrammersSearchOptions): Promise<ProgrammersProblemSummary[]>;
28
- /**
29
- * 검색 URL 생성
30
- */
31
- private buildSearchUrl;
32
27
  /**
33
28
  * 문제 상세 페이지 HTML 가져오기 (fetch 기반, BOJScraper 패턴)
34
29
  *
@@ -1 +1 @@
1
- {"version":3,"file":"programmers-scraper.d.ts","sourceRoot":"","sources":["../../src/api/programmers-scraper.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAGjC;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAGtC,IAAI,EACP,SAAS,GACT,oBAAoB,GACpB,kBAAkB,GAClB,aAAa;IACV,aAAa,CAAC,EAAE,OAAO;gBAN9B,OAAO,EAAE,MAAM,EACR,IAAI,EACP,SAAS,GACT,oBAAoB,GACpB,kBAAkB,GAClB,aAAa,EACV,aAAa,CAAC,EAAE,OAAO,YAAA;CAKjC;AAED;;GAEG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,WAAW,CAAgD;IACnE,OAAO,CAAC,YAAY,CAA6C;IACjE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;;IAe9D;;;;;;OAMG;IACG,cAAc,CAClB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,yBAAyB,EAAE,CAAC;IA4JvC;;OAEG;IACH,OAAO,CAAC,cAAc;IAsBtB;;;;;;OAMG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA8C1D;;OAEG;YACW,iBAAiB;IAsE/B;;OAEG;IACH,OAAO,CAAC,MAAM;IAId;;;;OAIG;IACH,aAAa;;;;IAOb;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;;;;;OAMG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC;CA2BvE"}
1
+ {"version":3,"file":"programmers-scraper.d.ts","sourceRoot":"","sources":["../../src/api/programmers-scraper.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAGjC;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAGtC,IAAI,EACP,SAAS,GACT,oBAAoB,GACpB,kBAAkB,GAClB,aAAa;IACV,aAAa,CAAC,EAAE,OAAO;gBAN9B,OAAO,EAAE,MAAM,EACR,IAAI,EACP,SAAS,GACT,oBAAoB,GACpB,kBAAkB,GAClB,aAAa,EACV,aAAa,CAAC,EAAE,OAAO,YAAA;CAKjC;AAED;;GAEG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,WAAW,CAAgD;IACnE,OAAO,CAAC,YAAY,CAA6C;IACjE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;;IAc9D;;;;;;OAMG;IACG,cAAc,CAClB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,yBAAyB,EAAE,CAAC;IAgJvC;;;;;;OAMG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA8C1D;;OAEG;YACW,iBAAiB;IAsE/B;;OAEG;IACH,OAAO,CAAC,MAAM;IAId;;;;OAIG;IACH,aAAa;;;;IAOb;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;;;;;OAMG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC;CA2BvE"}
@@ -1,4 +1,9 @@
1
- import { BrowserPool } from '../utils/browser-pool.js';
1
+ /**
2
+ * 프로그래머스 웹 스크래핑 클라이언트
3
+ *
4
+ * 검색: 프로그래머스 내부 JSON API (fetch 기반)
5
+ * 상세 페이지: cheerio (fetch 기반)
6
+ */
2
7
  import { RateLimiter } from '../utils/rate-limiter.js';
3
8
  import { LRUCache } from '../utils/lru-cache.js';
4
9
  import { parseProgrammersProblemContent } from '../utils/html-parser.js';
@@ -19,13 +24,11 @@ export class ProgrammersScrapeError extends Error {
19
24
  * 프로그래머스 스크래퍼
20
25
  */
21
26
  export class ProgrammersScraper {
22
- browserPool;
23
27
  rateLimiter;
24
28
  searchCache;
25
29
  problemCache;
26
30
  baseUrl = 'https://school.programmers.co.kr';
27
31
  constructor() {
28
- this.browserPool = BrowserPool.getInstance();
29
32
  // 초당 1회 요청 (보수적)
30
33
  this.rateLimiter = new RateLimiter({
31
34
  capacity: 2,
@@ -50,121 +53,87 @@ export class ProgrammersScraper {
50
53
  // 캐시 확인
51
54
  const cached = this.searchCache.get(cacheKey);
52
55
  if (cached !== undefined) {
53
- console.log('[ProgrammersScraper] 캐시 히트: 검색 결과');
56
+ process.stderr.write('[ProgrammersScraper] 캐시 히트: 검색 결과\n');
54
57
  // limit 적용 후 반환
55
58
  return limit && limit > 0 ? cached.slice(0, limit) : cached;
56
59
  }
57
60
  // Rate limiting (캐시 미스 시에만)
58
61
  await this.rateLimiter.acquire();
59
- let browser = null;
60
62
  try {
61
- // 1. BrowserPool에서 브라우저 획득
62
- browser = await this.browserPool.acquire();
63
- const browserPage = await browser.newPage();
64
- // 2. 검색 URL 생성
65
- const searchUrl = this.buildSearchUrl({
66
- levels,
67
- order,
68
- page,
69
- query,
70
- });
71
- console.log(`[ProgrammersScraper] 검색 URL: ${searchUrl}`);
72
- // 3. 페이지 이동 및 로딩 대기
73
- try {
74
- await browserPage.goto(searchUrl, {
75
- waitUntil: 'networkidle2',
76
- timeout: 30000,
77
- });
63
+ // 1. 프로그래머스 내부 API URL 생성
64
+ const params = new URLSearchParams();
65
+ params.set('page', String(page));
66
+ params.set('perPage', '20');
67
+ params.set('order', order);
68
+ for (const level of levels) {
69
+ params.append('levels[]', String(level));
78
70
  }
79
- catch (error) {
80
- throw new ProgrammersScrapeError(`페이지 로딩 실패: ${searchUrl}`, 'NAVIGATION_ERROR', error);
71
+ if (query) {
72
+ params.set('search', query);
81
73
  }
82
- // 4. JavaScript 렌더링 대기
74
+ const apiUrl = `${this.baseUrl}/api/v2/school/challenges/?${params.toString()}`;
75
+ process.stderr.write(`[ProgrammersScraper] API URL: ${apiUrl}\n`);
76
+ // 2. API 호출
77
+ const controller = new AbortController();
78
+ const timeoutId = setTimeout(() => controller.abort(), 10000);
79
+ let apiResponse;
83
80
  try {
84
- await browserPage.waitForSelector('table tbody tr', {
85
- timeout: 10000,
81
+ const response = await fetch(apiUrl, {
82
+ signal: controller.signal,
83
+ headers: {
84
+ 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
85
+ Accept: 'application/json',
86
+ 'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
87
+ },
86
88
  });
89
+ clearTimeout(timeoutId);
90
+ if (!response.ok) {
91
+ throw new ProgrammersScrapeError(`API 요청 실패: HTTP ${response.status}`, 'NAVIGATION_ERROR');
92
+ }
93
+ apiResponse = await response.json();
87
94
  }
88
95
  catch (error) {
89
- // 빈 결과일 수 있으므로 스크린샷 저장 후 빈 배열 반환
90
- await browserPage.screenshot({
91
- path: 'programmers-search-empty.png',
92
- });
93
- const rowCount = await browserPage.$$eval('table tbody tr', (rows) => rows.length);
94
- if (rowCount === 0) {
95
- console.log('[ProgrammersScraper] 검색 결과 없음');
96
- await browserPage.close();
97
- return [];
96
+ clearTimeout(timeoutId);
97
+ if (error instanceof ProgrammersScrapeError) {
98
+ throw error;
99
+ }
100
+ if (error.name === 'AbortError') {
101
+ throw new ProgrammersScrapeError('요청이 타임아웃되었습니다 (10000ms 초과)', 'TIMEOUT', error);
98
102
  }
99
- throw new ProgrammersScrapeError('table tbody tr selector를 찾을 수 없습니다', 'SELECTOR_NOT_FOUND', error);
103
+ throw new ProgrammersScrapeError(`API 요청 실패: ${error.message}`, 'NAVIGATION_ERROR', error);
100
104
  }
101
- // 5. 문제 목록 추출
105
+ // 3. API 응답 → ProgrammersProblemSummary 매핑
102
106
  let problems;
103
107
  try {
104
- problems = await browserPage.$$eval('table tbody tr', (rows) => {
105
- return rows.map((row) => {
106
- const titleLink = row.querySelector('td.title a[href*="/lessons/"]');
107
- const categoryEl = row.querySelector('td.title small.part-title');
108
- const levelSpan = row.querySelector('td.level span[class*="level-"]');
109
- const finishedEl = row.querySelector('td.finished-count');
110
- const rateEl = row.querySelector('td.acceptance-rate');
111
- const href = titleLink?.getAttribute('href') || '';
112
- const problemId = href.match(/lessons\/(\d+)/)?.[1] || '';
113
- const title = titleLink?.textContent?.trim() || '';
114
- const category = categoryEl?.textContent?.trim() || '기타';
115
- const levelClass = levelSpan?.className || '';
116
- const level = parseInt(levelClass.match(/level-(\d+)/)?.[1] || '0');
117
- const finishedText = finishedEl?.textContent?.trim() || '0명';
118
- const finishedCount = parseInt(finishedText.replace(/,/g, '').replace('명', '') || '0');
119
- const rateText = rateEl?.textContent?.trim() || '0%';
120
- const acceptanceRate = parseInt(rateText.replace('%', '') || '0');
121
- return {
122
- problemId,
123
- title,
124
- level,
125
- category,
126
- url: `https://school.programmers.co.kr${href}`,
127
- finishedCount,
128
- acceptanceRate,
129
- };
130
- });
131
- });
108
+ problems = (apiResponse.result ?? []).map((item) => ({
109
+ problemId: String(item.id),
110
+ title: item.title,
111
+ level: item.level,
112
+ category: item.partTitle,
113
+ finishedCount: item.finishedCount,
114
+ acceptanceRate: item.acceptanceRate,
115
+ url: `${this.baseUrl}/learn/courses/30/lessons/${item.id}`,
116
+ }));
132
117
  }
133
118
  catch (error) {
134
119
  throw new ProgrammersScrapeError('문제 목록 파싱 실패', 'PARSE_ERROR', error);
135
120
  }
136
- await browserPage.close();
137
- // 6. 캐시에 저장 (limit 적용 전 전체 결과 저장)
121
+ // 4. 캐시에 저장 (limit 적용 전 전체 결과 저장)
138
122
  this.searchCache.set(cacheKey, problems);
139
- // 7. limit 적용
123
+ // 5. limit 적용
140
124
  if (limit && limit > 0) {
141
125
  problems = problems.slice(0, limit);
142
126
  }
143
- console.log(`[ProgrammersScraper] ${problems.length}개 문제 검색 완료`);
127
+ process.stderr.write(`[ProgrammersScraper] ${problems.length}개 문제 검색 완료\n`);
144
128
  return problems;
145
129
  }
146
- finally {
147
- // 7. 브라우저 반환 (필수!)
148
- if (browser) {
149
- await this.browserPool.release(browser);
130
+ catch (error) {
131
+ if (error instanceof ProgrammersScrapeError) {
132
+ throw error;
150
133
  }
134
+ throw new ProgrammersScrapeError(`검색 실패: ${error.message}`, 'NAVIGATION_ERROR', error);
151
135
  }
152
136
  }
153
- /**
154
- * 검색 URL 생성
155
- */
156
- buildSearchUrl(options) {
157
- const params = new URLSearchParams();
158
- params.set('order', options.order);
159
- params.set('page', options.page.toString());
160
- if (options.levels.length > 0) {
161
- params.set('levels', options.levels.join(','));
162
- }
163
- if (options.query) {
164
- params.set('query', options.query);
165
- }
166
- return `${this.baseUrl}/learn/challenges?${params.toString()}`;
167
- }
168
137
  /**
169
138
  * 문제 상세 페이지 HTML 가져오기 (fetch 기반, BOJScraper 패턴)
170
139
  *
@@ -283,7 +252,7 @@ export class ProgrammersScraper {
283
252
  // 캐시 확인
284
253
  const cached = this.problemCache.get(problemId);
285
254
  if (cached !== undefined) {
286
- console.log(`[ProgrammersScraper] 캐시 히트: 문제 ${problemId}`);
255
+ process.stderr.write(`[ProgrammersScraper] 캐시 히트: 문제 ${problemId}\n`);
287
256
  return cached;
288
257
  }
289
258
  // 1. HTML 가져오기 (캐시 미스 시에만)