Agent Skill을 사용하여 Markdown 파일을 Substack에 게시하는 방법
요약
본 글은 agent skill인 publishing-kit을 활용하여 Markdown 아티클을 Substack에 게시하는 단계별 개발 가이드를 제공합니다. 이 과정에서 Substack의 API 부재로 인해 발생하는 HTML 및 마크다운 변환 문제를 분석하고, 코드 블록 유지와 링크 손실 등의 문제점을 해결하는 방법을 제시합니다.
핵심 포인트
- Substack은 공식 게시 API가 없어 웹 에디터에 의존해야 합니다.
- 붙여넣기 전 앵커 태그에서 `<code/>`를 제거하여 링크 손실을 방지해야 합니다.
- Medium과 Substack 간의 코드 블록, Alt 텍스트 처리 방식 차이점을 비교 분석했습니다.
이 글은 agent skill인 publishing-kit을 이용하여 markdown 아티클을 Substack에 게시하는 단계별 가이드입니다. publishing-kit의 대상 플랫폼 중 Substack은 dev.to, Medium, AWS Builder Center, LinkedIn 다음으로 다섯 번째 목적지이며, 아래 모든 결과는 이 kit 자체의 walk-through를 Substack 출판물로 게시한 것입니다.
[https://github.com/xbill9/publishing-kit]
하나의 markdown 파일이 들어가면, 코드 블록, 이미지, 링크가 모두 확인된 초안 Substack 포스트가 나옵니다.
문제는 무엇인가요?
Substack에는 게시 API가 없습니다. 포스트는 웹 에디터를 통해 입력되며, 이 에디터가 무엇이 살아남을지 결정합니다.
여기에 HTML을 붙여넣은 다음 저장된 초안 Substack을 읽어보면 다음과 같습니다:
| 콘텐츠 | 저장된 초안에 표시되는 내용 |
|---|---|
| 여러 줄 코드 블록 | 🟢 유지됨, 동일한 줄 수와 문자 수 |
| ... | |
| 없어서 오류를 발생시키는 실패는 없습니다. 표가 사라지거나 링크가 일반적인 고정폭(monospace) 텍스트가 되지만, 에디터는 이를 저장합니다. |
왜 링크가 사라지는 건가요?
Substack의 에디터는 ProseMirror 기반으로 구축되었으며, 그 스키마에는 포스트가 가질 수 있는 모든 요소와 마크(mark)가 나열되어 있습니다. code 마크는 excludes: "_"로 선언되어 있어 인라인 코드는 다른 어떤 마크도 포함할 수 없다는 의미입니다. 즉, 굵게(bold), 기울임꼴(italic), 링크를 가질 수 없습니다.
따라서 [`skills/publishing/SKILL.md`](https://github.com/xbill9/publishing-kit/blob/main/skills/publishing/SKILL.md)는 <a><code>skills/publishing/SKILL.md</code></a>로 도착하며, 에디터는 코드는 유지하고 링크는 폐기합니다. 이 kit의 walk-through에는 이런 방식으로 작성된 링크가 두 개 있으며, 그대로 붙여넣어도 저장된 초안은 총 4개 중 2개의 링크 대상만 가지고 있습니다.
해결책은 붙여넣기 전에 앵커(anchor)에서 <code/>를 제거하는 것입니다. 이렇게 하면 링크는 일반 텍스트로 살아남아 독자가 페이지에 도달할 수 있는 방법을 유지하게 됩니다.
Substack과 Medium의 비교는 어떤가요?
두 플랫폼 모두 표(table)를 지원하지 않기 때문에, 이 키트는 Medium에 구축한 것과 동일한 HTML을 Substack으로 전송하며, Medium에서는 모든 표가 이미 이미지로 처리됩니다. 이후 두 플랫폼은 갈라섭니다:
- 코드: Medium의 가져오기 도구(importer)는 여러 줄의 코드를 한 줄로 평탄화합니다. 반면 Substack은 이를 실제 코드 블록으로 유지합니다.
- Alt 텍스트: Medium 에디터는 붙여넣기 시 Alt 텍스트를 공백으로 만듭니다. 하지만 Substack은 이를 유지합니다.
- 제목(Headings): Medium은 두 가지 크기의 제목을 가지고 있어, 이 키트는 섹션을
<h4>태그로 격하시킵니다. Substack은 여섯 가지가 있으며, 본문 텍스트가 19px인 것에 비해<h4>를 21.375px로 렌더링하기 때문에 섹션 제목이 눈에 잘 띄지 않습니다.
이 시점에서 갖추어야 할 것들…
- Claude Code, Codex 또는 Antigravity에서
publishing-kit버전 0.32.0 이상을 사용하며, 페이지 내에서 JavaScript를 실행할 수 있는 브라우저 자동화 기능이 필요합니다. - 에이전트가 구동되는 브라우저 내에 가입된 Substack 출판물(publication)입니다.
make-medium.py로 Medium용으로 이미 구축되었고, 이미지 커밋 및 푸시까지 완료된 아티클이 준비되어 있어야 합니다.
1단계 — HTML 구축하기
Substack 버전은 Medium에서 호스팅하는 HTML을 사용하므로, 빌드 과정도 Medium의 빌드를 따릅니다:
python3 make-medium.py devto-substack-destination.md medium --cover=cover.325bdb37.jpg
devto-substack-destination.md: 2개의 표, 0개의 다이어그램
이것을 사용하세요 -> medium/devto-substack-destination-hosted.html (붙여넣기 또는 가져오기 필요; medium/img가 커밋 및 푸시되어야 함)
이것은 안 됩니다 -> medium/devto-substack-destination-embed.html (211 KB; data: URI를 사용하며, Medium은 붙여넣기 시 모두 제거함)
...
표와 박스 그리기 다이어그램은 medium/img/ 아래에 PNG 파일로 저장되며, -hosted.html은 이들의 공개 GitHub URL을 통해 이를 가리킵니다. Substack은 각각의 이미지를 가져와 자체 이미지 서버에 다시 호스팅합니다.
2단계 — 페이지를 에디터로 옮기기
에디터 페이지의 콘텐츠 보안 정책(content security policy)이 로컬 머신으로의 fetch를 차단하기 때문에, HTML은 window.name을 통해 전달되며 이 방식은 같은 탭 내에서 탐색이 일어나도 살아남습니다. 에이전트는 127.0.0.1에 저장소를 서비스하고, 거기에 호스팅된 HTML을 열어두며, 이를 키트의 Substack 도우미와 함께 저장합니다.
const html = await (await fetch(location.href)).text();
const js = await (await fetch('/skills/publishing/scripts/browser/substack-editor.js')).text();
window.name = JSON.stringify({html, js});
Step 3 — 준비 및 붙여넣기 (Prepare and Paste)
에디터에서 도우미는 페이로드(payload)로부터 로드하고 HTML을 조정하여 붙여넣습니다:
const p = JSON.parse(window.name); (0, eval)(p.js);
ss.prepare(p.html)
{"anchors": 6, "codeLinksUnwrapped": 2, "html": 11557, "images": 2, "pres": 4}
prepare()는 제목 블록을 제거합니다. 왜냐하면 제목(Title)과 부제목(subtitle)은 별도의 필드이기 때문입니다. 인라인 코드 주변의 두 링크를 풀고(unwraps), Medium의 <h4> 섹션을 <h3>으로 승격시킵니다. 또한 모든 <pre>에서 언어 클래스(language class)를 지웁니다: 원본 그대로 붙여넣은 내용은 pasted as-is, 백틱 세 개로 감싼 블록( ```text )은 Substack의 수학 요소인 latex_block으로 저장되며, shell, js, json 블록은 코드로 저장됩니다. Substack의 코드 블록은 언어를 저장하지 않으므로 아무것도 손실되지 않습니다. 그런 다음 단일 합성 붙여넣기(synthetic paste)가 이루어집니다:
`js await ss.paste() `
`json {"chars": 9020, "pasted": true} `
paste()는 에디터에 이미 텍스트가 있는 경우 거부하므로, 두 번째 실행으로는 기사를 두 번 추가할 수 없습니다. 여기서 모든 것은 배경 탭(background tab)에서 작동하며, 타이핑된 키 입력은 신뢰하기 어렵습니다.
Step 4 — 저장된 초안 읽어보기 (Read Back the Saved Draft)
에디터의 DOM에는 화면에 보이는 것이 표시됩니다. 게시되는 것은 저장된 초안 Substack이며, 에디터 페이지는 /api/v1/drafts/<id>에서 JSON 형태로 이를 읽을 수 있습니다. ss.audit()는 자동 저장(autosave)을 기다린 후 문서를 계산합니다:
`text code_block 4, lines 2/2/1/4 image2 2, both substack-post-media.s3.amazonaws.com, alt kept heading 7 (h3 7) link targets https://github.com/xbill9/publishing-kit https://github.com/xbill9/publishing-kit/blob/main/skills/publishing/SKILL.md https://github.com/xbill9/publishing-kit/tree/main/articles/publishing-kit-skill https://claude.com/claude-code `
모든 카운트는 입력된 HTML과 일치합니다: 동일한 줄 수를 가진 4개의 코드 블록, 2개의 이미지, 4개의 링크 대상(link targets)입니다.
5단계 — 제목 및 부제목 설정
에디터 페이지에는 두 쌍의 제목 필드가 있습니다. 게시물 자체의 제목은 자리 표시자(placeholder)가 Title과 Add a subtitle…인 텍스트 영역이며, Add a title... 입력란과 Add a description... 텍스트 영역은 SEO 설정에 속합니다. ss.setTitle()는 프론트 매터(front matter)의 title과 description을 사용하여 첫 번째 쌍을 채우며, 감사(audit)를 통해 둘 다 저장된 초안에 도달했음을 확인했습니다.
부제목은 255자 제한이 있으며, 이를 초과하면 본문을 포함한 전체 초안 저장이 중단되지만 에디터에는 모든 내용이 표시됩니다.
ok Builder Center: HTTP 200
WARN Medium: HTTP 403 to a browser UA (dev.to/Medium bot wall, not a broken link)
ok Substack: HTTP 200
ok Dev.to (aws-builders): HTTP 200
ok LinkedIn: HTTP 200
ok no markdown left; Slack renders none of it
0 fail, 1 warn
Substack은 쿠키가 없는 클라이언트에게 게시된 포스트(published post)에 대해 200을 반환하고 슬러그가 누락되면 404를 반환하므로, 링크는 다른 어떤 경우와 마찬가지로 확인됩니다. substack = PENDING 줄은 포스트가 라이브 상태가 될 때까지 실행을 실패하게 합니다.
🔎 팁: 본문 내부의 개수 세기
공개 페이지는 해당 포스트의 마크업을 여러 번 포함합니다. 이 포스트를 위해 제공된 HTML에는 8개의 <pre>와 16개의 <h3>가 있습니다. `class=
⚠️ 255자 이상의 부제목은 초안 저장을 중단시키며, 본문도 포함됩니다.
❌ 테이블과 API는 불가: 테이블은 이미지로 전송되며, 게시물은 브라우저를 통해 들어갑니다.
범위: Linux의 Claude Code에서 publishing-kit 0.32.0을 사용하여, 2026년 10월 5일에 하나의 Substack 출판물에 기사를 게시하는 과정이며, 배경 브라우저 탭에서 진행됩니다. 요소 및 크기 관련 발견 사항은 해당 날짜의 편집기 스키마와 스타일에서 가져온 것입니다.
Agent Skill을 사용하여 마크다운(markdown) 기사 하나를 Substack에 게시하는 전략은 점진적인 단계별 접근 방식으로 검증되었습니다.
참고 자료
- publishing-kit | GitHub
- Substack
- ProseMirror 스키마 가이드
- Write Markdown Once, Publish It Everywhere | dev.to
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기