bergside/design-md-chrome · 제작 Bergside(typeui.sh 운영사) · 라이선스 MIT · manifest.json 기준 버전 0.4.0 · Manifest V3 크롬 확장 · package.json 없음 — 외부 npm 의존성 0개, 순수 JS(ES 모듈) · 추적 파일 23개 · 최신 커밋 8e07614 "add: ref link to sponsors"(2026-05-25) · 별점 미인용)
design-md-chrome은 "웹사이트 → 디자인 토큰 문서" 변환기다. 확장 아이콘을 누르면 지금 활성 탭 페이지의 요소들에서 window.getComputedStyle로 실제 렌더링 값을 긁고, 그 값을 빈도 집계해 font.size.md, color.text.primary, space.3 같은 이름 붙은 토큰으로 정리한 뒤, 정해진 마크다운 템플릿에 끼워 넣는다.
출력은 두 모드다. DESIGN.md는 사람이 참고할 디자인 시스템 문서이고, SKILL.md는 YAML 프런트매터와 TYPEUI_SH_MANAGED_START/END 마커까지 갖춰 Claude Code(.claude/skills) 같은 에이전트가 바로 읽을 수 있게 만든 버전이다. 팝업에는 Copy·Download 외에 Quick install 버튼이 있어, 브라우저의 파일시스템 접근 API로 프로젝트 폴더에 SKILL.md를 곧장 써 넣을 수도 있다.
사람이 "저 사이트 느낌으로 만들어줘"라고 하면 에이전트는 색을 짐작하거나 스크린샷을 보고 어림잡는다. 이 확장은 그 대신 줄자(computed style)를 페이지에 대고 여백·글자 크기·색을 그대로 재서 숫자로 적어온다.
다만 "이 사이트가 어떤 성격인지(대시보드? 쇼핑몰?)"는 줄자로 잴 수 없는 부분이라, 그건 여전히 제목·메뉴 글자를 훑어보고 짐작하는 수준이다. 정확한 실측과 대략적인 짐작이 한 문서 안에 섞여 있다는 점이 이 도구의 정체다.
2025~2026년 사이 Claude Code의 .claude/skills, Codex의 에이전트 규칙 파일처럼 "AI 코딩 에이전트에게 미리 읽혀 두는 스펙 문서"가 하나의 표준 관행이 됐다. 문제는 이런 문서를 사람이 처음부터 손으로 쓰기가 번거롭다는 것이다. 색상 하나, 여백 값 하나까지 다 사람이 재서 적어야 했다.
실무에서 흔한 요청은 "이 경쟁사 사이트 톤으로 우리 랜딩페이지 만들어줘"다. 이 확장은 그 사이트를 열어놓고 버튼 한 번으로 색상 hex 값·spacing px 값까지 뽑아주므로, 에이전트가 대략적인 느낌이 아니라 구체적인 숫자를 갖고 시작하게 해준다.
이 저장소를 만든 Bergside는 typeui.sh라는 "디자인 시스템 스킬 큐레이션" 서비스를 운영한다. README와 팝업 도움말은 계속 typeui.sh/design-md, typeui.sh/design-skills를 참조 링크로 건다. 즉 이 크롬 확장은 무료로 배포해 사용자를 typeui.sh 생태계로 끌어들이는 로컬 도구이기도 하다 — 오픈소스 확장 자체가 회사의 배포 채널인 구조다.
package.json조차 없는 순수 바닐라 JS라 npm install도, 빌드도 필요 없다. "압축해제 상태로 로드"만 하면 그대로 동작한다. 이런 제로 디펜던시·제로 빌드 특성이 "가볍게 훑어보고 바로 쓸 수 있는 도구"로 눈에 띄는 이유 중 하나다.
디자인 토큰을 뽑는 도구는 이미 많다. Figma의 Dev Mode나 Tokens Studio는 디자인 파일 원본이 있어야 하고, 브라우저 DevTools로 하나하나 복사하는 방법은 사람 손이 많이 간다. 이 확장은 "디자인 파일 없이, 이미 배포된 아무 웹사이트에서" 바로 토큰을 뽑는다는 좁지만 실용적인 틈을 파고든다.
문제. AI 코딩 에이전트에게 "이 사이트처럼 만들어줘"라고 말해도, 에이전트는 스크린샷을 보고 색을 추측하거나(부정확), 아예 아무 근거 없이 흔한 값(#111827, 16px 같은 "관용적으로 자주 쓰이는" 숫자)을 골라 쓴다. 결과물은 "비슷하지만 미묘하게 다른" 디자인이 되기 쉽다.
해결. 브라우저는 페이지를 그릴 때 이미 모든 요소의 정확한 색·크기·간격을 계산해 갖고 있다(getComputedStyle). design-md-chrome은 이 값을 그대로 읽어와 빈도순으로 집계하고, color.text.primary, space.3 같은 의미 있는 이름을 붙여 마크다운 표로 정리한다. 에이전트는 이제 "대략 짙은 회색"이 아니라 #111827이라는 정확한 값을 받는다.
색·크기·간격은 실측값이라 정확하다. 반면 문서의 Brand·Audience·Product surface 항목(예: "대시보드 웹앱", "개발자와 기술팀")은 lib/normalize.mjs의 inferSiteProfile()이 제목·메뉴 텍스트·폼 개수 같은 신호를 키워드로 스코어링해 추측한 결과다.
즉 이 문서를 곧이곧대로 믿으면 "색상표는 정확한데 브랜드 설명은 헛다리 짚은" 결과물이 나올 수 있다. 실측 부분과 추측 부분을 구분해서 읽는 게 이 도구를 제대로 쓰는 첫걸음이다.
코드는 4개의 층으로 깔끔히 나뉜다. 팝업(사용자 화면), 백그라운드 서비스 워커(조율자), 콘텐츠 스크립트(페이지에 실제로 들어가는 코드), 그리고 lib/ 아래 부작용 없는 순수 함수 4개(정규화·마크다운 생성 2종·검증)다.
이 구조가 주는 교훈은 "브라우저 API를 만지는 코드(주입·메시징)"와 "순수 데이터 변환 코드(lib/)"를 확실히 분리했다는 점이다. lib/ 안 함수들은 전부 입력을 받아 값을 반환할 뿐 chrome.* API를 전혀 부르지 않는다 — 그래서 tests/run-tests.mjs가 크롬 없이 node 하나로 그대로 테스트할 수 있다.
보통 확장은 manifest.json의 content_scripts에 페이지를 등록해 놓고 자동 주입시키지만, 이 저장소는 그렇게 하지 않는다. manifest.json에는 content_scripts 항목 자체가 없다. 대신 service-worker.js의 injectExtractor()가 확장 아이콘을 누른 그 순간에만 chrome.scripting.executeScript({ target:{tabId}, files:["content-script.js"] })로 코드를 밀어 넣는다.
content-script.js 맨 위의 if (window.__typeuiStyleExtractorInstalled) return;는 같은 탭에서 여러 번 눌러도 리스너가 중복 등록되지 않게 막는 가드다. "모든 페이지에 항상 살아있는 코드" 대신 "필요할 때만 잠깐 들어갔다 나오는 코드"로 설계해 권한 범위와 성능 부담을 줄인 것이다.
일반적인 콘텐츠 스크립트는 모든 방에 상주하는 경비원과 같다. 반면 이 확장은 호출할 때만 잠깐 들어오는 검침원이다. 버튼을 누르는 순간에만 방(탭)에 들어와 계량기(스타일)를 확인하고, 이미 들어와 있으면("설치됨" 표시를 보고) 중복으로 들어오지 않는다.
content-script.js의 collectSampledElements(280)는 body → 제목(h1~h6) → p → a → button → 입력 요소 → label → nav/header/footer/main/section/article/aside → 리스트 항목 → 표 → [role='button'] → 클래스에 card/btn이 들어간 요소 → [tabindex] 순서로 셀렉터를 훑으며, isVisible()로 화면에 실제로 보이는 요소만 골라 최대 280개까지 채운다.
요소마다 window.getComputedStyle을 호출해 fontFamily/fontSize/lineHeight/fontWeight, color/backgroundColor/borderColor/outlineColor, 8방향 margin·padding, borderRadius, boxShadow, transitionDuration 등을 그대로 배열에 쌓는다. 동시에 collectSiteSignals()가 메타 태그(description·keywords·og:type)와 제목·내비게이션·CTA 문구, 그리고 document.body.innerText 앞부분 14,000자까지를 함께 담아 반환한다.
웹 컴포넌트(Shadow DOM)나 위 셀렉터 목록에 없는 커스텀 태그, 또는 페이지에 요소가 아주 많아 280개를 넘기는 경우 뒤쪽 디자인 토큰은 아예 표본에서 빠진다. "정확한 실측"이라는 이 도구의 강점은 표본이 닿는 범위 안에서만 성립한다.
lib/normalize.mjs의 normalizeExtractedStyles()는 원시 값을 그대로 쓰지 않는다. increment(map, key) 헬퍼로 각 값이 몇 번 등장했는지 Map에 누적하고, topEntries()로 등장 빈도가 높은 순서대로만 골라 SIZE_NAMES = ["xs","sm","md","lg","xl","2xl","3xl","4xl"], RADIUS_NAMES, TEXT_NAMES = ["primary","secondary","tertiary","inverse"] 같은 고정 이름 배열을 순서대로 붙인다. 즉 color.text.primary는 "가장 자주 쓰인 글자색"이라는 뜻이지, 실제 CSS의 --text-primary 변수와는 무관하다.
색상은 toCanonicalColor() → parseRgbLike() → rgbToHex() 경로로 rgb(17, 24, 39) 같은 값을 #111827 형태의 hex로 통일한다. 여백·radius는 parsePx()로, 트랜지션 시간은 parseDuration()으로 150ms/0.15s 표기를 하나의 숫자(ms)로 맞춘다. "많이 쓰인 값일수록 진짜 토큰일 가능성이 높다"는 빈도 = 신뢰도 가정이 이 모듈 전체를 관통한다.
inferSiteProfile()은 documentation/dashboard/marketing/content/ecommerce/webapp 6개 "제품 성격" 후보와 developer/operator/business/consumer/reader/general 6개 "대상 독자" 후보에 각각 점수를 매긴다. 점수 계산은 세 함수뿐이다.
keywordScore(corpus, keywords, weight, ...) — 제목·메타·헤딩·내비게이션·본문 텍스트를 합친 corpus 문자열에 "dashboard", "pricing", "checkout" 같은 키워드가 몇 번 포함되는지 세어 가중치를 곱한다.numericCue(value, threshold, weight, ...) — 폼 개수·표 개수·코드 블록 개수 같은 구조적 수치가 임계값을 넘으면 점수를 더한다.pathnameCue(pathname, fragments, ...) — URL 경로에 /docs, /api 같은 조각이 있으면 가산점을 준다.
pickTop()이 가장 높은 점수의 후보를 고르고, confidenceFromScores()가 "점수 ≥ 5이고 2등과 차이 ≥ 1.4면 high, 점수 ≥ 2.4이고 차이 ≥ 0.5면 medium, 아니면 low"라는 하드코딩된 문턱값으로 신뢰도 라벨을 매긴다. 여기엔 임베딩도, 분류 모델도, LLM 호출도 없다 — if/else급 점수표다.
저장소 루트에는 DESIGN.md라는 파일이 하나 더 있는데, 이건 확장이 만들어내는 결과물이 아니라 "SKILL.md를 어떻게 써야 하는지"를 규정한 메타 문서다. 여기 적힌 Mission/Brand/Style Foundations/Accessibility/Writing Tone/Rules: Do/Rules: Don't/Guideline Authoring Workflow/Required Output Structure/Component Rule Expectations/Quality Gates 11개 섹션 이름이, lib/generate-design-md.mjs·lib/generate-skill-md.mjs가 실제로 채워 넣는 섹션과 lib/validate.mjs의 REQUIRED_HEADINGS 배열에 토씨 하나 안 틀리고 그대로 등장한다. 즉 "설계도(DESIGN.md) → 생성 코드 → 검증 코드"가 같은 스펙을 세 번 반복하는 자기참조 구조다.
lib/validate.mjs의 validateMarkdownOutput()은 진짜 마크다운 파서를 쓰지 않는다. markdown.includes("## Mission")처럼 부분 문자열이 포함돼 있는지만 검사한다. REQUIRED_STATES = ["default","hover","focus-visible","active","disabled","loading","error"] 각 단어가 본문 어딘가에 등장하는지, "WCAG 2.2 AA" 문구가 있는지, "must"/"should" 단어가 쓰였는지도 같은 방식으로 확인한다.
순서가 뒤바뀌어 있어도, 섹션 제목만 어딘가에 존재하면 runCheck()는 통과로 친다. "필요한 단어가 문서 안에 있는지"만 볼 뿐 문맥이나 값의 타당성은 전혀 검사하지 않는다. 팝업의 경고 목록(issuesEl)이 비어 있다고 해서 생성된 토큰 값이 실제로 쓸만하다는 보장은 없다.
이 저장소에서 버전의 진실원(source of truth)은 manifest.json의 "version": "0.4.0"이다. 팝업 배지에 보이는 "v0.4.0" 텍스트는 popup/popup.html에 별도로 손으로 박아 넣은 문자열이라, 둘을 늘 맞춰줘야 한다.
| 구성 요소 | 내용 |
|---|---|
| 패키지 매니저 | 없음. 저장소 어디에도 package.json이 없다. npm/yarn/pnpm install 자체가 필요 없는 순수 정적 JS 확장 |
| 확장 형식 | Manifest V3(manifest_version: 3). 백그라운드는 service_worker + "type": "module"로 ES 모듈 임포트 지원 |
| 권한 | activeTab·scripting·storage·downloads 4개뿐. 항상 켜져 있는 백그라운드 권한(tabs 전체 등)은 요구하지 않음 |
| 프런트엔드 | 팝업은 프레임워크 없는 바닐라 HTML/CSS/JS(popup/popup.html·popup.css·popup.js). 번들러·트랜스파일러 없음 — 브라우저가 파일을 그대로 로드 |
| 핵심 로직 | lib/normalize.mjs·lib/generate-design-md.mjs·lib/generate-skill-md.mjs·lib/validate.mjs — 전부 브라우저 API에 의존하지 않는 순수 ES 모듈 |
| 테스트 | tests/run-tests.mjs가 node:assert/strict만으로 작성됨. 테스트 프레임워크(Jest/Vitest 등) 없이 node tests/run-tests.mjs로 직접 실행 |
| CI | .github/workflows 자체가 저장소에 없음 — GitHub Actions 등 자동 테스트 파이프라인 없음(로컬 실행 의존) |
| 아이콘 빌드 | scripts/generate-icons.sh가 macOS 전용 sips 명령으로 assets/logo.png를 16/32/48/128px 아이콘으로 리사이즈. 크로스플랫폼 빌드 스크립트가 아님 |
| 외부 서비스 연동 | 런타임 코드에는 없음. 다만 팝업 도움말·README가 typeui.sh/design-md를 참고 링크로 반복 인용(직접 fetch는 하지 않음) |
service_worker + type: module 조합으로 ES 모듈을 백그라운드에서 바로 import한다..js와 달리 별도 설정 없이 import 구문을 바로 쓸 수 있다.| 항목 | 내용 |
|---|---|
| 설치 방법 | chrome://extensions 접속 → 개발자 모드 켜기 → 압축해제된 확장 프로그램을 로드 → 이 저장소 폴더 선택. 이게 전부다(빌드·설치 명령 없음) |
| 브라우저 | Manifest V3를 지원하는 크로미움 계열(Chrome·Edge·Brave 등). Firefox는 MV3 구조가 달라 그대로는 동작하지 않음 |
| Node.js | 확장 실행 자체엔 불필요. 로컬 테스트(node tests/run-tests.mjs)를 돌릴 때만 Node가 필요 |
| Quick install 버튼 | window.showDirectoryPicker(File System Access API)로 프로젝트 폴더에 직접 SKILL.md를 쓴다. 이 API가 없는 환경에서는 클립보드 복사 + 다운로드로 자동 대체(fallback) |
| 사용 불가 페이지 | service-worker.js의 getActiveTab()이 chrome:// 페이지에서는 명시적으로 에러를 던짐 — 브라우저 내부 설정 화면에서는 추출이 안 됨 |
npm 의존성이 0개이므로 "버전 충돌", "취약점 스캔에 걸리는 패키지" 같은 걱정이 원천적으로 없다. 코드를 그대로 읽고 바로 로드해 쓸 수 있다는 점은 이 저장소의 가장 큰 실무적 장점이다.
토큰을 뽑고 싶은 페이지를 열어둔 채 확장 아이콘을 누르면 popup.js의 init()이 실행된다. chrome.storage.local에서 지난번에 쓰던 모드(outputMode)를 불러와 그대로 이어서 runExtraction()을 자동 실행한다.
팝업 상단의 두 버튼으로 사람이 읽을 DESIGN.md와 에이전트가 바로 읽을 SKILL.md를 오갈 수 있다. 모드를 바꾸면 state.mode가 갱신되고 즉시 runExtraction()이 다시 돌아간다.
service-worker.js가 injectExtractor()로 content-script.js를 주입하고, requestExtractionPayload()로 원시 스타일 데이터를 받아, normalizeExtractedStyles() → generateDesignMarkdown()/generateSkillMarkdown() → validateMarkdownOutput() 순으로 처리한 뒤 팝업으로 되돌려준다.
결과 마크다운이 <textarea id="preview">에 그대로 채워진다(실제 파일에는 이스케이프된 형태로 존재). validation.errors/warnings가 있으면 목록(issuesEl)으로 뜬다. 앞서 s5에서 봤듯 이 검증은 문자열 포함 여부만 확인한다는 점을 기억해 둘 것.
Copy는 클립보드에, Download는 data:text/markdown URL로 파일 저장 대화상자를 띄운다. Quick install은 Claude Code/Codex/Cursor 버튼 중 하나를 누르면 폴더 선택창을 띄워 .claude/skills/design-system/SKILL.md(또는 .agents/…, .cursor/…) 경로에 바로 파일을 써 넣는다.
물음표 버튼을 누르면 renderGenerationExplanation()이 "이번 실행에서 샘플링된 요소 수", "토큰 개수", "브랜드 추론 신뢰도(high/medium/low)와 근거 키워드"를 실제 숫자로 보여준다. 결과를 그냥 믿기 전에 이 패널로 신뢰도를 먼저 확인하는 습관을 들이면 좋다.
팝업 UI에 세 도구 로고가 나란히 붙어 있어 "AI가 이 사이트를 분석해준다"고 오해하기 쉽다. 실제로는 fetch로 외부 API를 부르는 코드가 단 한 줄도 없다. 색·간격 추출은 getComputedStyle 실측, 브랜드 추론은 lib/normalize.mjs의 키워드 스코어링이 전부다. "AI 확장"이 아니라 "AI에게 넘겨줄 스펙을 만드는 확장"이다.
popup/popup.html의 GitHub 배지는 https://github.com/bergside/design-md-extractor-chrome("-extractor"가 붙은 이름)로 연결되지만, 실제로 이 문서가 분석한 저장소 주소는 bergside/design-md-chrome이다. 저장소 이름이 바뀌었거나 리네이밍되면서 팝업 안 링크가 갱신되지 않은 문서-코드 불일치 사례다.
lib/validate.mjs는 마크다운을 파싱하지 않고 markdown.includes(...)로만 검사한다. 섹션 순서가 뒤죽박죽이어도, 심지어 본문 어딘가 엉뚱한 문맥에 "disabled" 같은 단어만 있어도 검사는 통과로 뜬다. 경고 목록이 비어 있다고 해서 토큰 값 자체의 품질까지 보장되는 건 아니다.
collectSampledElements(280)의 한도와 고정 셀렉터 목록 밖에 있는 요소(예: Shadow DOM 안의 커스텀 컴포넌트)는 추출 대상에서 빠진다. 복잡한 페이지일수록 "실제로 뽑힌 토큰"이 그 사이트 디자인의 일부만 대표할 수 있다.
버전의 진실원은 manifest.json이지만 popup/popup.html의 "v0.4.0" 배지 텍스트는 손으로 따로 박아둔 값이라 둘이 어긋날 수 있다. 또한 scripts/generate-icons.sh는 macOS 전용 sips 명령을 쓰므로 Linux/Windows 개발 환경에서는 그대로 실행되지 않는다.
| 방법 | 이 확장과 비교 |
|---|---|
| 브라우저 DevTools 수동 복사 | 정확하지만 요소 하나하나 값을 손으로 옮겨 적어야 한다. 이 확장은 280개 요소를 자동으로 훑고 빈도순으로 정리해 시간을 크게 절약하지만, 사람의 판단(어떤 값이 "진짜 토큰"인지)을 빈도 집계로 대신한다. |
| Figma Dev Mode / Tokens Studio | 디자이너가 만든 원본 디자인 파일이 있어야 동작한다. 이 확장은 디자인 파일 없이 이미 배포된 실제 웹사이트에서 바로 토큰을 뽑을 수 있어, 경쟁사 사이트 분석이나 레퍼런스 벤치마킹에 더 적합하다. |
| 스크린샷을 AI에게 보여주고 "느낌 설명해줘" | 빠르지만 색상 hex·정확한 px 값은 매번 다르게 추측되는(hallucination) 문제가 있다. 이 확장은 getComputedStyle 실측값을 쓰므로 색·간격 부분만큼은 결정적(deterministic)이다 — 다만 브랜드 성격 추론은 여전히 휴리스틱 추측이라는 점은 동일한 한계. |
| SKILL.md를 손으로 처음부터 작성 | 정확하지만 시작이 느리다. 이 확장은 실제 사이트에서 뽑은 초안을 자동으로 만들어주므로, 빈 문서가 아니라 "고쳐 쓸 초안"에서 시작하게 해준다. |
| typeui.sh 웹서비스(같은 회사) | 이 확장이 참조하는 typeui.sh/design-md·typeui.sh/design-skills는 Bergside의 별도 웹 서비스다. 확장은 그 스펙을 로컬에서 무료로 실행하는 클라이언트 역할이며, 웹사이트에 접속하지 않고도 같은 규격의 문서를 만들어낸다. |
design-md-chrome은 크지 않은 저장소지만(package.json도 없는 23개 파일) 목적이 뚜렷하다. 임의의 웹사이트에서 실제 CSS 값을 실측해, AI 코딩 에이전트가 곧바로 참고할 수 있는 마크다운 스펙으로 정리해 준다. 색상·간격 같은 "잴 수 있는 값"은 정확하지만, 브랜드 성격 같은 "해석이 필요한 값"은 여전히 키워드 기반 추측이라는 이중적 성격을 정확히 이해하고 쓰는 게 핵심이다.
· "이 사이트 느낌으로 만들어줘"를 자주 요청받는 프런트엔드 개발자·1인 개발자
· Claude Code/Codex/Cursor에 물려줄 SKILL.md 초안을 빠르게 확보하고 싶은 사람
· npm 의존성 없는 가벼운 크롬 확장의 실제 구조(온디맨드 주입·메시지 패싱·순수 함수 분리)를 공부하고 싶은 초보 개발자
Audience·Product surface 항목은 키워드 스코어링 결과일 뿐이다. 실제 서비스 문서로 쓸 거라면 색상·간격 등 실측 토큰 표만 신뢰하고, 브랜드 설명 문단은 반드시 사람이 다시 검토·수정해야 한다.