3가지를 구현하라: (A) index.html을 대시보드로 교체 — AI 상태 + 프로젝트 트리 + 타임라인. (B) 프로젝트별 페이지를 서빙하는 API. (C) PDF 내보내기 엔드포인트.
POST /api/comms/msg → JSONL 저장 + Markdown→HTML 자동 렌더 ✅GET /api/comms/inbox/{ai}, /projects, /claims, /health ✅GET /export/{id}.md → Markdown 다운로드 ✅comms-hub.service :8770 ✅/api/comms/* → :8770, /export/* → :8770 ✅현재 /home/ubuntu/reports/index.html은 정적 파일. 이것을 comms-hub가 서빙하는 동적 페이지로 교체한다. 기존 정적 보고서(수동 HTML)도 함께 표시.
새 Jinja2 템플릿 templates/index.html.j2를 만들고, GET / 라우트에서 렌더링.
Caddy 변경 필요: 루트 / 요청을 comms-hub가 처리하도록. 단, 기존 정적 파일(*.html, _pad.js, manifest.json)은 그대로 Caddy file_server가 서빙.
# Caddy에 추가할 라우트 (PM이 배포 시 적용)
handle /dashboard {
reverse_proxy 127.0.0.1:8770
}
handle /projects {
reverse_proxy 127.0.0.1:8770
}
handle /projects/* {
reverse_proxy 127.0.0.1:8770
}
대안 (더 단순): index.html을 comms-hub가 /home/ubuntu/reports/index.html에 렌더링해서 덮어쓰는 방식. Caddy 변경 불필요. 이 방식 채택.
<!-- 섹션 1: 헤더 -->
┌─────────────────────────────────────────────────────┐
│ ai.shinjadong.cloud │
│ 보고/의사결정 워크스페이스 │
│ [☀️ 라이트] 토글 │
└─────────────────────────────────────────────────────┘
<!-- 섹션 2: AI 상태 패널 -->
┌─────────────────────────────────────────────────────┐
│ 🟢 claude-code 3분 전 ⚪ codex 2시간 전 │
│ ⚪ claude-app 어제 ⚪ gemini 어제 │
│ │
│ 📨 미처리 2 ⏳ 진행중 1 ✅ 완료 6 📎 claims 0 │
└─────────────────────────────────────────────────────┘
<!-- 섹션 3: 프로젝트 트리 -->
┌─────────────────────────────────────────────────────┐
│ 📂 프로젝트 │
│ │
│ 📂 keeper-direct (7) 📂 comms-hub (1) │
│ 📂 atlas (0) 📂 consciousness-bridge (0) │
│ 📂 outbound-recorder (0) ... │
│ │
│ (프로젝트 클릭 → 해당 프로젝트 문서 목록) │
└─────────────────────────────────────────────────────┘
<!-- 섹션 4: 최근 활동 타임라인 -->
┌─────────────────────────────────────────────────────┐
│ 01:12 cc 📄 comms-hub Phase 1 배포 완료 @jadong │
│ 00:30 cc 📋 Codex Brief: comms-hub P1 @codex │
│ 00:07 cc 📄 AI Comms Hub 마스터 플랜 @jadong │
│ 23:01 cc 📄 Anti-abuse Design v3 @jadong │
│ ... │
└─────────────────────────────────────────────────────┘
index.html은 2가지 소스를 합산:
store.messages에서 직접 읽기manifest.json에서 읽기 (comms-hub가 생성하지 않은 레거시 HTML)레거시 보고서 판별: manifest에 있지만 store.messages에 slug가 없는 파일.
@app.post("/api/comms/rebuild-index")
def rebuild_index():
"""index.html을 렌더링하여 REPORTS_DIR에 저장."""
html = render_index(store, REPORTS_DIR)
(REPORTS_DIR / "index.html").write_text(html, encoding="utf-8")
return {"ok": True}
자동 트리거: POST /api/comms/msg와 PATCH .../status 성공 후 자동으로 rebuild_index() 호출. 별도 요청 불필요.
PC에서 생성하여 Syncthing으로 전달 (PM이 설정):
# PC에서 실행 (1회 또는 크론)
ls -1d ~/projects/*/ | xargs -I{} basename {} | \
python3 -c "import sys,json; print(json.dumps({'projects':sorted(sys.stdin.read().split())}))" \
> ~/reports/projects.json
index.html 렌더 시 이 파일이 있으면 읽어서 문서가 없는 프로젝트도 트리에 표시 (회색).
index.html에서 프로젝트 클릭 시 → 해당 프로젝트의 문서 목록 페이지로 이동.
정적 HTML 파일 생성 방식: REPORTS_DIR/projects/{name}/index.html
rebuild_index() 시 프로젝트별 페이지도 함께 생성.
┌─────────────────────────────────────────────────────┐
│ ← 홈 📂 keeper-direct │
│ 7개 문서 · 마지막 활동: 2026-05-06 01:12 │
├─────────────────────────────────────────────────────┤
│ │
│ 01:12 cc 📄 Phase 1 배포 완료 sent │
│ 00:30 cc 📋 Codex Brief P1 sent │
│ 23:01 cc 📄 Anti-abuse v3 approved │
│ ... │
│ │
│ 각 항목 클릭 → 해당 문서 HTML로 이동 │
└─────────────────────────────────────────────────────┘
templates/project.html.j2 — 프로젝트별 문서 목록 페이지.
# requirements.txt에 추가
weasyprint>=63
weasyprint는 시스템 라이브러리가 필요합니다. EC2에 이미 설치되어 있을 수 있지만, 없으면:
sudo apt-get install -y libpango-1.0-0 libharfbuzz0b libfontconfig1 libcairo2
설치 실패 시 weasyprint 없이도 동작하도록 graceful fallback 구현하라 (아래 참조).
@app.get("/export/{id}.pdf")
def export_pdf(id: str):
msg = store.get_message(id)
if msg is None:
raise HTTPException(404)
# 캐시: /var/lib/comms/pdf_cache/{id}.pdf
cache_path = COMMS_DATA_DIR / "pdf_cache" / f"{id}.pdf"
if not cache_path.exists():
html = render_message(msg) # 라이트 모드 강제
pdf_bytes = weasyprint.HTML(string=html).write_pdf()
cache_path.parent.mkdir(parents=True, exist_ok=True)
cache_path.write_bytes(pdf_bytes)
return FileResponse(cache_path, media_type="application/pdf",
filename=f"{msg.slug}.pdf")
PDF 렌더 시 HTML에 <html data-theme="light">를 주입하여 항상 라이트 모드로 생성. renderer.py에 render_message(msg, force_light=True) 옵션 추가.
try:
import weasyprint
HAS_WEASYPRINT = True
except ImportError:
HAS_WEASYPRINT = False
# PDF 요청 시 weasyprint 없으면 503
if not HAS_WEASYPRINT:
raise HTTPException(503, "PDF export unavailable (weasyprint not installed)")
PDF 전용 CSS 추가 (Jinja2 템플릿의 @media print 블록 확장):
page-break-before: alwaysbreak-inside: avoid)a[href]::after { content: " (" attr(href) ")"; })| 파일 | 액션 | 설명 |
|---|---|---|
templates/index.html.j2 | 신규 | 대시보드 메인 페이지 |
templates/project.html.j2 | 신규 | 프로젝트별 문서 목록 |
server.py | 수정 | rebuild-index 라우트 + PDF 엔드포인트 + POST/PATCH 후 auto-rebuild |
renderer.py | 수정 | render_index() + render_project_page() + force_light 옵션 + PDF CSS |
requirements.txt | 수정 | weasyprint 추가 |
# index.html 생성
curl -s -X POST http://localhost:8770/api/comms/rebuild-index
# 기대: output/index.html이 생성됨
# 내용 확인: AI 상태 패널 + 프로젝트 트리 + 타임라인
# output/projects/comms-hub/index.html 존재 확인
# 내용: comms-hub 프로젝트의 문서 목록
# 새 메시지 생성 후 index.html이 자동 갱신되는지 확인
curl -s -X POST http://localhost:8770/api/comms/msg \
-H "Content-Type: application/json" \
-d '{"from":"claude-code","to":["jadong"],"type":"report","project":"test-project","title":"테스트","content_md":"# 테스트"}'
# 기대: index.html + projects/test-project/index.html 자동 생성
# manifest.json에 있는 기존 수동 HTML 보고서가 타임라인에 표시되는지 확인
# 기대: keeper-out-anti-abuse-design-v3 등 기존 보고서도 목록에 포함
curl -s -o test.pdf http://localhost:8770/export/{id}.pdf
# 기대: 유효한 PDF 파일 (file test.pdf → "PDF document")
# weasyprint 미설치 시 503 응답 (500이 아님)
# index.html에 테마 토글 버튼이 있고 작동하는지 확인
# localStorage에 theme 저장 확인
# output/projects.json이 있으면 문서 없는 프로젝트도 트리에 표시 (회색)
# 없어도 에러 없이 동작 (메시지 기반으로만 프로젝트 표시)