read · diff

git:20260826.b92bd8f to git:20260826.0cf7d65

197 added, 0 removed. Audit C to A.

+ ---
+ name: read
+ description: Figma 파일 URL을 받아 페이지 목록을 보여주고, 사용자가 선택한 페이지의 모든 최상위 프레임을 자동 수집한다. 각 프레임의 메타데이터와 스크린샷을 병렬로 읽어 마크다운 트리로 정리. Figma 데스크톱에서 프레임을 일일이 선택할 필요 없이 파일 URL만으로 전체 구조 파악 가능. Triggers - "/fig:read", "피그마 파일 읽어줘", "프레임 전부 뽑아줘", "피그마 전체 구조 보여줘", "이 파일 화면 목록 보여줘".
+ allowed-tools: AskUserQuestion, Bash, mcp__plugin_figma_figma__get_metadata, mcp__plugin_figma_figma__get_screenshot
+ ---
+
+ # fig:read — Figma 파일 전체 프레임 자동 수집
+
+ Figma 파일 URL 하나만 받아서 페이지 → 프레임 구조를 자동 탐색하고, 사용자가 고른 페이지의 모든 최상위 프레임을 메타데이터+스크린샷으로 수집한다. 데스크톱 앱에서 프레임을 선택하지 않아도 동작한다.
+
+ ## When to invoke
+
+ - 사용자가 Figma **파일** URL(노드 지정 없음)을 주며 "전부 읽어줘", "구조 보여줘", "프레임 다 뽑아줘"
+ - PRD·정본 작업 전 화면 인벤토리 파악 단계
+ - "/fig:read" 명시적 호출
+
+ ## When NOT to invoke
+
+ - 프레임 네이밍·섹션 정리 → `/fig:prep`
+ - 규칙 위반 검증 → `/fig:lint`
+ - 작업분이 정본에 반영됐는지 감사 → `/fig:sync`
+ - 코드 구현용 디자인 컨텍스트 → `figma:figma-design-to-code` skill · 프론트 레포 반영 → `/fig:code`
+
+ ## Inputs
+
+ - `figma_url` (필수): figma.com/design/:fileKey/... 형태의 URL
+ - node-id 파라미터가 있어도 무시하고 전체 파일로 다룬다 (사용자가 페이지 선택)
+
+ ## Procedure
+
+ ### 1. URL 파싱
+
+ Figma URL에서 fileKey 추출:
+ - 패턴: `figma.com/design/([A-Za-z0-9]+)/...`
+ - fileKey가 추출되지 않으면 사용자에게 정확한 URL 재요청
+
+ ### 1.5 토큰 사전 검증
+
+ REST API 경로를 쓰기 전에 가벼운 ping 으로 토큰 상태를 확인한다. 환경변수 이름은 설정 `tools.figma_token_env` 가 정한다(기본 `FIGMA_TOKEN`).
+
+ ```bash
+ source ~/.zshrc 2>/dev/null
+ VAR=$(python3 ${CLAUDE_PLUGIN_ROOT}/_common/scripts/lib/resolve-config.py \
+ | python3 -c 'import json,sys; print(json.load(sys.stdin)["tools"]["figma_token_env"])')
+ TOKEN="${!VAR}"
+ if [ -z "$TOKEN" ]; then
+ echo "STATE=NO_TOKEN ($VAR 미설정)"
+ else
+ CODE=$(curl -sS -o /dev/null -w '%{http_code}' \
+ -H "X-Figma-Token: $TOKEN" "https://api.figma.com/v1/me")
+ echo "STATE=HTTP_$CODE"
+ fi
+ ```
+
+ 상태별 분기:
+
+ | STATE | 의미 | 처리 |
+ |---|---|---|
+ | `NO_TOKEN` | 토큰 환경변수 미설정 | "토큰 미설정" 안내 + Notes의 PAT 발급 가이드 링크 + plugin:figma fallback |
+ | `HTTP_200` | 정상 | Step 2의 REST 경로 진행 |
+ | `HTTP_401` | 토큰 만료/무효 | **"FIGMA_TOKEN이 만료되었거나 유효하지 않습니다. 재발급이 필요합니다."** + 발급 가이드 + fallback |
+ | `HTTP_403` | 권한 부족 | "토큰 권한이 부족합니다. file_content:read scope로 재발급하세요" + fallback |
+ | `HTTP_429` | rate limit | "잠시 후 다시 시도하세요" + fallback |
+ | 그 외 | 알 수 없는 오류 | HTTP 코드 그대로 노출 + fallback |
+
+ **중요**: 401 은 "토큰 없음"·"만료"·"무효" 가 같이 나오는 상태 코드다. 위에서 환경변수 존재 여부를 먼저 보기 때문에, 401 이 나왔다는 건 값은 있는데 거부됐다는 뜻 — 만료·무효로 안내한다.
+
+ ### 2. 페이지 목록 조회
+
+ **우선순위 1: Figma REST API (1.5에서 HTTP_200 통과 시)**
+
+ ```bash
+ curl -sS -w "\nHTTP_CODE=%{http_code}" \
+ -H "X-Figma-Token: $FIGMA_TOKEN" \
+ "https://api.figma.com/v1/files/{fileKey}?depth=1"
+ ```
+
+ - depth=1로 페이지 노드만 받음 (전체 트리 다운로드 회피)
+ - 응답 본문에서 `.document.children[]` → `{nodeId}|{pageName}` 추출
+ - HTTP_CODE도 같이 받아 파일별 권한 이슈 가드:
+ - `200` → 진행
+ - `403` → "이 파일에 접근 권한이 없습니다 (다른 팀/비공개 파일일 수 있음)" + fallback
+ - `404` → "파일을 찾을 수 없습니다. URL을 확인하세요"
+ - 그 외 → 에러 노출 + fallback
+ - 토큰을 응답이나 로그에 출력 금지 — 헤더에만 사용
+
+ **우선순위 2: plugin:figma MCP fallback (토큰 없음/오류 시)**
+
+ `mcp__plugin_figma_figma__get_metadata`를 fileKey만 전달해 호출.
+ - 한계: 데스크톱 앱의 현재 열린 파일/뷰포트에 의존해 일부 페이지만 반환할 수 있음
+ - 사용자 URL의 node-id가 페이지(canvas 타입)면 그 페이지도 직접 진입 후보로 포함
+
+ **페이지 0개**: 토큰 미설정 + plugin도 빈 응답이면, 출력 마지막의 "PAT 발급 가이드" 안내하고 종료.
+
+ 페이지가 1개뿐이면 선택 단계 생략하고 바로 3-2로 진행.
+
+ ### 3. 페이지 선택 (페이지 ≥ 2개일 때)
+
+ `AskUserQuestion`으로 페이지 목록 제시:
+ - `multiSelect: true`
+ - 각 페이지 이름을 option label로 (최대 4개까지만 표시 가능 — 페이지가 5개 이상이면 처음 3개 + "전체" + "직접 선택" 식으로 구성)
+ - 페이지가 너무 많으면(8개 초과) 마크다운으로 번호 매겨 출력 후 사용자가 직접 번호 입력하게 안내
+
+ ### 3-2. 프레임 트리 조회
+
+ 선택된 페이지마다 `get_metadata` 호출:
+ - `fileKey` + 페이지 `nodeId`
+ - depth 2 정도로 충분 (페이지 직속 자식 = 최상위 프레임)
+
+ 각 페이지의 최상위 프레임 ID 목록 수집.
+
+ ### 4. 프레임 수 가드
+
+ 전체 프레임 수가 설정 `tools.frame_count_guard` 를 넘으면 진행 전 경고한다:
+ - 마크다운으로 "{N}개 프레임 발견. 스크린샷까지 받으면 시간/컨텍스트 부담이 큼. 계속할까요?" 출력
+ - `AskUserQuestion`으로 "전부 진행 / 메타데이터만(스크린샷 생략) / 페이지 다시 선택" 분기
+
+ ### 5. 프레임별 수집 (병렬)
+
+ 각 프레임에 대해 다음 두 호출을 한 메시지 안에 병렬로:
+ - `mcp__plugin_figma_figma__get_metadata` (fileKey + nodeId)
+ - `mcp__plugin_figma_figma__get_screenshot` (fileKey + nodeId)
+
+ 한 번에 너무 많이 띄우면 무거우므로 **5개씩 배치**로 나눠 호출.
+
+ 스크린샷 호출이 실패해도 메타데이터는 살리고, 실패 사실은 출력에서 명시(`스크린샷: 실패`).
+
+ ### 6. 마크다운 트리 출력
+
+ 다음 형식으로 대화창에 출력:
+
+ ```
+ # {파일명}
+
+ ## {페이지명 1}
+
+ ### {프레임명} (`{nodeId}`)
+ - 링크: https://figma.com/design/{fileKey}/?node-id={nodeId(:→-)}
+ - 크기: {width}×{height}
+ - 자식 수: {childCount}
+ - 스크린샷: {경로 또는 "실패"}
+
+ ### {프레임명 2} ...
+
+ ## {페이지명 2} ...
+ ```
+
+ 스크린샷은 `get_screenshot`이 반환한 이미지를 그대로 표시(어시스턴트 응답에 포함).
+
+ ### 7. 다음 액션 안내
+
+ 출력 마지막에 한 줄로:
+ > 다음 단계 후보: `/fig:prep`(구조 정리) · `/fig:lint`(검증) · `/fig:sync`(정본 반영 감사)
+
+ ## Output Contract
+
+ - 모든 출력은 마크다운, 대화창에만 (파일/Notion 생성 없음)
+ - 노드 링크는 클릭 가능한 figma.com URL 형식 — nodeId의 `:`를 `-`로 변환
+ - 실패한 항목은 누락이 아니라 명시적으로 "실패" 표기
+
+ ## Constraints
+
+ - 파일/Notion/Slack 등 외부 쓰기 금지 — 순수 읽기 스킬
+ - 페이지 선택 외에는 인터뷰 최소화 (사용자 부담 최소화)
+ - 한 번 수집한 결과는 컨텍스트에 남으므로 후속 스킬이 이어받기 좋게 구조화
+
+ ## Notes
+
+ - plugin:figma MCP의 `get_metadata`/`get_screenshot`은 fileKey+nodeId만 있으면 호출 가능 — 데스크톱 앱 선택 상태 무관
+ - 단, plugin MCP `get_metadata(fileKey only)`는 데스크톱 앱 컨텍스트에 묶여 일부 페이지만 노출됨 → 전체 페이지 enumerate는 Figma REST API가 필수
+ - 컴포넌트/인스턴스/벡터까지 펴는 건 의도적으로 안 함. 최상위 프레임에서 멈춤
+ - 디자인이 자주 바뀌면 결과 캐싱 의미 없음 — 매번 다시 호출
+
+ ### Figma Personal Access Token (PAT) 설정
+
+ REST API 경로를 쓰려면 PAT가 필요. 1회 설정:
+
+ 1. Figma 웹 → 우상단 프로필 → Settings → **Security** 탭 → **Personal access tokens** → **Generate new token**
+ 2. Scope 최소화: `File content` → **Read-only**만 체크 (다른 권한 불필요)
+ 3. 토큰 복사 (한 번만 표시됨, `figd_` 로 시작)
+ 4. `~/.zshrc` 끝에 추가 (이름은 설정 `tools.figma_token_env` 와 맞춘다):
+
+ ```bash
+ export FIGMA_TOKEN="figd_여기에_붙여넣기"
+ ```
+
+ 5. 새 터미널 열기 또는 `source ~/.zshrc` 실행
+
+ 토큰 보안:
+ - 절대 응답·로그·커밋에 노출 금지
+ - jq로 파싱할 때도 헤더로만 전달, 응답 본문에만 의존
+ - 만료 의심되면 Figma Security 탭에서 토큰 revoke 후 재발급
+
+ 토큰 만료 감지 한계:
+ - Figma PAT 는 토큰 자체에 만료일이 인코딩되지 않아 사전 조회가 안 된다
+ - API 호출의 HTTP 응답 코드로만 사후 감지 가능
+ - `401` 응답은 "토큰 없음·만료·무효"가 모두 같이 나오는 상태이므로, 스킬은 환경변수 존재 여부를 먼저 체크해 케이스를 분리함