흔한 "AI로 PPT 만들기"는 둘 중 하나로 망한다. 코드로 처음부터 그리면 정렬이 어긋나고 촌스럽고, 반대로 템플릿 파일을 통째로 LLM에 던지면 도형·서식이 깨진다. GordenPPTSkill은 제3의 길을 택했다 — 디자이너가 만든 템플릿은 신성불가침으로 두고, AI는 "몇 번 슬라이드를 쓸지"와 "각 텍스트 칸에 무슨 글자를 넣을지"만 결정한다. 나머지(레이아웃·배색·아이콘·애니메이션)는 원본 그대로 보존된다.
그래서 결과물이 PowerPoint·WPS·Keynote에서 정상적으로 열리는 편집 가능한 진짜 파일이다. 이미지로 렌더링된 가짜가 아니라.
"채워 넣기만 하면 되는 고급 양식 서류"를 떠올리면 된다. 변호사가 정교하게 짜둔 계약서 양식이 있고, 당신은 빈칸(이름·날짜·금액)만 채운다. 양식의 글꼴·여백·도장 자리는 절대 건드리지 않는다. 다 채우면 그대로 인쇄해도 전문가가 만든 문서처럼 보인다.
GordenPPTSkill에서 "양식"은 template.pptx, "빈칸의 위치 지도"는 detail.json, "이 칸에 이 글자"라는 채움 지시서는 edits.json, 실제로 채워 인쇄하는 사람은 build_pptx.py다.
이 저장소는 2026년 5월 27일에 첫 릴리스된 아주 신생 프로젝트다(스타 48개). 그런데도 TrendShift 데일리 4위에 오른 건, 별 개수가 아니라 "언급 모멘텀"으로 순위를 매기는 TrendShift 특성상 지금 개발자 커뮤니티에서 빠르게 회자되고 있다는 뜻이다. 배경에는 세 가지 흐름이 있다.
같은 날 TrendShift 데일리 토픽에서 #AI skills가 3위(10.3k 스타 유입)를 기록했다. Claude Code·Codex 같은 코딩 에이전트에 SKILL.md 한 장으로 능력을 "꽂는" 패턴이 유행하면서, "PPT 만들기 능력을 내 AI에 장착한다"는 컨셉이 정확히 그 파도를 탔다.
SKILL.md가 진입점이다. 모델을 재학습하지 않고도 새 능력을 더한다.아래 표가 이 프로젝트의 존재 이유를 압축한다.
| 접근법 | 방식 | 결과의 한계 |
|---|---|---|
| 코드로 처음부터 그리기 | python-pptx로 도형·글상자를 직접 배치 | 정렬·여백·배색을 매번 손수 잡아야 함 → 아마추어 느낌, 정보밀도 낮음 |
| 템플릿 통째로 LLM에 | pptx XML/내용을 모델에 넣고 "바꿔줘" | 도형 좌표·서식이 깨지거나 환각으로 망가짐, 토큰 폭발 |
| GordenPPTSkill | 슬라이드 선택 + run 텍스트만 치환 | 디자이너 품질 유지 · 레이아웃 100% 보존 · 진짜 편집 가능 .pptx |
저자 본인의 표현이 "史上最强原生PPT Skill,更适合中国宝宝(역대 최강 네이티브 PPT 스킬, 중국 초보자에게 더 적합)"이다. 템플릿 19종이 중문 직장·당정(党政)·학술 시나리오에 맞춰져 있다 — 분기/연말 업무보고, 述职竞聘(승진 발표), 开题答辩(논문 개제 발표), 당정 사상교육 과제 등. 정보밀도가 높고 "있어 보이는" 国企·대기업풍 슬라이드가 강점이다.
DeepSeek·샤오미 Mimo·Claude·GPT로 모두 실측됐다고 밝힌다. 스킬의 "지능"이 모델 안에 박혀 있지 않고 SKILL.md·detail.json의 명시적 규칙과 데이터에 들어 있어서, 약한 국산 모델로도 결과가 잘 나온다. 이게 스킬 설계의 핵심 미덕이다.
스킬을 처음 켜면 AI가 첫 번째로 python3 scripts/apply_update.py를 실행하도록 설계됐다. 원격 저장소의 updates.json과 로컬 VERSION을 비교해 바뀐 파일만 증분 다운로드한다. 템플릿이 추가/개선되면 사용자는 가만히 있어도 최신본을 쓴다.
템플릿 소재가 제3자 디자이너 작품이라, 저장소·내장 템플릿 모두 개인 학습·연구 용도로만 허용된다. 상업 발표·판매·고객 제안·영리 목적 사내 사용·이차 배포는 금지다. 학습 대상으로는 훌륭하지만, 실제 업무 산출물에 템플릿을 그대로 쓰면 안 된다.
보통 딥다이브는 백엔드/프론트엔드/인프라로 나누지만, 이 프로젝트는 서버도 화면도 없는 CLI 스킬 패키지다(언어 구성 Python 100%). 그래서 세 축을 이렇게 재정의하는 게 정확하다: ① 실행 엔진(스크립트), ② 데이터 계약(JSON), ③ 배포·갱신 인프라.
| 스크립트 | 역할 |
|---|---|
build_pptx.py | 핵심. edits.json대로 슬라이드를 골라내고 텍스트를 치환해 최종 pptx 출력. 출框(글자 넘침) 검사 내장 |
render_slides.py | pptx → PDF → 페이지별 PNG. 미리보기·자가검수용 (LibreOffice + pdftoppm 사용) |
compute_capacity.py | template.pptx에서 각 글상자의 용량(chars_per_line·max_lines·max_chars)을 계산. 새 템플릿 추가 시에만 필요 |
check_update.py | 로컬 VERSION ↔ 원격 updates.json 비교, 바뀔 파일 목록만 미리보기 |
apply_update.py | 델타(추가/수정/삭제) 파일만 내려받아 로컬 갱신. sha256 무결성 검증 |
build_manifest.py | 모든 파일의 sha256·버전 귀속을 담은 manifest.json 재생성 |
Presentation → slides → shapes → text_frame → paragraphs → runs라는 객체 트리로 다뤄준다. 이 프로젝트의 "글자만 교체" 마법이 전부 이 트리 조작이다.이 프로젝트의 두뇌는 코드가 아니라 JSON 두 개다. 각각 버전이 박힌 정식 스키마를 갖는다.
detail.json (스키마 ppt-template-detail/v1) — 템플릿 제작자가 쓰는 "지도". 각 슬라이드·각 텍스트 칸의 주소·용량·역할을 기술.edits.json (스키마 ppt-edit-spec/v1) — 편집하는 AI가 쓰는 "지시서". 어떤 슬라이드를 쓰고, 어느 칸에 무슨 글자를 넣을지.| 구성요소 | 역할 |
|---|---|
VERSION | 현재 시맨틱 버전(예 1.0.9). 업데이트 비교의 기준점 |
updates.json | 버전별 added/modified/removed 델타 이력 + update_source(git 원격 주소) |
manifest.json | 전 파일의 sha256 해시 → 내려받은 파일 무결성 검증 |
| Git (LFS → plain blob) | v1.0.6에서 대형 폰트 임베딩 제거(508MB→63MB) + LFS 탈출로 대역폭 제한 해소 |
이 프로젝트의 가장 배울 점은 역할을 둘로 쪼갠 데이터 계약이다. detail.json은 "이 템플릿은 이렇게 생겼다"는 사실(정비공의 지도)이고, edits.json은 "그래서 이렇게 바꿔라"는 의도(편집자의 지시서)다. 둘 다 $schema 버전 필드를 달고, 후방호환(v2가 나와도 v1을 계속 파싱) 원칙을 명시한다.
// edits.json — 편집 AI가 만드는 지시서
{
"template_slug": "minimal-business-summary",
"selected_slides": [1, 2, 3, 5, 7, 9, 10, 12, 14, 16],
"edits": [
{"slide": 1, "slot_id": "cover_title_cn", "new_text": "2026 연말 업무보고"},
{"slide": 9, "address": {"shape_id": 9, "paragraph": 0, "run": 0},
"expected_text": "METHOD", "new_text": "PILLARS"}
]
}
cover_title_cn처럼 사람이 읽는 의미 이름 — detail.json을 거쳐 물리 주소로 번역된다. address는 {shape_id, paragraph, run}로 글자를 정확히 찍는 좌표 — detail.json 없이도 동작한다. 도형 안의 글자는 문단(paragraph) → 런(run) 단위로 쪼개지는데, run 하나만 바꾸면 그 칸의 글꼴·색·크기가 보존된다.build_pptx.py의 apply_edit_to_shape()는 문단의 특정 run만 run.text = new_text로 갈아끼운다. run 번호를 안 주면 "문단 전체 교체" 모드가 되는데, 이때도 run 0의 서식은 유지하고 나머지 run만 비운다. 즉 글자는 바뀌어도 폰트·색·자간은 원본 그대로다.
prune_slides()는 프레젠테이션의 슬라이드 목록 XML(sldIdLst)에서 고른 슬라이드만 남기고 순서대로 재배열한다. 영리한 점: 새 요소를 만들지 않고 기존 요소 인스턴스를 그대로 재사용한다. 그래야 각 슬라이드의 관계 ID(r:id)가 보존되어 이미지·레이아웃 연결이 끊기지 않는다.
스프링 노트에서 필요한 페이지만 남기기. 페이지를 복사하지 않고, 링을 풀어 안 쓸 장을 빼고 다시 끼운다. 페이지에 붙은 포스트잇·클립(=관계 ID)이 그대로 따라온다. 새로 인쇄(복사)했다면 그 부속이 다 떨어졌을 것이다.
중국어·영어가 섞인 텍스트가 글상자를 넘치는지 빌드 시점에 검사한다. _visual_width()는 한자=1, 영문/숫자≈0.5, 공백≈0.35로 시각 폭을 계산하고, detail.json의 max_chars(글상자 실측 + 20% 여유로 미리 산출)와 비교한다.
autofit(자동 축소) 글상자는 "soft"로 통과 — 파워포인트가 알아서 줄인다.--strict를 주어도 현재(v1.0.17+)는 저장을 거부하지 않고 경고만 출력한다(advisory only). 넘침 발생 시 텍스트를 간결하게 다듬는 것이 원칙.detail.json 맨 위의 type_scale는 글자 크기 계층표(level 1 = 가장 큼)이고, 각 칸에 level이 붙는다. 규칙: 같은 level의 제목은 모두 같은 크기를 유지해야 한다. 한 곳이 길어 안 들어가면 글꼴을 줄이는 게 아니라 표현을 더 간결하게 줄인다 — 안 그러면 동급 제목 크기가 들쭉날쭉해져 보기 싫어지기 때문. 패턴 ④의 린트가 이 철학을 코드로 강제한다.
AI가 토큰을 아끼며 단계적으로만 정보를 연다: SKILL.md(언제·어떻게) → INDEX.md(19종 한 줄 요약) → 후보의 intro.md(압축 소개) → 확정된 1종의 detail.json(상세 좌표). 19개 전체 detail.json을 한꺼번에 읽지 않는다.
apply_update.py는 원격 git을 --depth 1 얕은 클론(LFS 스머지 끔)으로 가져와, 버전 이력에서 내 버전 이후 변경분만 합산해 그 파일만 복사한다. 받은 파일은 manifest.json의 sha256과 대조해 검증한다. "패키지 관리자(npm/apt)를 한 파일로 압축한" 듯한 설계다.
template.pptx(원본) + intro.md(AI 매칭용 요약) + detail.json(좌표·용량 데이터) + preview.png(사람이 보고 고르는 썸네일) 4개로 고정. AI는 preview.png로 사람에게 묻고, detail.json으로 정확히 채운다 — 역할이 깔끔히 분리된다.README는 "17 templates"라고 적고, templates/INDEX.md와 updates.json 이력은 19종을 가리킨다. 진실은 19종 — v1.0.2에서 거대 합집합 템플릿(report-template-massive)을 3종(models/charts/reports)으로 쪼개면서 늘었고, README 문구만 갱신이 덜 됐다. v1.0.8 변경 이력에도 "스크립트 목록/템플릿 수 정정"이 적혀 있어, 활발히 다듬어지는 중임을 보여준다.
Presentation → slides → shapes → text_frame → paragraphs → runs 트리를 자유자재로 다루는 법. 특히 그룹 도형 안을 재귀 탐색하는 find_shape()(MSO_SHAPE_TYPE.GROUP 분기)와, run 단위로 서식을 보존하며 텍스트만 바꾸는 기법.
실습 아이디어: 아무 .pptx나 열어 모든 슬라이드의 shape.shape_id·shape.has_text_frame·문단/런 텍스트를 트리로 덤프하는 30줄 스크립트를 써보기.
prune_slides()가 건드리는 sldIdLst, 슬라이드를 잇는 관계 ID r:id가 무엇인지. pptx의 압축을 풀어 내부 XML을 직접 들여다보면 "왜 요소 인스턴스를 재사용해야 관계가 안 끊기는지"가 손에 잡힌다.
실습 아이디어: pptx 확장자를 .zip으로 바꿔 압축 해제 → ppt/presentation.xml에서 <p:sldIdLst> 찾아보기.
사람용 README와 다른 "AI 실행용 진입 문서"를 어떻게 쓰는가. "언제 이 스킬을 호출하나", "첫 tool call로 무엇을 하라", "편집 철칙 9개", "정보를 단계적으로만 열어 토큰을 아껴라" — 에이전트를 위한 글쓰기의 교과서.
실습 아이디어: 자신이 자주 하는 작업(예: 회의록 정리)을 하나 골라 SKILL.md 형식으로 "AI가 따라 할 절차서"를 써보기.
역할이 다른 두 주체(템플릿 제작자 / 편집 AI)가 주고받는 데이터를 $schema 버전 + 필수/선택 필드 + 후방호환 규칙으로 못박는 법. "순서 무관", "마지막 값 우선", "editable=false는 건드리지 마라" 같은 강제 약속을 문서로 박제하는 감각.
실습 아이디어: pptx-edit-schema.md를 읽고, detail.json의 한 페이지 entry를 직접 손으로 써보기(slot_id·address·max_chars 포함).
npm/apt 없이 git 한 개로 증분 업데이트를 구현하는 법: 버전 이력에서 변경분 합산, 바뀐 파일만 다운로드, sha256으로 무결성 검증, LFS에서 일반 blob으로 탈출(대역폭 절감). 어떤 도구에든 이식 가능한 패턴.
실습 아이디어: apply_update.py의 collect_delta()를 읽고, "v1.0.3 → v1.0.9 업데이트 시 실제로 받는 파일 목록"을 손으로 계산해 --dry-run 결과와 맞춰보기.
한자·영문·공백의 폭이 다른 혼합 텍스트가 고정 크기 박스를 넘치는지 렌더링 없이 수식으로 예측하는 법. _visual_width()(한자=1, ascii=0.5)와 chars_per_line·max_lines·max_chars의 관계, 그리고 20% 여유(calibration tolerance)를 두는 실전 감각.
실습 아이디어: _visual_width()를 그대로 포팅해, 내가 쓴 슬라이드 제목 후보 5개의 시각 폭을 재고 어느 게 한 줄에 들어갈지 예측해보기.
이 스킬은 모델을 돌리지 않는다(텍스트 생성은 당신이 쓰는 외부 AI가 함). 그래서 요구사항이 매우 가볍다.
| 용도 | 필요한 것 | 비고 |
|---|---|---|
| 최소(빌드만) | Python 3.9+ · python-pptx 1.0+ | pptx 생성에는 이게 전부. CPU·RAM 거의 안 씀 |
| 미리보기 렌더 | LibreOffice(soffice) + Poppler(pdftoppm) | pptx→PDF→PNG. 없어도 빌드는 됨 |
| 폰트(렌더 정확도) | 微软雅黑 또는 fallback(WenQuanYi/Noto Sans SC 등) | fontconfig fonts.conf alias로 대체 |
| 자가 업데이트 | git (+ 인터넷) | --depth 1 얕은 클론, LFS 스머지 끔 |
| GPU | 불필요 | 추론을 안 하므로 그래픽카드 필요 없음 |
fallback 체인은 미리보기 PNG를 그리는 데만 쓰인다. 사용자에게 건네는 .pptx는 PowerPoint·WPS·Keynote에서 열 때 호스트 PC의 폰트로 자동 렌더되므로, 내 서버에 微软雅黑이 없어도 최종 파일 품질은 멀쩡하다.
저장소를 클론하고 minimal-business-summary 하나를 골라, selected_slides에 5장만 넣고 edits.json을 손으로 작성해 build_pptx.py를 돌려본다. 결과 pptx를 파워포인트에서 열어 레이아웃이 정말 안 깨졌는지 눈으로 확인.
python3 scripts/build_pptx.py \
templates/minimal-business-summary/template.pptx \
edits.json out/final.pptx \
--detail templates/minimal-business-summary/detail.json
같은 명령에 --dry-run을 붙여 "실제로 어떤 칸을 바꿀 계획인지"만 출력해보고, 일부러 아주 긴 제목을 넣은 뒤 --strict로 돌려 출框 경고 메시지(시각 폭 vs 용량, advisory only)를 읽어본다. 패턴 ④·⑤가 체감된다.
minimal-business-summary/detail.json을 열어 page_roles(cover/agenda/section_divider/content/ending)와 한 페이지의 text_slots(slot_id·address·max_chars·editable)를 해독한다. "이 칸은 왜 editable=false인가?"를 스스로 설명해보기.
detail.json이 없는 임의의 .pptx를 python-pptx로 직접 열어, 각 슬라이드의 shape/paragraph/run 구조를 덤프하고 render_slides.py로 PNG를 떠본다. 그런 뒤 명시적 address만으로 표지 제목을 교체하는 edits.json을 써본다(원본 파일은 절대 수정 금지, 새 파일로 출력).
apply_update.py의 collect_delta()·git_apply() 로직을 떼어, 자신의 다른 프로젝트(예: 프롬프트 모음 저장소)에 "버전 비교 → 변경 파일만 받기 → sha256 검증" 자가 업데이트를 붙여본다. updates.json·manifest.json·VERSION 3종 세트를 직접 설계하는 게 핵심.
| 주차 | 주제 | 구체적 학습 |
|---|---|---|
| 1주차 | python-pptx 기초 | Presentation/slide/shape/text_frame/paragraph/run 트리, 텍스트·표·차트 읽기. 공식 문서 + 이 저장소의 build_pptx.py 정독 |
| 2주차 | OOXML & .pptx 내부 | pptx=ZIP+XML 구조, sldIdLst·관계(r:id)·레이아웃/마스터. 압축 풀어 XML 직접 분석, 슬라이드 가지치기 직접 구현 |
| 3주차 | AI Skill 설계 + 데이터 계약 | SKILL.md 작성법, 점진적 공개, JSON Schema 버저닝·후방호환. detail.json/edits.json 같은 계약을 직접 설계 |
| 4주차 | 배포·자가 업데이트 | 시맨틱 버전, 델타 업데이트, sha256 무결성, Git LFS 트레이드오프. 작은 패키지에 자가 갱신 붙이기 |
1·2주차는 "파일을 다루는 손"(python-pptx + OOXML), 3·4주차는 "시스템을 설계하는 머리"(AI 계약 + 배포)다. 앞이 없으면 뒤가 공허하고, 뒤가 없으면 앞이 일회용 스크립트에 머문다. 이 저장소는 그 둘을 한 곳에서 보여주는 드문 표본이다.
| 용어 | 뜻 |
|---|---|
| python-pptx | .pptx를 파이썬으로 읽고 쓰는 라이브러리. 이 프로젝트의 텍스트 치환 엔진 |
| OOXML | Office Open XML. .pptx/.docx/.xlsx의 실체 — 여러 XML이 든 ZIP 파일 |
| shape / shape_id | 슬라이드 위의 개별 도형과 그 고유 번호. 텍스트 칸을 찾는 1차 좌표 |
| text_frame / paragraph / run | 도형 안 글자의 계층. 글상자→문단→런(같은 서식이 이어지는 글자 토막) |
| run 단위 치환 | run 하나의 .text만 바꿔 글꼴·색·크기를 보존하며 내용만 교체 |
| sldIdLst | presentation.xml 안 슬라이드 순서 목록. 가지치기가 이걸 재배열 |
| r:id (관계 ID) | 슬라이드↔이미지/레이아웃을 잇는 참조. 인스턴스 재사용으로 보존 |
| MSO_SHAPE_TYPE.GROUP | 그룹 도형. find_shape가 재귀로 그 안까지 탐색 |
| slot_id | cover_title_cn 같은 의미 이름. detail.json이 물리 주소로 번역 |
| address | {shape_id, paragraph, run} 물리 좌표. detail.json 없이도 정확히 지정 |
| detail.json | 템플릿 제작자가 쓰는 페이지·칸 지도(스키마 ppt-template-detail/v1) |
| edits.json | 편집 AI가 쓰는 선택·치환 지시서(스키마 ppt-edit-spec/v1) |
| max_chars / chars_per_line / max_lines | 글상자 용량 3종 지표. 글자 넘침 예측의 기준값(20% 여유 포함) |
| visual width(시각 폭) | 한자=1·영문/숫자≈0.5·공백≈0.35로 환산한 텍스트 폭. 출框 판정용 |
| 출框(overflow) 린트 | 빌드 시점 글자 넘침 검사. --strict도 저장 거부 없이 경고만 출력(advisory only, v1.0.17+) |
| autofit | 파워포인트의 자동 글자 축소. 켜진 박스는 넘침을 soft 통과 |
| type_scale / level | 글자 크기 계층표. 같은 level은 같은 크기 유지(길면 글자를 줄임) |
| page_roles | cover/agenda/section_divider/content/ending 등 페이지 역할 인덱스 |
| editable: false | 장식용 번호/플레이스홀더 등 건드리면 안 되는 칸 표시 |
| has_chart | 원생 PPT 차트 여부. true일 때만 --chart-data로 데이터 갱신 가능 |
| 모드 A / B / C | 내장 템플릿 사용 / 사용자 pptx 사용 / 완전 원창 설계 |
| SKILL.md | AI가 읽는 스킬 진입 문서. README(사람용)와 구분되는 실행 절차서 |
| Progressive Disclosure | 점진적 공개. INDEX→intro→detail 순으로만 열어 토큰 절약 |
| VERSION / updates.json / manifest.json | 시맨틱 버전 · 델타 이력 · sha256 무결성 — 자가 업데이트 3종 세트 |
| apply_update.py | 변경 파일만 증분 다운로드해 로컬 갱신하는 자가 업데이트 스크립트 |
| Git LFS | 대용량 파일용 git 확장. v1.0.6에서 탈출해 대역폭 제한을 없앰 |
| LibreOffice(soffice) / Poppler(pdftoppm) | 미리보기용 pptx→PDF, PDF→PNG 변환 도구 |
| fontconfig fallback | 微软雅黑 없을 때 WenQuanYi/DengXian/Noto Sans SC 등으로 대체 |
| 비상업 라이선스 | 템플릿이 제3자 작품 → 개인 학습·연구만 허용, 상업·재배포 금지 |