/last30days 플러그인 셋업부터 Reddit 댓글 무료 최적화까지 — Claude Code 리서치 자동화
Claude Code의 /last30days 스킬 셋업부터 코드 수정까지. 키체인 팝업 해결, NoneType 버그 수정, Reddit 댓글 무료 최적화 방법을 공유합니다.
1. /last30days란?
Claude Code에서 /last30days [주제]를 입력하면, Reddit, X/Twitter, YouTube, Hacker News, Polymarket 등 10개 이상의 소스에서 최근 30일간의 논의를 수집하고 종합 리포트를 만들어주는 플러그인입니다.
예를 들어 /last30days claude code source code leak이라고 입력하면, 해당 주제에 대한 Reddit 스레드 25개, X 포스트 23개, YouTube 영상 19개, HN 스토리 24개를 수집하고, 각 소스의 engagement 데이터(upvote, 좋아요, 조회수)를 기반으로 스코어링한 뒤, 핵심 인사이트를 요약해줍니다.
문제는, 기본 설정만으로는 전체 기능의 40%만 사용할 수 있고, 나머지를 열려면 유료 API 키가 필요하다고 안내된다는 점입니다. 특히 Reddit 댓글은 ScrapeCreators라는 유료 서비스를 써야만 가져올 수 있다고 되어 있었는데... 실제로는 그렇지 않았습니다.
이 글에서는 셋업 과정에서 만난 세 가지 문제를 해결하고, 유료 API 없이 Reddit 댓글을 더 풍부하게 가져오도록 코드를 수정한 과정을 공유합니다.
2. 설치 및 초기 셋업
설치
Claude Code에서 두 줄이면 됩니다:
/plugin marketplace add mvanhorn/last30days-skill
/plugin install last30days@last30days-skill
설치 후 세션을 다시 시작하면 "Ready to use" 메시지가 뜹니다.
소스별 설정 상태
설치 직후의 소스 가용성입니다:
| 소스 | 설정 필요 여부 | 비용 |
|---|---|---|
| Reddit (스레드) | 없음 | 무료 |
| Hacker News | 없음 | 무료 |
| Polymarket | 없음 | 무료 |
| X/Twitter | 브라우저 쿠키 또는 수동 설정 | 무료 |
| YouTube | yt-dlp 설치 | 무료 |
| Reddit (댓글) | ScrapeCreators API 키 | 유료 |
| TikTok / Instagram | ScrapeCreators API 키 | 유료 |
무설정 3개 소스만으로도 기본적인 리서치는 가능합니다. 하지만 X와 YouTube를 추가하면 리서치 품질이 확 올라가므로 셋업 위저드를 실행하는 걸 추천합니다.
Setup Wizard 실행
/last30days setup
위저드가 자동으로 하는 일:
- Chrome/Firefox/Safari에서 X/Twitter 로그인 쿠키를 스캔
yt-dlp를 Homebrew로 설치 (YouTube 검색용)~/.config/last30days/.env에 설정 저장
3. 문제 1: macOS 키체인 팝업이 계속 뜨는 문제
증상
셋업 완료 후, /last30days를 실행할 때마다 macOS 키체인 접근 팝업이 반복적으로 나타났습니다:
키체인에서 security이(가) 'Chrome Safe Storage'에
저장된 비밀 정보를 사용하려고 합니다.
거부를 눌러도 다음 실행 시 또 뜹니다.
원인 분석
Chrome은 쿠키를 AES 암호화해서 저장하며, 복호화 키를 macOS 키체인의 Chrome Safe Storage에 보관합니다. 스크립트가 X/Twitter 쿠키를 읽으려고 security 커맨드로 키체인에 접근할 때 이 팝업이 발생합니다.
문제의 근본 원인은 .env 파일의 FROM_BROWSER 설정이었습니다:
# env.py (line 260-267)
from_browser = config.get("FROM_BROWSER")
if from_browser is None:
if setup_complete:
from_browser = "auto" # ← 여기!
else:
from_browser = "off"
SETUP_COMPLETE=true가 설정되어 있으면, FROM_BROWSER가 명시적으로 없어도 자동으로 'auto'로 폴백합니다. 즉, .env에서 FROM_BROWSER=auto를 삭제해도 소용이 없었습니다.
해결
이미 X 쿠키를 수동으로 AUTH_TOKEN/CT0에 입력했으므로, 브라우저 스캔을 명시적으로 끄면 됩니다:
# ~/.config/last30days/.env
SETUP_COMPLETE=true
FROM_BROWSER=off # ← 명시적으로 off
BIRD_DISABLE_BROWSER_COOKIES=1 # ← Node.js Bird 모듈도 차단
AUTH_TOKEN=your_auth_token_here
CT0=your_ct0_here
핵심은 FROM_BROWSER=off입니다. 이 값이 env.py의 Line 271에서 즉시 빈 dict를 반환하게 만들어, 쿠키 추출 로직 자체를 건너뜁니다:
# env.py (line 271-272)
if from_browser == "off":
return {} # ← 쿠키 추출 없이 즉시 종료
X/Twitter 쿠키 수동 설정 방법
브라우저 자동 스캔 대신 수동으로 쿠키를 설정하는 방법입니다:
- Chrome에서
x.com에 로그인 - F12 → Application 탭 → Cookies →
https://x.com auth_token값 복사 (40자 hex 문자열)ct0값 복사 (더 긴 hex 문자열).env에 추가:
AUTH_TOKEN=your_auth_token_here
CT0=your_ct0_token_here
수동 설정의 장점:
- 키체인 팝업 없음
- 실행 속도 향상 (쿠키 스캔 건너뜀)
- 특정 계정의 쿠키를 명시적으로 지정 가능
4. 문제 2: INCLUDE_SOURCES NoneType 에러
증상
첫 리서치 실행 시 Python 에러가 발생했습니다:
AttributeError: 'NoneType' object has no attribute 'split'
에러 위치:
# last30days.py (line 1740)
_include_sources = {
s.strip().lower()
for s in config.get('INCLUDE_SOURCES', '').split(',')
if s.strip()
}
원인
config.get('INCLUDE_SOURCES', '')가 None을 반환했습니다. Python의 dict.get(key, default)는 키가 존재하지 않을 때만 기본값을 반환합니다. 키가 존재하되 값이 None이면, 기본값 '' 대신 None을 그대로 반환합니다.
# dict.get()의 동작
d = {"key": None}
d.get("key", "") # → None (키가 존재하므로 기본값 무시)
d.get("missing", "") # → "" (키가 없으므로 기본값 반환)
이 스크립트의 config 로더가 .env 파일을 파싱할 때, 존재하지 않는 키를 None으로 채우는 동작을 하고 있어서 발생한 문제입니다.
해결
or '' 패턴으로 None과 빈 문자열 모두를 안전하게 처리합니다:
# Before (line 1740)
_include_sources = {
s.strip().lower()
for s in config.get('INCLUDE_SOURCES', '').split(',') # ← None일 때 에러
if s.strip()
}
# After
_include_sources = {
s.strip().lower()
for s in (config.get('INCLUDE_SOURCES') or '').split(',') # ← None-safe
if s.strip()
}
config.get(key, default) vs config.get(key) or default:
| 표현 | key 없음 |
key=None |
key="" |
key="value" |
|---|---|---|---|---|
.get(key, "") |
"" |
None |
"" |
"value" |
.get(key) or "" |
"" |
"" |
"" |
"value" |
or 패턴이 더 방어적입니다. 다만 key=""일 때도 기본값을 반환하므로, 빈 문자열이 유효한 값인 경우에는 주의가 필요합니다. 이 경우 INCLUDE_SOURCES는 빈 문자열일 때 "소스 없음"과 동일하므로 or 패턴이 적합합니다.
5. 문제 3: Reddit 댓글, 유료 API 없이도 이미 가져오고 있었다
이 부분이 가장 흥미로운 발견이었습니다.
ScrapeCreators 없이는 댓글을 못 가져온다?
스크립트 실행 후 하단에 이런 메시지가 나타납니다:
🔍 Research Coverage: 80%
Missing: Reddit with comments.
Paid options:
- Reddit with comments: add SCRAPECREATORS_API_KEY
이걸 보면 "아, 유료 API가 있어야 댓글을 볼 수 있구나"라고 생각하게 됩니다. 하지만 실제 출력을 보면 댓글이 이미 있었습니다:
💬 Top comment (988 upvotes): "Wow did their AI not catch that lol
Or maybe an Anthropic employee started vibe coding too hard"
소스 코드 분석
reddit_enrich.py의 무료 enrichment 경로를 추적해봤습니다:
# reddit_enrich.py - enrich_reddit_item() (free path)
def enrich_reddit_item(item, ...):
# 1. Reddit 공개 .json 엔드포인트로 스레드 데이터 fetch
thread_data = fetch_thread_data(url)
# → GET https://reddit.com/r/{sub}/comments/{id}.json?raw_json=1
# 2. 댓글 파싱
comments = parse_thread_data(thread_data)
# → kind == "t1" (댓글) 필터링, score/author/body 추출
# 3. Top 10 댓글 선별
top_comments = get_top_comments(comments, limit=10) # ← 10개나 가져옴!
# 4. 인사이트 추출
insights = extract_comment_insights(top_comments, limit=7)
10개의 top comment를 가져오고 7개의 인사이트를 추출하고 있었습니다! Reddit의 공개 .json 엔드포인트는 인증 없이 전체 댓글 트리를 반환합니다:
# 아무 Reddit 스레드에 .json을 붙이면 댓글 전체가 JSON으로 반환됩니다
curl "https://www.reddit.com/r/ClaudeAI/comments/abc123.json?raw_json=1"
그런데 왜 1개만 보였을까?
렌더링 코드가 원인이었습니다:
# render.py (line 184-189)
if item.top_comments and item.top_comments[0].score >= 10:
tc = item.top_comments[0] # ← [0]만 접근!
excerpt = tc.excerpt[:200]
lines.append(f'💬 Top comment ({tc.score} upvotes): "{excerpt}"')
10개를 가져와놓고 [0] 하나만 표시하고 있었습니다. 나머지 9개는 버려지고 있었던 거죠.
수정: Top 3 댓글 표시
# render.py - Before
if item.top_comments and item.top_comments[0].score >= 10:
tc = item.top_comments[0]
excerpt = tc.excerpt[:200]
lines.append(f'💬 Top comment ({tc.score} upvotes): "{excerpt}"')
# render.py - After
if item.top_comments:
for tc in item.top_comments[:3]: # ← top 3까지 순회
if tc.score < 10:
break # ← score 10 미만이면 중단
excerpt = tc.excerpt[:200]
lines.append(f'💬 Top comment ({tc.score} upvotes): "{excerpt}"')
동일한 수정을 render.py 내 두 곳의 렌더링 경로 모두에 적용했습니다.
추가 개선: excerpt 길이 확장
댓글 본문이 200자에서 잘리고 있어서 400자로 늘렸습니다:
# reddit_enrich.py - Before
"excerpt": c.get("body", "")[:200],
# reddit_enrich.py - After
"excerpt": c.get("body", "")[:400], # ← 200 → 400
수정 결과
Before (수정 전):
**R3** r/LocalLLaMA [3632pts, 700cmt]
Claude code source code has been leaked...
💬 Top comment (988 upvotes): "Wow did their AI not catch that lol..."
After (수정 후):
**R3** r/LocalLLaMA [3646pts, 707cmt]
Claude code source code has been leaked...
💬 Top comment (988 upvotes): "Wow did their AI not catch that lol
Or maybe an Anthropic employee started vibe coding too hard"
💬 Top comment (944 upvotes): ">**3. Undercover Mode** - Automatically
activated for Anthropic employees on public repos..."
💬 Top comment (696 upvotes): "All hail Claude code because it is now
'Open Source'?"
같은 API 호출, 같은 비용(무료), 3배 더 풍부한 결과입니다.
6. 전체 수정 사항 정리
| 파일 | 변경 내용 | 효과 |
|---|---|---|
.env |
FROM_BROWSER=off 추가 |
키체인 팝업 완전 차단 |
.env |
BIRD_DISABLE_BROWSER_COOKIES=1 추가 |
Node.js 모듈의 브라우저 스캔도 차단 |
last30days.py:1740 |
config.get('INCLUDE_SOURCES', '') → (config.get('INCLUDE_SOURCES') or '') |
NoneType 에러 수정 |
render.py:184-189 |
top comment [0]만 → [:3] 순회 |
요약 렌더링 top 3 댓글 표시 |
render.py:769-776 |
top comment [0]만 → [:3] 순회 |
상세 렌더링 top 3 댓글 표시 |
reddit_enrich.py:254 |
excerpt [:200] → [:400] |
댓글 본문 2배 확장 |
7. 최종 .env 파일 구성
실제 사용 중인 설정 파일입니다 (API 키는 마스킹):
# ~/.config/last30days/.env
# 기본 설정
SETUP_COMPLETE=true
FROM_BROWSER=off
BIRD_DISABLE_BROWSER_COOKIES=1
# X/Twitter (수동 쿠키)
AUTH_TOKEN=your_auth_token_here
CT0=your_ct0_token_here
# 선택: 추가 소스 (모두 무료)
# EXA_API_KEY=... # 시맨틱 웹 검색, 1000회/월 무료
# BSKY_HANDLE=you.bsky.social
# BSKY_APP_PASSWORD=... # Bluesky 앱 비밀번호
# BRAVE_API_KEY=... # Brave 검색, 2000회/월 무료
8. 소스 가용성 정리: 무료로 어디까지 가능한가
수정 후 실제 테스트 결과 기준입니다:
| 소스 | 무료 사용 가능 여부 | 댓글/트랜스크립트 | 필요 설정 |
|---|---|---|---|
| Reddit 스레드 | ✅ | 공개 JSON API | 없음 |
| Reddit 댓글 | ✅ (수정 후) | .json 엔드포인트, top 3 |
코드 수정 |
| X/Twitter | ✅ | 포스트 전문 | 수동 쿠키 설정 |
| YouTube | ✅ | 트랜스크립트 전문 | brew install yt-dlp |
| Hacker News | ✅ | 댓글 인사이트 | 없음 |
| Polymarket | ✅ | 마켓 오즈 | 없음 |
| TikTok | ❌ | - | ScrapeCreators (유료) |
| ❌ | - | ScrapeCreators (유료) |
결론: TikTok/Instagram을 제외하면, 유료 API 없이도 "Research Coverage: 100%"에 가까운 결과를 얻을 수 있습니다.
9. 활용 예시
기술 트렌드 리서치
/last30days claude code source code leak
25개 Reddit 스레드, 23개 X 포스트, 19개 YouTube 영상에서 핵심 인사이트를 2분 만에 수집합니다.
비교 분석
/last30days Claude Code vs Codex
두 주제를 병렬로 리서치하고, side-by-side 비교표를 자동 생성합니다.
빠른 체크
/last30days react server components --quick
--quick 플래그로 소스당 8-12개만 수집하여 속도를 높입니다.
심층 리서치
/last30days AI coding agents --deep
--deep 플래그로 Reddit 50-70개, X 40-60개까지 수집합니다.
10. FAQ
Q: ScrapeCreators 없이도 Reddit 댓글을 가져올 수 있나요?
A: 네. Reddit의 공개 .json 엔드포인트가 인증 없이 전체 댓글 트리를 반환합니다. 스크립트가 이미 이 엔드포인트를 사용하고 있었고, 렌더링 코드만 수정하면 top 3 댓글을 볼 수 있습니다.
Q: 키체인 팝업이 계속 뜨는데 어떻게 해결하나요?
A: .env 파일에 FROM_BROWSER=off를 명시적으로 설정하세요. FROM_BROWSER 값이 없으면 SETUP_COMPLETE=true일 때 자동으로 auto로 폴백하여 매 실행마다 브라우저 쿠키를 스캔하려 시도합니다.
Q: X/Twitter 쿠키는 언제 만료되나요?
A: X 세션 쿠키는 보통 수 개월간 유효하지만, 비밀번호 변경이나 세션 만료 시 갱신이 필요합니다. 갱신이 필요하면 Chrome DevTools에서 다시 복사하세요.
Q: 플러그인 업데이트 시 수정한 코드가 덮어쓰기되나요?
A: 네, 플러그인 업데이트 시 ~/.claude/plugins/marketplaces/last30days-skill/ 디렉토리가 갱신됩니다. 수정 사항은 git patch로 백업해두고, 업데이트 후 재적용하는 것을 추천합니다. .env 파일은 ~/.config/last30days/에 있으므로 영향받지 않습니다.
Q: Rate limit에 걸리지 않나요?
A: 스크립트가 5개 워커로 병렬 enrichment를 하며, 429 응답 시 자동으로 exponential backoff합니다. 25개 스레드 정도는 rate limit 없이 처리됩니다. .json 엔드포인트는 Reddit API rate limit(분당 60회)보다 훨씬 여유롭습니다.
11. 참고 자료
- 검색 키워드:
mvanhorn/last30days-skill GitHub - 검색 키워드:
Reddit JSON API .json endpoint