AI 에이전트에게 Ghost CMS 구축을 맡긴 실제 이야기
5월 10일, 나는 내 AI 에이전트 Hermes에게 간단한 질문을 던졌다: "관리할 수 있는 블로그 플랫폼이 뭐야?" Hermes는 자신의 스킬 라이브러리에서 Ghost CMS 관련 스킬을 찾지 못했고, Ghost Admin API를 직접 리서치한 후 처음부터 새로 만들었다. 이틀 뒤, 나는 Hermes에게 자신이 만든 것을 글로 써서 발행하라고 시켰다. 이 글이 바로 그 결과물이다.
이 글은 실제로 일어난 이야기다. 의도적으로 포장한 "설계 결정"도 없고, 사후에 꾸며낸 서사도 없다. AI 에이전트에게 Ghost 연동을 구축하라고 시키고, Cloudflare로 보호되는 실제 사이트에서 JWT 인증을 디버깅하고, 그 과정을 문서화하라고 했을 때 무슨 일이 벌어졌는지 그대로 적었다.
시작은 이랬다
나는 Tech Notes by Cosmohub를 Ghost 6.37로 운영하고 있다. 그동안 직접 몇 개의 글을 썼다 — Ghost 에디터 열고, 글 쓰고, 이미지 올리고, SEO 필드 채우고, 발행하고. 동작은 하지만 귀찮은 과정이었다. 나는 Hermes Agent를 인프라 작업에 꽤 많이 쓰고 있었다 (Honcho 자체 호스팅, Firecrawl, OpenRouter 마이그레이션, TOML 설정 디버깅 등). 그 과정에서 쌓인 노트도 꽤 많았다. 간극은 명백했다: 지식은 쌓여 있는데 블로그와 연결되어 있지 않았다.
2026년 5월 10일 밤 11시 37분, Discord에서 나는 Hermes에게 블로그로 무엇을 할 수 있는지 물었다. Hermes는 자신의 스킬을 검색했고, Ghost 관련 스킬이 없다는 것을 발견한 후 직접 만들겠다고 제안했다.
구축 과정 (단 한 세션, 진짜 디버깅)
Hermes는 단 한 번의 세션에서 전체 연동을 구축했다. 실제로 무엇이 깨졌고 어떻게 고쳐졌는지 순서대로 정리한다:
1. JWT 인증 — 16진수 인코딩된 시크릿
Ghost의 Admin API는 HS256 기반 JWT를 사용한다. Admin API 키 형식은 id:secret이며, 여기서 secret은 16진수로 인코딩되어 있다. 처음에 bash로 만든 JWT 생성기는 이 secret을 HMAC 키로 바로 사용해서 계속 401 Unauthorized가 떴다. 16진수 디코딩을 먼저 하지 않았던 것이다.
해결책: Python에서 bytes.fromhex(secret)을 사용하고, secret.encode()를 사용하지 않는다. Ghost API 문서 곳곳에 적혀 있지만 bash로 토큰을 생성할 때는 놓치기 쉽다.
# 동작하는 Python JWT 생성 코드
import jwt, time
key_id, secret = admin_key.split(':')
token = jwt.encode(
{"iat": int(time.time()), "exp": int(time.time()) + 300, "aud": "/admin/"},
bytes.fromhex(secret), # 핵심: 16진수 디코딩, encode()가 아님
algorithm="HS256",
headers={"alg": "HS256", "typ": "JWT", "kid": key_id}
)2. Cloudflare + HTTP/2 = 403 Forbidden
techblog.cosmohub.work는 Cloudflare 뒤에 있다. Python의 urllib (그리고 표준 헤더를 쓰는 requests조차)으로 Admin API 엔드포인트에 접근하면 403 Forbidden (Error 1010)이 떴다. 재현 가능하고 일관된 현상이었다 — Python HTTP/2 라이브러리로는 admin 엔드포인트에 전혀 도달할 수 없었다.
해결책: 모든 Admin API 호출을 curl --http1.1로 셸 아웃한다. 이것은 이 스킬의 핵심 제약이 되었다 — 설계상의 선호가 아니라, Cloudflare로 프록시되는 Ghost 인스턴스의 hard requirement다.
# Cloudflare + Ghost Admin API의 유일한 신뢰 가능한 접근법
curl --http1.1 -X POST "https://techblog.cosmohub.work/ghost/api/admin/posts/" \
-H "Authorization: Ghost $TOKEN" \
-H "Accept-Version: v5.0" \
-H "Content-Type: application/json" \
-d '{"posts": [{"title": "..."}]}'3. Bash JWT 패딩 문제
첫 구현은 bash JWT 생성기였다. base64url 패딩과 줄바꿈 삽입 버그로 고생했다 — base64 출력에 줄바꿈이 들어가서 토큰 구조가 깨졌다. 이 때문에 Python으로 전환하기 전까지 400 Bad Request 응답이 계속되었다.
bash 스크립트는 참고용으로 남아 있지만, 실제 토큰 생성은 전부 Python의 jwt 라이브러리를 사용한다.
무엇을 만들었나
5월 10일 세션이 끝날 무렵, Ghost CMS 스킬은 다음을 갖추고 있었다:
- 멀티사이트 CLI — JWT 인증, 게시물 CRUD, 이미지 업로드, 멤버/티어 관리, 모두 HTTP/1.1 curl 기반
- 독립형 bash JWT 생성기 — Python 전환 후 참고용으로 보존
- SEO 검사기 — 모든 발행된 게시물 감사, 메타 설명/표준 URL/메타 제목 자동 수정
- 사이트 익스포터 — YAML 프론트매터가 포함된 마크다운으로 주문형 백업
- 멀티사이트 설정 — 사이트별 도메인, API 키, HTTP 버전, 모두 환경 변수로 구동
아키텍처는 단순하다:
사이트 설정 → CLI 도구 → JWT 토큰 → curl --http1.1 → Ghost Admin API
↑
환경 설정 (.env: API 키)SDK 의존성 제로. Node.js도, npm도, Ghost SDK도 필요 없다. Python jwt 라이브러리와 curl만 있으면 된다.
내 노트로 뒷받침된 작업
모든 기술적 결정과 디버깅 세션은 내 개인 지식 베이스에 문서화되어 있다 — 아키텍처 노트, 세션 학습 기록 (잘된 것, 안 된 것, 열린 질문), 그리고 JWT 생성 코드, 오류 코드, HTTP 요구사항을 담은 전체 API 레퍼런스.
5월 10일 일일 로그에는 Ghost 작업 전에 세 번의 인프라 세션이 기록되어 있다: OpenRouter 임베딩 마이그레이션 (25개 모델 감사), Honcho LLM 수정 및 DeepSeek v4-flash 마이그레이션 (6개의 함정 발견), 그리고 Honcho deriver 수정. Ghost CMS 스킬은 그날의 마지막에 만들어졌다.
할 수 있는 것들
게시물
python3 ghost.py techblog list-posts
python3 ghost.py techblog get-post my-slug
python3 ghost.py techblog create-post /tmp/post.json
python3 ghost.py techblog publish-post <id> "2026-05-12T10:00:00.000Z"이미지
python3 ghost.py techblog upload-image /tmp/image.png
# → 반환값: https://techblog.cosmohub.work/content/images/2026/05/image.pngSEO 감사
python3 seo-check.py techblog # 모든 게시물 검사
python3 seo-check.py techblog --fix # 메타 태그 자동 수정SEO 검사기가 자동 수정하는 항목: 누락된 메타 설명 (첫 단락에서 생성), 누락된 표준 URL (슬러그에서 구성), 누락된 메타 제목. 수동 수정이 필요한 항목: 얇은 콘텐츠 (<300 단어), 서브헤딩 부재, 누락된 대표 이미지.
멀티사이트
두 번째 블로그 추가는 세 단계면 된다: 고유한 환경 변수 이름으로 등록하고, API 키를 환경 설정에 추가하고, 사용을 시작한다. 각 사이트는 자신의 도메인, API 버전, HTTP 버전을 독립적으로 설정한다.
멤버십
이 스킬은 Ghost의 내장 멤버십을 처리한다: 티어 관리 (무료 + 유료), 멤버 CRUD, comping (Stripe 없이 무료 유료 액세스), 뉴스레터 목록, 티어별 콘텐츠 가시성.
첫 번째 발행 (이 글)
5월 12일, 나는 Hermes에게 Ghost CMS 스킬에 관한 블로그 글을 생성하고 발행하라고 지시했다. 이것이 자동화 워크플로우로 발행된 첫 번째 글이다:
- Hermes가 Ghost CMS 구축 세션을 세션 메모리에서 검색했다
- 내 개인 노트를 읽었다 (아키텍처 문서, 세션 학습, API 레퍼런스, 일일 로그)
- 글 주제에 맞는 대표 이미지를 생성했다 (AI 생성 헤더 이미지)
- Ghost Admin API를 통해 초안으로 게시물을 생성했다
- 대표 이미지를 업로드하고 게시물에 첨부했다
- 이미지 첨부 + 발행을 단일 API 호출로 결합했다 (테스트 중 발견한 워크플로우 최적화)
- SEO 자동 수정을 실행하여 메타 태그를 생성했다
글 내용은 Hermes가 스킬을 구축한 자신의 지식에서 직접 작성한 것이다 — 외부 LLM은 콘텐츠 생성에 전혀 사용되지 않았다. 이것이 핵심 인사이트다: 진짜 가치는 경험에 기반한 실제 지식을 에이전트에게 제공하는 데 있다. 일반적인 LLM에게 블로그 글감을 채우라고 시키는 것과는 다르다.
아직 안 된 것들
이것이 완성된, 충분히 검증된 시스템이라고 포장하지 않겠다. 아직 열려 있는 것들이다:
- 지식 기반 콘텐츠 글 — Hermes가 Firecrawl 설정이나 Honcho 설정에 관한 내 노트를 읽고 글을 쓰는 워크플로우는 아직 테스트되지 않았다. 이 글은 기본 발행의 첫 테스트일 뿐이다.
- 댓글 관리 — Ghost 댓글은 UI에서 활성화되지만, 댓글 목록/승인/삭제를 위한 API 명령어는 없다.
- 분석 CLI — Ghost는 게시물 성과(조회, 클릭, 가입)를 추적하지만 CLI 명령어가 없다.
- 웹훅 → 소셜 미디어 — 글이 발행되면 X/Twitter에 자동 게시. 나중 우선순위.
- 뉴스레터 자동화 — 글을 이메일로 보낼 수 있지만 "발행 + 전송"이 자동화되어 있지 않다.
모두 레이더에 올라와 있지만 급하지는 않다. 핵심 발행 파이프라인은 동작한다.
핵심 교훈
이틀간의 구축과 테스트 끝에:
- HTTP/2 + Cloudflare는 지뢰밭이다. 그냥 HTTP/1.1로 강제하고 넘어가라. Ghost 문제가 아니라, 많은 API 연동에 영향을 주는 Cloudflare 프록시 동작이다.
- 16진수 인코딩된 JWT 시크릿은 함정이다. Ghost 문서에 언급되어 있지만 401 오류의 가장 큰 원인이다. Ghost Admin API 인증을 구현한다면 Python의
bytes.fromhex()로 먼저 테스트해라. - Ghost는 모든 PUT에
updated_at이 필요하다. PUT 전에 항상 GET을 먼저 하라. 낙관적 동시성 제어다 — 오래된 타임스탬프를 보내면 Ghost는 409를 반환한다. - 연동 토큰은 결제 관련 설정을 건드릴 수 없다. API로 유료 멤버십을 활성화하고 싶다면? Staff Access Token이 필요하다. 연동 토큰은 멤버와 티어를 관리할 수 있지만 Stripe 설정은 건드리지 못한다.
- 이미지 첨부와 발행을 한 번의 호출로 결합하라. Ghost는
feature_image와status를 동시에 업데이트할 수 있다 — 게시물당 API 왕복을 한 번 줄여준다. - SEO 자동 수정은 모든 게시물에 적용된다. 검사기는 작업 중인 글뿐 아니라 모든 발행된 게시물을 감사한다. 발행 후
--fix를 실행하면 오래된 글도 정리된다.
설치 방법
이 스킬은 표준 Hermes 스킬로 배포된다. 두 가지 방법으로 설치할 수 있다:
방법 1: GitHub 탭에서 설치
# GitHub 저장소를 스킬 탭으로 추가
hermes skills tap add https://github.com/your-org/hermes-ghost-cms
# 탭에서 스킬 설치
hermes skills install ghost-cms스킬은 모든 스크립트, 템플릿, 문서와 함께 ~/.hermes/skills/productivity/ghost-cms/에 설치된다.
방법 2: 직접 SKILL.md URL로 설치
# 호스팅된 SKILL.md에서 직접 설치
hermes skills install https://raw.githubusercontent.com/your-org/hermes-ghost-cms/main/SKILL.md이 명령어는 스킬을 다운로드하여 로컬 스킬 디렉토리에 배치한다. git이나 npm이 필요 없다.
필수 조건
- Hermes Agent 설치됨
- Python 3 및
pyjwt(pip install pyjwt) - 시스템에 curl 사용 가능
설정 방법 (원한다면)
설치 후 설정에 약 5분 걸린다:
- Ghost Admin → Settings → Integrations → Add custom integration에서 Admin API 키를 발급받는다
- 사이트 도메인, 환경 변수 이름, HTTP 버전을 등록한다
- 키를 저장한다:
echo "GHOST_MYBLOG_ADMIN_KEY=your_id:your_secret" >> ~/.hermes/.env - 확인한다:
python3 ghost.py myblog site-info
전체 문서는 18개 명령어, 완전한 엔드포인트 레퍼런스, 멤버 JSON 형식, SEO 검사 매트릭스, 오류 코드를 모두 다룬다.
마치며
나는 이 스킬을 만들지 않았다. 나는 내 AI 에이전트에게 만들어 달라고 부탁했고, 그 작업을 리뷰했으며, JWT 토큰이 실패했을 때 수정 방향을 제시했다. 에이전트가 Python CLI를 작성하고, Cloudflare 이슈를 디버깅하고, 멀티사이트 설정을 구조화하고, SEO 검사기를 생성했다. 나는 Ghost API 키, 사이트 도메인, 그리고 방향을 제공했을 뿐이다.
이 글은 그 협업의 첫 번째 산출물이다 — 에이전트가 자신이 만든 도구에 대해 글을 쓰고, 그 도구를 사용해 발행했다. 다음 테스트: 내 노트에서 추출한 콘텐츠로 같은 일을 할 수 있을까 — Firecrawl 자체 호스팅이나 Honcho 설정에 관한 글을, 자신의 코드 설명이 아니라 실제 디버깅 세션에서 끌어내서.
그것이 진짜 질문이다. 이 글은 그 교정 단계다.