/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

위저드가 자동으로 하는 일:

  1. Chrome/Firefox/Safari에서 X/Twitter 로그인 쿠키를 스캔
  2. yt-dlp를 Homebrew로 설치 (YouTube 검색용)
  3. ~/.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 쿠키 수동 설정 방법

브라우저 자동 스캔 대신 수동으로 쿠키를 설정하는 방법입니다:

  1. Chrome에서 x.com에 로그인
  2. F12 → Application 탭 → Cookieshttps://x.com
  3. auth_token 값 복사 (40자 hex 문자열)
  4. ct0 값 복사 (더 긴 hex 문자열)
  5. .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 (유료)
Instagram - 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