add: figma_to_html_agent/blocks/ + 변환 도구 docs 갱신

전체 401 files (397 추가 + 4 수정), 14304 insertions.

추가:
- figma_to_html_agent/blocks/ — Figma 변환 결과 (32 frame, ~79MB).
  각 frame folder = {analysis.md, flat.md, texts.md, index.html, assets/,
  _renders/, _render.py, RELATIONSHIPS.md / STATUS.md / classification.md
  (일부 frame)}.
  Phase Z 의 *figma source layer* — runtime 에서 직접 사용 X, contract /
  partial / builder adapter (미래 axis A) 의 source.
- figma_to_html_agent/DISCUSSION-SUMMARY-20260411.md — 변환 설계 회의 기록.
- figma_to_html_agent/HARNESS.md — 변환 검증 harness.
- figma_to_html_agent/scripts/fetch_figma_screenshots.py — Figma 스크린샷 자동 수집.

수정:
- figma_to_html_agent/PROCESS-CONTROL.md / PROCESS.md / RULES.md —
  변환 프로세스 / 룰 갱신 (R8/R9 lock 강화 등).
- figma_to_html_agent/blocks_index.md — 32 frame 인덱스 갱신.

Phase Z 영향 0 (figma_to_html_agent/blocks/ 가 V4 catalog +
templates/phase_z2/families adapter 의 source — runtime 에서 직접 import X).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 09:41:05 +09:00
parent cc2f434000
commit 9fbe3ac90c
401 changed files with 14304 additions and 2 deletions

View File

@@ -0,0 +1,123 @@
"""Figma Desktop MCP HTTP 엔드포인트를 직접 호출해 스크린샷 PNG를 저장한다.
MCP는 Streamable HTTP(SSE) 방식. 세션 헤더 필요.
"""
import base64
import json
import sys
import uuid
from pathlib import Path
from urllib import request, error
MCP_URL = "http://127.0.0.1:3845/mcp"
BLOCKS_DIR = Path(r"d:/ad-hoc/kei/design_agent/figma_to_html_agent/blocks")
TARGETS = [
("182:2602", "1171281192"),
("182:2766", "1171281198"),
("145:8504", "1171281208"),
("181:2519", "1171281210"),
]
def parse_sse(body: str) -> dict:
for line in body.splitlines():
if line.startswith("data: "):
return json.loads(line[6:])
raise RuntimeError(f"No data line in response: {body[:200]}")
def post(payload: dict, session_id: str | None = None) -> tuple[dict, str | None]:
data = json.dumps(payload).encode()
req = request.Request(
MCP_URL,
data=data,
method="POST",
headers={
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
},
)
if session_id:
req.add_header("mcp-session-id", session_id)
try:
with request.urlopen(req, timeout=60) as resp:
body = resp.read().decode()
sid = resp.headers.get("mcp-session-id")
return (parse_sse(body) if body.strip() else {}, sid)
except error.HTTPError as e:
sys.stderr.write(f"HTTP {e.code}: {e.read().decode()}\n")
raise
def initialize() -> str:
init_payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "figma-screenshot-saver", "version": "1.0"},
},
}
_, sid = post(init_payload)
if not sid:
sid = str(uuid.uuid4())
# notifications/initialized (no response expected)
notify = {"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}}
data = json.dumps(notify).encode()
req = request.Request(
MCP_URL,
data=data,
method="POST",
headers={
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"mcp-session-id": sid,
},
)
try:
request.urlopen(req, timeout=10).read()
except error.HTTPError:
pass
return sid
def get_screenshot(session_id: str, node_id: str, call_id: int) -> bytes:
payload = {
"jsonrpc": "2.0",
"id": call_id,
"method": "tools/call",
"params": {
"name": "get_screenshot",
"arguments": {"nodeId": node_id},
},
}
resp, _ = post(payload, session_id=session_id)
if "error" in resp:
raise RuntimeError(f"MCP error for {node_id}: {resp['error']}")
# content is a list; find image entry
for item in resp.get("result", {}).get("content", []):
if item.get("type") == "image":
return base64.b64decode(item["data"])
raise RuntimeError(f"No image in response for {node_id}: {json.dumps(resp)[:300]}")
def main() -> int:
print(f"[init] MCP session...")
sid = initialize()
print(f"[init] session-id={sid}")
for idx, (node_id, frame_id) in enumerate(TARGETS, start=2):
out_dir = BLOCKS_DIR / frame_id
out_dir.mkdir(parents=True, exist_ok=True)
out_path = out_dir / "preview.png"
print(f"[{node_id}] fetching -> {out_path}")
png = get_screenshot(sid, node_id, idx)
out_path.write_bytes(png)
print(f"[{node_id}] saved {len(png)} bytes")
return 0
if __name__ == "__main__":
raise SystemExit(main())