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:
123
figma_to_html_agent/scripts/fetch_figma_screenshots.py
Normal file
123
figma_to_html_agent/scripts/fetch_figma_screenshots.py
Normal 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())
|
||||
Reference in New Issue
Block a user