From cd58dcaece646859ccbd581b5093a44ca0fc8db2 Mon Sep 17 00:00:00 2001 From: Manav Arya Singh <144777973+Manavarya09@users.noreply.github.com> Date: Sun, 26 Apr 2026 21:55:00 +0400 Subject: [PATCH] feat(v12): chat REPL + permalinks + gallery + spec + distribution prep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "best in the game" release: takes designlang from one-shot extractor to iterative + shareable + standardized + distributed. ITERATIVE — designlang chat - src/chat.js: REPL over a live extraction. Heuristic-only router parses natural-ish English ("make it brutalist", "primary #ff4800", "sharpen radii", "dark mode") into structured operations on the design object. Re-derives DTCG / Tailwind / CSS vars / DESIGN.md on `save`. Loads from URL or existing *-design-tokens.json. - bin: new `chat ` subcommand. ~360 lines, zero new deps. SHAREABLE — permalinks - website/lib/cache.js: getCachedByHash + listRecent helpers over the existing Blob cache (24h TTL), no new infra. - website/lib/build-files.js: shared file-builder used by both /api/extract (streaming) and /x/[hash] (permalink page). - website/app/x/[hash]/page.js + PermalinkViewer.js: every cached extraction is now a stable URL. Stat strip + Copy permalink CTA + the same 12-tab ResultViewer. - /api/extract emits a `permalink` event up front; HeroExtractor rewrites the URL bar via history.replaceState so refresh-and-share works mid-stream. - website/app/gallery/page.js: public directory of recent extractions, palette-strip cards, relative timestamps, intent + material + library signals. ISR-cached every 10 min. STANDARDIZED — DESIGN.md spec - website/app/spec/page.js: formal spec doc. Eight canonical sections, YAML front-matter schema, reference implementation pointer, CC BY 4.0 license, credit to design-extractor.com for pioneering the format. - website/public/badge.svg: embeddable Verified badge (paper + ink + accent). - Sitemap + robots updated with /spec, /gallery. DISTRIBUTED — submission playbook - marketplace/SUBMISSION-PLAYBOOK.md: ordered list of 6 marketplaces with click-time estimates and review-queue waits. - marketplace/{figma,cursor,vscode,claude-code-skill,raycast,chrome}-listing.md: pre-written listing copy, manifests, screenshot briefs, tweet drafts for each. Click "submit" tomorrow morning; nothing left to write. Home nav: Gallery + Spec links added; v11 → v12 label. Tests: 317/317 pass. --- bin/design-extract.js | 15 + marketplace/SUBMISSION-PLAYBOOK.md | 95 +++++++ marketplace/chrome-listing.md | 45 +++ marketplace/claude-code-skill.md | 52 ++++ marketplace/cursor-listing.md | 55 ++++ marketplace/figma-listing.md | 50 ++++ marketplace/raycast-listing.md | 38 +++ marketplace/vscode-listing.md | 46 +++ src/chat.js | 356 ++++++++++++++++++++++++ website/app/api/extract/route.js | 99 +------ website/app/components/HeroExtractor.js | 9 + website/app/gallery/page.js | 127 +++++++++ website/app/page.js | 5 +- website/app/sitemap.js | 8 +- website/app/spec/page.js | 157 +++++++++++ website/app/x/[hash]/PermalinkViewer.js | 126 +++++++++ website/app/x/[hash]/page.js | 45 +++ website/lib/build-files.js | 84 ++++++ website/lib/cache.js | 61 ++++ website/public/badge.svg | 9 + 20 files changed, 1387 insertions(+), 95 deletions(-) create mode 100644 marketplace/SUBMISSION-PLAYBOOK.md create mode 100644 marketplace/chrome-listing.md create mode 100644 marketplace/claude-code-skill.md create mode 100644 marketplace/cursor-listing.md create mode 100644 marketplace/figma-listing.md create mode 100644 marketplace/raycast-listing.md create mode 100644 marketplace/vscode-listing.md create mode 100644 src/chat.js create mode 100644 website/app/gallery/page.js create mode 100644 website/app/spec/page.js create mode 100644 website/app/x/[hash]/PermalinkViewer.js create mode 100644 website/app/x/[hash]/page.js create mode 100644 website/lib/build-files.js create mode 100644 website/public/badge.svg diff --git a/bin/design-extract.js b/bin/design-extract.js index 3ac1be4..64fc5da 100755 --- a/bin/design-extract.js +++ b/bin/design-extract.js @@ -1085,6 +1085,21 @@ program } }); +// ── Chat — REPL over a live extraction (v12) ────────────── +program + .command('chat ') + .description('Interactive REPL over an extraction. is either a URL or a path to an existing *-design-tokens.json file.') + .option('-o, --out ', 'output directory for `save`', './chat-output') + .action(async (target, opts) => { + try { + const { runChat } = await import('../src/chat.js'); + await runChat(target, opts); + } catch (err) { + console.error(chalk.red(`\n ${err.message}\n`)); + process.exit(1); + } + }); + // ── Replay — record a short WebM of motion from a URL ───── program .command('replay ') diff --git a/marketplace/SUBMISSION-PLAYBOOK.md b/marketplace/SUBMISSION-PLAYBOOK.md new file mode 100644 index 0000000..9ce4535 --- /dev/null +++ b/marketplace/SUBMISSION-PLAYBOOK.md @@ -0,0 +1,95 @@ +# designlang — Distribution Submission Playbook + +Submit each marketplace below. All listing copy + manifests are pre-written. +Review queues take 1–7 days; this playbook is everything you need to click "submit". + +--- + +## 1. Figma Community (`figma-plugin/`) + +**Goal:** publicly listed in the Figma Community plugin store. + +**Steps:** +1. Open Figma Desktop → Community → Publish a plugin. +2. Upload from local: `figma-plugin/`. +3. Use the title, tagline, description, tags, and cover-image brief from `marketplace/figma-listing.md`. +4. Screenshots: take 4 from the live extraction studio. +5. Submit. Review queue: 3–7 days. + +--- + +## 2. Cursor — Custom Tool listing + +**Goal:** Cursor users can `Cmd+Shift+P → designlang: Extract` from any URL. + +**Steps:** +1. Read `marketplace/cursor-listing.md`. +2. The provided `mcp.json` snippet is what users paste into `~/.cursor/mcp.json`. +3. We don't submit to Cursor's marketplace directly (no formal review process yet); + the MCP integration is documented and self-served. Promotion = blog post + tweet. + +--- + +## 3. VS Code Marketplace (`vscode-extension/`) + +**Goal:** publicly listed at marketplace.visualstudio.com/items?itemName=designlang. + +**Steps:** +1. Install `vsce` once: `npm install -g @vscode/vsce`. +2. From `vscode-extension/`: `vsce package` → produces `.vsix`. +3. Create publisher account: . +4. `vsce publish` (or upload the `.vsix` via the dashboard). +5. Use the listing copy from `marketplace/vscode-listing.md`. +6. Review queue: ~24h. + +--- + +## 4. Claude Code Skill registry + +**Goal:** designlang appears in the official Claude Code skills list. + +**Steps:** +1. Read `marketplace/claude-code-skill.md` — it contains the SKILL.md the registry expects. +2. The SKILL.md is already emitted by `designlang --emit-agent-rules` at + `.claude/skills/designlang/SKILL.md`. We submit a copy at the project root for discovery. +3. Open a PR against (or the + then-current registry repo) — title: "Add: designlang skill". + +--- + +## 5. Raycast Store (`raycast-extension/`) + +**Goal:** designlang as a one-keystroke Raycast command. + +**Steps:** +1. Read `marketplace/raycast-listing.md`. +2. From `raycast-extension/`: follow Raycast's contribution guide + . +3. PR to . +4. Review queue: 5–14 days. + +--- + +## 6. Chrome Web Store (`chrome-extension/`) + +**Goal:** one-click extract from any tab via the Chrome toolbar. + +**Steps:** +1. Read `marketplace/chrome-listing.md`. +2. Zip `chrome-extension/`: `cd chrome-extension && zip -r ../designlang-chrome.zip .`. +3. Upload at . ($5 one-time dev fee.) +4. Review queue: 1–3 days. + +--- + +## Sequencing + +Do them in this order — easiest payoff first, most setup last: +1. **Cursor MCP** (no review, just docs + tweet — ship today). +2. **Chrome Web Store** ($5 fee, 1–3 day review — submit today). +3. **VS Code Marketplace** (free, ~24h review — submit today). +4. **Figma Community** (free, 3–7 day review — submit today). +5. **Claude Code Skills registry** (PR-based, async — submit this week). +6. **Raycast Store** (PR-based, 5–14 day review — submit this week). + +Total click-time across all six: ~90 minutes. The wall-clock for everything to be live: ~2 weeks. diff --git a/marketplace/chrome-listing.md b/marketplace/chrome-listing.md new file mode 100644 index 0000000..01293d6 --- /dev/null +++ b/marketplace/chrome-listing.md @@ -0,0 +1,45 @@ +# Chrome Web Store — designlang extension + +## Name + +designlang — Extract design system from this tab + +## Summary (max 132 chars) + +One click to extract any website's design system: tokens, typography, spacing, components, voice. $0, no account, MIT. + +## Description (long) + +designlang lives in your Chrome toolbar. Click the icon on any tab; the active URL is sent to designlang.app, which runs a Playwright extraction and opens the result inline as tabs (DESIGN.md, DTCG tokens, Tailwind, CSS variables, Figma variables, and 7 more). + +Permissions: +• `activeTab` — read the URL of the current tab. Nothing else. + +Use cases: +• Read a competitor's design tokens in 5 seconds +• Pull a Tailwind config off any production site +• Generate a DESIGN.md for AI coding agents (Claude Code, Cursor, Windsurf) +• One-click copy hex / radius / shadow values +• Import a Figma Variable collection from any URL + +Open source: https://github.com/Manavarya09/design-extract + +## Category + +Developer Tools + +## Languages + +English + +## Screenshots (4 — 1280x800) + +1. Browser tab on a real site (e.g. Stripe), with the designlang toolbar icon highlighted. +2. The same tab after clicking — designlang.app opens with extraction in progress (token paint streaming). +3. The result viewer with the DESIGN.md tab active, source code visible, copy/download buttons. +4. The result viewer on the Tailwind config tab, showing extracted palette + scale. + +## Submission + +ZIP the `chrome-extension/` directory, upload at . +$5 one-time developer fee. diff --git a/marketplace/claude-code-skill.md b/marketplace/claude-code-skill.md new file mode 100644 index 0000000..ad26a15 --- /dev/null +++ b/marketplace/claude-code-skill.md @@ -0,0 +1,52 @@ +# Claude Code Skills registry — designlang + +## Skill name + +designlang + +## SKILL.md (the file the registry expects) + +```markdown +--- +name: designlang +description: Reverse-engineer any website into a complete design system. Outputs DTCG W3C tokens, motion, anatomy, voice, page intent, material language, plus a single agent-native DESIGN.md and a one-command Next.js clone. Use when the user wants to extract, mirror, or compare design tokens against a live URL. +when_to_use: | + Use this skill when the user wants to: + - Extract design tokens, palette, typography, or shadows from a public website + - Generate a DESIGN.md for an existing site (agent-native single-file artifact) + - Clone a website's design as a runnable Next.js starter + - Compare local tokens against a deployed site (drift detection) + - Import a website's tokens into Figma, Tailwind, iOS, Android, Flutter, or WordPress +arguments: + - name: url + description: Public URL of the site to extract from. + required: true +--- + +# designlang skill + +`npx designlang ` runs a Playwright extraction and writes ~25 files +(DTCG tokens, Tailwind, CSS vars, Figma vars, motion, anatomy, voice, +intent, material, library detection, prompt pack for v0/Lovable/Cursor, +plus the single-file `DESIGN.md`). + +Sub-commands: +- `designlang clone ` — generates a working Next.js repo from the extraction +- `designlang ci --tokens ./tokens.json` — drift bot, writes a PR-comment markdown +- `designlang studio` — local web studio over the latest extraction +- `designlang chat ` — REPL with mutations (sharpen / soften / dark / brutalist / glass / swap-color / swap-font) +- `designlang mcp` — stdio MCP server + +For agent rules and ready-to-paste prompts, run with `--emit-agent-rules` or read +the `*-prompts/` directory. + +The full spec for `DESIGN.md` is at . +``` + +## Submission flow + +1. Fork (or the then-current registry repo). +2. Add `skills/designlang/SKILL.md` with the content above. +3. PR title: `Add: designlang skill`. +4. PR body: link to , describe the surface, mention 1.6k stars + 5K npm downloads. +5. Tag the PR with `skill` and `community`. diff --git a/marketplace/cursor-listing.md b/marketplace/cursor-listing.md new file mode 100644 index 0000000..b12a092 --- /dev/null +++ b/marketplace/cursor-listing.md @@ -0,0 +1,55 @@ +# Cursor — designlang MCP integration + +## What it gives the user + +Inside Cursor (and Windsurf, and Claude Desktop), `designlang` exposes the **last extraction** as a live MCP resource. Cursor can read tokens, regions, components, and CSS health without re-extracting, and call MCP tools to refine the design (`designlang chat` ops are wired here). + +## Install (paste into `~/.cursor/mcp.json`) + +```json +{ + "mcpServers": { + "designlang": { + "command": "npx", + "args": ["-y", "designlang", "mcp", "--output-dir", "./design-extract-output"] + } + } +} +``` + +## What Cursor sees + +Resources: +- `designlang://latest/tokens` — DTCG W3C token tree of the most recent extraction +- `designlang://latest/regions` — semantic region map (nav, hero, pricing, …) +- `designlang://latest/components` — component clusters with variant/size/state +- `designlang://latest/css-health` — specificity graph + unused-CSS report +- `designlang://latest/design-md` — the agent-native single-file artifact + +Tools: +- `designlang_extract` — paste a URL, get tokens +- `designlang_chat` — apply mutations (sharpen / soften / dark / brutalist / glass / swap-color / swap-font) +- `designlang_clone` — generate a working Next.js repo from a URL +- `designlang_drift` — compare local tokens against a live URL + +## Tweet thread for launch + +``` +1/ Cursor + designlang. Paste any URL inside Cursor and it reads the live design system as MCP resources. + +`~/.cursor/mcp.json`: +{ + "mcpServers": { + "designlang": { + "command": "npx", + "args": ["-y", "designlang", "mcp"] + } + } +} + +2/ Then ask: "Refactor my + + Extract another → + + + + {/* Stat strip */} +
+ + + + + +
+ + + + + ); +} + +function Numeral({ value, label }) { + return ( +
+
+ {value} +
+
+ {label} +
+
+ ); +} diff --git a/website/app/x/[hash]/page.js b/website/app/x/[hash]/page.js new file mode 100644 index 0000000..16ebc80 --- /dev/null +++ b/website/app/x/[hash]/page.js @@ -0,0 +1,45 @@ +import { notFound } from 'next/navigation'; +import { getCachedByHash } from '../../../lib/cache'; +import { buildFiles, buildSummary } from '../../../lib/build-files'; +import PermalinkViewer from './PermalinkViewer'; + +export const dynamic = 'force-dynamic'; +export const runtime = 'nodejs'; + +export async function generateMetadata({ params }) { + const { hash } = await params; + const cached = await getCachedByHash(hash); + if (!cached) { + return { + title: 'Extraction not found — designlang', + description: 'This designlang permalink has expired or never existed. Run a fresh extraction at designlang.app.', + }; + } + const { design } = cached; + const url = design.meta?.url || ''; + const title = design.meta?.title || url; + const palette = (design.colors?.all || []).slice(0, 5).map((c) => c.hex).join(' · '); + return { + title: `${title} — design system extracted by designlang`, + description: `${url} · ${design.colors?.all?.length ?? 0} colors · ${design.typography?.families?.[0]?.name || 'system'} · ${design.materialLanguage?.label || 'flat'} material · palette ${palette}.`, + alternates: { canonical: `https://designlang.app/x/${hash}` }, + openGraph: { + title: `${title} — designlang`, + description: `Design system extracted from ${url}.`, + url: `https://designlang.app/x/${hash}`, + }, + }; +} + +export default async function PermalinkPage({ params }) { + const { hash } = await params; + const cached = await getCachedByHash(hash); + if (!cached) notFound(); + + const { design } = cached; + const url = design.meta?.url || ''; + const { files } = buildFiles(design, url); + const summary = buildSummary(design); + + return ; +} diff --git a/website/lib/build-files.js b/website/lib/build-files.js new file mode 100644 index 0000000..4ec26d2 --- /dev/null +++ b/website/lib/build-files.js @@ -0,0 +1,84 @@ +// Shared file-builder used by /api/extract (streaming) and /x/[hash] +// (permalink page). Pure function over a `design` object. + +import { formatMarkdown } from '../../src/formatters/markdown.js'; +import { formatDesignMd } from '../../src/formatters/design-md.js'; +import { formatTailwind } from '../../src/formatters/tailwind.js'; +import { formatCssVars } from '../../src/formatters/css-vars.js'; +import { formatPreview } from '../../src/formatters/preview.js'; +import { formatFigma } from '../../src/formatters/figma.js'; +import { formatReactTheme, formatShadcnTheme } from '../../src/formatters/theme.js'; +import { formatWordPress, formatWordPressTheme } from '../../src/formatters/wordpress.js'; +import { formatDtcgTokens } from '../../src/formatters/dtcg-tokens.js'; +import { formatIosSwiftUI } from '../../src/formatters/ios-swiftui.js'; +import { formatAndroidCompose } from '../../src/formatters/android-compose.js'; +import { formatFlutterDart } from '../../src/formatters/flutter-dart.js'; +import { formatAgentRules } from '../../src/formatters/agent-rules.js'; +import { nameFromUrl } from '../../src/utils.js'; + +export function buildFiles(design, targetUrl) { + const prefix = nameFromUrl(targetUrl); + const dtcg = formatDtcgTokens(design); + const dtcgJson = JSON.stringify(dtcg, null, 2); + + const files = { + [`${prefix}-DESIGN.md`]: formatDesignMd(design), + [`${prefix}-design-language.md`]: formatMarkdown(design), + [`${prefix}-design-tokens.json`]: dtcgJson, + [`${prefix}-tailwind.config.js`]: formatTailwind(design), + [`${prefix}-variables.css`]: formatCssVars(design), + [`${prefix}-preview.html`]: formatPreview(design), + [`${prefix}-figma-variables.json`]: formatFigma(design), + [`${prefix}-theme.js`]: formatReactTheme(design), + [`${prefix}-shadcn-theme.css`]: formatShadcnTheme(design), + [`${prefix}-wordpress-theme.json`]: formatWordPress(design), + }; + + files[`${prefix}-mcp.json`] = JSON.stringify({ + colors: { all: design.colors?.all || [] }, + regions: design.regions || [], + componentClusters: design.componentClusters || [], + accessibility: { remediation: design.accessibility?.remediation || [] }, + cssHealth: design.cssHealth || null, + }, null, 2); + + files['ios/DesignTokens.swift'] = formatIosSwiftUI(dtcg); + + const android = formatAndroidCompose(dtcg); + for (const name of Object.keys(android)) { + files[`android/${name}`] = android[name]; + } + + files['flutter/design_tokens.dart'] = formatFlutterDart(dtcg); + + const wpTheme = formatWordPressTheme(dtcg, design); + for (const name of Object.keys(wpTheme)) { + files[`wordpress-theme/${name}`] = wpTheme[name]; + } + + const agentFiles = formatAgentRules({ design, tokens: dtcg, url: targetUrl }); + for (const name of Object.keys(agentFiles)) { + files[name] = agentFiles[name]; + } + + return { files, dtcg, prefix }; +} + +export function buildSummary(design) { + return { + url: design.meta?.url, + title: design.meta?.title, + colors: design.colors?.all?.length ?? 0, + colorList: (design.colors?.all || []).slice(0, 20).map((c) => c.hex), + fonts: design.typography?.families?.map((f) => f.name).join(', ') || 'none detected', + spacingCount: design.spacing?.scale?.length ?? 0, + spacingBase: design.spacing?.base ?? null, + shadowCount: design.shadows?.values?.length ?? 0, + radiiCount: design.borders?.radii?.length ?? 0, + componentCount: Object.keys(design.components || {}).length, + cssVarCount: Object.values(design.variables || {}).reduce((s, v) => s + Object.keys(v).length, 0), + a11yScore: design.accessibility?.score ?? null, + a11yFailCount: design.accessibility?.failCount ?? 0, + score: design.score, + }; +} diff --git a/website/lib/cache.js b/website/lib/cache.js index 9615d5c..77d7f12 100644 --- a/website/lib/cache.js +++ b/website/lib/cache.js @@ -71,3 +71,64 @@ export async function putCached(key, { design }) { console.error('[cache] write failed', err?.message); } } + +// ─── Public permalink helpers — used by /x/[hash] and /gallery ─── +// +// Permalinks are forever (well, 24h while the Blob entry exists). The page +// re-derives files from the cached `design` object on each request — same +// pipeline the streaming /api/extract uses on a cache hit. + +export async function getCachedByHash(hash) { + if (!/^[a-f0-9]{64}$/.test(hash)) return null; + const cached = await getCached(hash); + return cached; +} + +export async function listRecent(limit = 24) { + if (!hasBlob()) return []; + try { + const { list } = await import('@vercel/blob'); + // Pull more than we need so we can drop expired ones. + const result = await list({ prefix: 'extract-cache/', limit: limit * 2 }); + const entries = await Promise.all( + (result.blobs || []) + .sort((a, b) => new Date(b.uploadedAt).getTime() - new Date(a.uploadedAt).getTime()) + .slice(0, limit * 2) + .map(async (blob) => { + try { + const m = blob.pathname.match(/^extract-cache\/([a-f0-9]{64})\.json$/); + if (!m) return null; + const res = await fetch(blob.url, { cache: 'no-store' }); + if (!res.ok) return null; + const payload = await res.json(); + const generatedAt = typeof payload?.generatedAt === 'number' ? payload.generatedAt : 0; + if (Date.now() - generatedAt > TTL_MS) return null; + const design = payload?.design; + if (!design) return null; + return { + hash: m[1], + url: design.meta?.url || '', + title: design.meta?.title || '', + generatedAt, + colors: design.colors?.all?.length ?? 0, + primary: design.colors?.primary?.hex || null, + secondary: design.colors?.secondary?.hex || null, + accent: design.colors?.accent?.hex || null, + foreground: design.colors?.text?.[0] || null, + background: design.colors?.backgrounds?.[0] || null, + fontFamily: design.typography?.families?.[0]?.name || null, + intent: design.pageIntent?.type || null, + material: design.materialLanguage?.label || null, + library: design.componentLibrary?.library || null, + score: design.score?.overall ?? null, + grade: design.score?.grade ?? null, + }; + } catch { return null; } + }) + ); + return entries.filter(Boolean).slice(0, limit); + } catch (err) { + console.error('[cache] listRecent failed', err?.message); + return []; + } +} diff --git a/website/public/badge.svg b/website/public/badge.svg new file mode 100644 index 0000000..25eb3f5 --- /dev/null +++ b/website/public/badge.svg @@ -0,0 +1,9 @@ + + DESIGN.md verified + + + + DESIGN.md + VERIFIED + +