#!/usr/bin/env python # -*- coding: utf-8 -*- """ zioinfo(ythong 마켓플레이스) 사내전용 설치가이드 생성 — python-pptx. (스크립트가 진실원천: 내용 수정 후 재생성) 실행: python docs/gen_install_guide.py → docs/zioinfo_사내전용_설치가이드.pptx """ import os from pptx import Presentation from pptx.util import Inches, Pt from pptx.dml.color import RGBColor from pptx.enum.text import PP_ALIGN, MSO_ANCHOR from pptx.enum.shapes import MSO_SHAPE # ── 테마 (ZIO INFOTECH 브랜드: 네이비 + 라이트블루 + 그레이) ────────── NAVY = RGBColor(0x17, 0x35, 0x7F) # 브랜드 네이비 (배경/제목) BLUE = RGBColor(0x2E, 0x9B, 0xD6) # 라이트블루 액센트 SKY = RGBColor(0x4F, 0xB3, 0xE8) # 밝은 하늘 포인트 STEEL = RGBColor(0xA7, 0xAB, 0xB0) # 브랜드 그레이 INK = RGBColor(0x14, 0x2B, 0x66) # 본문 제목 네이비 GRAY = RGBColor(0x55, 0x5F, 0x70) # 본문 회색 LIGHT = RGBColor(0xE2, 0xED, 0xF8) # 연블루 배경 CARD = RGBColor(0xF6, 0xF9, 0xFD) WHITE = RGBColor(0xFF, 0xFF, 0xFF) FONT = "맑은 고딕" HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.dirname(HERE) BANNER = os.path.join(ROOT, "zioinfo_banner.png") ICON = os.path.join(ROOT, "zioinfo_icon.png") TEAM = os.path.join(ROOT, "zioinfo_team.png") prs = Presentation() prs.slide_width = Inches(13.333) prs.slide_height = Inches(7.5) BLANK = prs.slide_layouts[6] def _set(run, size, bold=False, color=INK, font=FONT): run.font.size = Pt(size); run.font.bold = bold run.font.color.rgb = color; run.font.name = font def box(slide, x, y, w, h, fill=None, line=None, line_w=1.0, round_=False): shp = slide.shapes.add_shape( MSO_SHAPE.ROUNDED_RECTANGLE if round_ else MSO_SHAPE.RECTANGLE, Inches(x), Inches(y), Inches(w), Inches(h)) shp.shadow.inherit = False if fill is None: shp.fill.background() else: shp.fill.solid(); shp.fill.fore_color.rgb = fill if line is None: shp.line.fill.background() else: shp.line.color.rgb = line; shp.line.width = Pt(line_w) return shp def text(slide, x, y, w, h, runs, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, space=4, line_spacing=None): tb = slide.shapes.add_textbox(Inches(x), Inches(y), Inches(w), Inches(h)) tf = tb.text_frame; tf.word_wrap = True; tf.vertical_anchor = anchor tf.margin_left = tf.margin_right = Inches(0.05) tf.margin_top = tf.margin_bottom = Inches(0.02) for i, para in enumerate(runs): p = tf.paragraphs[0] if i == 0 else tf.add_paragraph() p.alignment = align; p.space_after = Pt(space) if line_spacing: p.line_spacing = line_spacing for spec in para: txt, size, bold, color = spec[0], spec[1], spec[2], spec[3] fnt = spec[4] if len(spec) > 4 else FONT r = p.add_run(); r.text = txt; _set(r, size, bold, color, fnt) return tb def header(slide, title, kicker=None, n=None): box(slide, 0, 0, 13.333, 1.15, fill=NAVY) box(slide, 0, 1.15, 13.333, 0.06, fill=BLUE) runs = [] if kicker: runs.append([(kicker, 12, True, SKY)]) runs.append([(title, 26, True, WHITE)]) text(slide, 0.6, 0.12, 11.5, 0.95, runs, anchor=MSO_ANCHOR.MIDDLE, space=2) if n is not None: text(slide, 12.2, 0.12, 0.9, 0.95, [[(f"{n:02d}", 22, True, SKY)]], align=PP_ALIGN.RIGHT, anchor=MSO_ANCHOR.MIDDLE) def footer(slide): text(slide, 0.6, 7.05, 9, 0.35, [[("ZIO INFOTECH · ythong 마켓플레이스 사내전용 설치가이드", 9, False, GRAY)]]) text(slide, 11.0, 7.05, 1.9, 0.35, [[("2026-07", 9, False, GRAY)]], align=PP_ALIGN.RIGHT) def card(slide, x, y, w, h, title, lines, accent=BLUE, tsize=15): box(slide, x, y, w, h, fill=CARD, line=RGBColor(0xDD, 0xE6, 0xF2), line_w=0.75, round_=True) box(slide, x, y, 0.12, h, fill=accent) runs = [[(title, tsize, True, INK)]] for ln in lines: runs.append([("· ", 11, True, accent), (ln, 11.5, False, GRAY)]) text(slide, x + 0.3, y + 0.22, w - 0.45, h - 0.4, runs, space=4) def codebox(slide, x, y, w, h, title, code_lines): box(slide, x, y, w, h, fill=CARD, line=RGBColor(0xDD, 0xE6, 0xF2), line_w=0.75, round_=True) box(slide, x, y, 0.12, h, fill=NAVY) text(slide, x + 0.3, y + 0.15, w - 0.5, 0.4, [[(title, 14, True, INK)]]) runs = [[(ln, 12.5, False, RGBColor(0x1C, 0x3D, 0x91), "Consolas")] for ln in code_lines] text(slide, x + 0.3, y + 0.6, w - 0.5, h - 0.75, runs, space=3) # ── 1. 타이틀 ──────────────────────────────────────────────────────── s = prs.slides.add_slide(BLANK) box(s, 0, 0, 13.333, 7.5, fill=NAVY) box(s, 0, 0, 0.25, 7.5, fill=BLUE) box(s, 0.25, 0, 0.1, 7.5, fill=SKY) if os.path.exists(BANNER): try: s.shapes.add_picture(BANNER, Inches(7.6), Inches(0.7), height=Inches(2.4)) except Exception: pass text(s, 0.9, 2.1, 10.5, 2.4, [ [("사내전용 설치가이드", 52, True, WHITE)], [("ythong 마켓플레이스 · zioinfo 통합 플러그인 v2.0.0", 21, True, SKY)], ], space=10) text(s, 0.95, 4.6, 11.4, 1.6, [ [("ZIO INFOTECH Suite — 풀스택 개발 · 하네스 팩토리 · 제안서 · PM·PMO 4개 트랙 통합", 16, True, RGBColor(0xCF, 0xE4, 0xF5))], [("설치 2줄이면 끝 — /plugin install zioinfo@ythong → /reload-plugins", 14, False, RGBColor(0xB9, 0xCD, 0xE6))], ], space=8) text(s, 0.95, 6.5, 11, 0.5, [[("ZIO INFOTECH · 사내 배포용 · 외부 공유 금지", 11, False, RGBColor(0x8A, 0x9F, 0xC0))]]) # ── 2. 개요: 무엇이 설치되나 ───────────────────────────────────────── s = prs.slides.add_slide(BLANK); header(s, "무엇이 설치되나 — zioinfo 통합 플러그인 (4개 트랙)", "OVERVIEW", 2) rows = [ ("풀스택 개발", "zio-harness 스킬", "React + Spring Boot + Mobile 6인 팀 — analyst·designer·agent·bot·hermes(전령: push·배포·알림)"), ("하네스 팩토리", "harness 스킬", "메타 스킬 — 도메인 한 문장을 에이전트 팀 + 스킬로 자동 생성"), ("제안서", "/zioinfo:proposal", "RFP + PPT 템플릿 → 제안서 자동 완성 (전문가 7인 + 아키텍처 풀)"), ("PM·PMO", "/zioinfo:pmo", "계획/WBS·리스크·보고(EVM)·감리·포트폴리오·AI 의뢰서 (8인) + LLM wiki(/zioinfo:wiki)"), ] y = 1.6 for name, ver, desc in rows: box(s, 0.6, y, 12.1, 1.05, fill=CARD, line=RGBColor(0xDD, 0xE6, 0xF2), line_w=0.75, round_=True) box(s, 0.6, y, 0.12, 1.05, fill=BLUE) text(s, 0.95, y + 0.1, 2.9, 0.5, [[(name, 16, True, INK)]]) text(s, 0.95, y + 0.58, 2.9, 0.4, [[(ver, 11, True, BLUE)]]) text(s, 4.0, y, 8.5, 1.05, [[(desc, 12.5, False, GRAY)]], anchor=MSO_ANCHOR.MIDDLE) y += 1.22 footer(s) # ── 3. 사전 준비 ───────────────────────────────────────────────────── s = prs.slides.add_slide(BLANK); header(s, "사전 준비", "PREREQUISITES", 3) card(s, 0.6, 1.6, 5.95, 2.3, "필수", [ "Claude Code v2.x 이상 (claude --version)", "사내 Gitea 접근 가능 (git.zioinfo.co.kr)", "git 클라이언트 설치", ], accent=NAVY, tsize=16) card(s, 6.8, 1.6, 5.95, 2.3, "선택 (기능별)", [ "Agent Teams 팀 모드: CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1", "제안서 트랙 PPTX 생성: pip install python-pptx", "지식그래프·LLM wiki: graphify (훅이 자동 설치, 끄기 ZIO_HARNESS_NO_GRAPHIFY=1)", ], accent=BLUE, tsize=16) card(s, 0.6, 4.15, 12.15, 2.1, "권한·보안 안내", [ "마켓플레이스는 사내 Gitea 저장소(ythong/harness) 기준 — 외부 공개 저장소 아님", "플러그인 산출물에 자격증명·내부 IP 기재 금지 (하네스 공통 보안 원칙)", "설치 범위는 기본 user scope — 개인 PC 단위로 설치·관리", ], accent=SKY, tsize=16) footer(s) # ── 4. STEP 1: 마켓플레이스 등록 ───────────────────────────────────── s = prs.slides.add_slide(BLANK); header(s, "STEP 1 — 마켓플레이스 등록 (최초 1회)", "INSTALL", 4) codebox(s, 0.6, 1.6, 12.1, 1.7, "Claude Code 세션 안에서", [ "/plugin marketplace add https://git.zioinfo.co.kr/ythong/harness.git", ]) codebox(s, 0.6, 3.5, 12.1, 1.7, "또는 터미널에서", [ "claude plugin marketplace add https://git.zioinfo.co.kr/ythong/harness.git", ]) text(s, 0.6, 5.5, 12.1, 1.2, [ [("⚠ 비-GitHub git 저장소는 URL 끝에 ", 13, False, GRAY), (".git 필수", 13, True, NAVY), (" — 없으면 \"expected object, received string\" 오류", 13, False, GRAY)], [("→ 성공 메시지: Successfully added marketplace: ythong", 12.5, False, BLUE)], ], space=6) footer(s) # ── 5. STEP 2: 플러그인 설치 ───────────────────────────────────────── s = prs.slides.add_slide(BLANK); header(s, "STEP 2 — 플러그인 설치 + 리로드", "INSTALL", 5) codebox(s, 0.6, 1.6, 12.1, 2.0, "설치는 하나면 끝 (세션 안 2줄)", [ "/plugin install zioinfo@ythong", "/reload-plugins", ]) text(s, 0.6, 3.75, 12.1, 0.9, [ [("구버전 4개(harness·zio-harness·proposal-builder·구 zioinfo)를 쓰던 PC는 먼저 ", 12.5, False, GRAY), ("claude plugin uninstall <이름>@ythong", 12.5, True, NAVY), (" 으로 제거", 12.5, False, GRAY)], ], space=6) text(s, 0.6, 4.8, 12.1, 1.6, [ [("설치 후 ", 13.5, False, GRAY), ("/reload-plugins", 13.5, True, NAVY), (" 한 번이면 현재 세션에 즉시 반영 (재시작 불필요)", 13.5, False, GRAY)], [("→ 성공 메시지: Successfully installed plugin: <이름>@ythong (scope: user)", 12.5, False, BLUE)], ], space=6) footer(s) # ── 6. STEP 3: 설치 검증 ───────────────────────────────────────────── s = prs.slides.add_slide(BLANK); header(s, "STEP 3 — 설치 검증", "VERIFY", 6) codebox(s, 0.6, 1.6, 5.95, 2.2, "설치 목록·상태", [ "claude plugin list", ]) codebox(s, 6.8, 1.6, 5.95, 2.2, "컴포넌트 인벤토리", [ "claude plugin details zioinfo@ythong", ]) card(s, 0.6, 4.1, 12.15, 2.2, "정상 판정 기준 (zioinfo v2.0.0)", [ "Skills 4: zio-harness·harness·proposal-builder-orchestrator·pm-pmo-orchestrator / Commands 3: pmo·proposal·wiki", "Agents 15 · Hooks 1(SessionStart graphify) · Themes 1(ZIO WISE) · 오류 0", "세션에서 /zioinfo:pmo 커맨드가 자동완성에 보이면 로드 완료", ], accent=BLUE, tsize=16) footer(s) # ── 7. 플러그인별 사용법 ───────────────────────────────────────────── s = prs.slides.add_slide(BLANK); header(s, "설치 후 바로 쓰기 — 트리거 한 마디", "USAGE", 7) card(s, 0.6, 1.55, 5.95, 2.45, "풀스택 개발 (zio-harness 트랙)", [ "\"기능 추가해줘\" · \"push하고 배포 확인해줘\"", "→ 분석→디자인→구현→검증→전달(hermes) 6인 파이프라인", ], accent=BLUE, tsize=16) card(s, 6.8, 1.55, 5.95, 2.45, "하네스 팩토리 (harness 트랙)", [ "\"이 프로젝트용 하네스 구성해줘\"", "→ 에이전트 팀 + 스킬 + CLAUDE.md 자동 생성", ], accent=NAVY, tsize=16) card(s, 0.6, 4.25, 5.95, 2.45, "제안서 (/zioinfo:proposal)", [ "/zioinfo:proposal ○○사업 제안서 또는 \"제안서 작성해줘\"", "→ RFP 분석→전략→아키텍처→본문→PPTX→QA", ], accent=SKY, tsize=16) card(s, 6.8, 4.25, 5.95, 2.45, "PM·PMO + LLM wiki", [ "/zioinfo:pmo 프로젝트 계획 · 주간보고 · 감리 준비", "/zioinfo:wiki → graphify-out/wiki/ 코드베이스 위키 생성", ], accent=STEEL, tsize=16) footer(s) # ── 8. 업데이트 · 제거 ─────────────────────────────────────────────── s = prs.slides.add_slide(BLANK); header(s, "업데이트 · 제거", "MAINTAIN", 8) codebox(s, 0.6, 1.6, 5.95, 2.6, "업데이트 (마켓플레이스 갱신 후 재설치)", [ "claude plugin marketplace update ythong", "claude plugin install zioinfo@ythong", "/reload-plugins", ]) codebox(s, 6.8, 1.6, 5.95, 2.6, "제거", [ "claude plugin uninstall zioinfo@ythong", ]) card(s, 0.6, 4.5, 12.15, 1.9, "버전 정책", [ "플러그인 버전은 저장소 plugin.json이 진실원천 (Semantic Versioning)", "변경 이력은 저장소 CLAUDE.md 하네스 섹션·CHANGELOG 참조", ], accent=BLUE, tsize=16) footer(s) # ── 9. 트러블슈팅 ──────────────────────────────────────────────────── s = prs.slides.add_slide(BLANK); header(s, "트러블슈팅", "FAQ", 9) rows = [ ("plugin not found", "마켓플레이스 add 여부 확인 → /plugin marketplace add ... (.git 포함)"), ("expected object, received string", "비-GitHub git URL에 .git 누락 — URL 끝에 .git을 붙여 재시도"), ("invalid manifest", "plugins/<이름>/.claude-plugin/plugin.json 존재·JSON 유효성 확인 (BOM 없는 UTF-8)"), ("설치했는데 커맨드가 안 보임", "/reload-plugins 실행 또는 세션 재시작"), ("스킬이 트리거되지 않음", "명시적 표현 사용(\"제안서 작성\", \"프로젝트 계획\") 또는 /proposal·/pmo 직접 호출"), ("팀 모드가 동작 안 함", "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 환경변수 확인"), ] y = 1.5 for prob, fix in rows: box(s, 0.6, y, 12.1, 0.82, fill=CARD, line=RGBColor(0xDD, 0xE6, 0xF2), line_w=0.75, round_=True) text(s, 0.85, y, 4.15, 0.82, [[(prob, 12.5, True, NAVY)]], anchor=MSO_ANCHOR.MIDDLE) text(s, 5.15, y, 7.4, 0.82, [[(fix, 11.5, False, GRAY)]], anchor=MSO_ANCHOR.MIDDLE) y += 0.92 footer(s) # ── 10. 마무리 ─────────────────────────────────────────────────────── s = prs.slides.add_slide(BLANK) box(s, 0, 0, 13.333, 7.5, fill=NAVY) box(s, 0, 0, 13.333, 0.18, fill=BLUE) box(s, 0, 7.32, 13.333, 0.18, fill=SKY) if os.path.exists(ICON): try: s.shapes.add_picture(ICON, Inches(5.85), Inches(0.9), height=Inches(1.7)) except Exception: pass text(s, 0.9, 2.9, 11.5, 2.2, [ [("설치 2줄, 팀이 도착한다.", 34, True, WHITE)], [("/plugin install <플러그인>@ythong · /reload-plugins", 18, True, SKY)], ], align=PP_ALIGN.CENTER, space=12) text(s, 0.9, 5.4, 11.5, 1.2, [ [("문의: ZIO INFOTECH 인프라팀 · 저장소: git.zioinfo.co.kr/ythong/harness", 14, False, RGBColor(0xCF, 0xE4, 0xF5))], [("사내 배포용 · 외부 공유 금지 · Apache-2.0", 12, False, RGBColor(0x9A, 0xAF, 0xD0))], ], align=PP_ALIGN.CENTER, space=8) out = os.path.join(HERE, "zioinfo_사내전용_설치가이드.pptx") try: prs.save(out) except PermissionError: out = os.path.join(HERE, "zioinfo_사내전용_설치가이드-1.pptx") prs.save(out) print("(원본 파일이 열려 있어 -1로 저장)") print("생성 완료:", out, "(", len(prs.slides._sldIdLst), "슬라이드 )")