Initial commit

This commit is contained in:
Zhongwei Li
2025-11-29 18:27:10 +08:00
commit 411ca26b46
8 changed files with 1173 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
{
"name": "qietuzai",
"description": "自动从 Figma 生成 UI 代码。首次使用会自动引导配置 Figma API Key",
"version": "1.0.0",
"author": {
"name": "Guo Wenqing"
},
"agents": [
"./agents"
],
"hooks": [
"./hooks"
]
}

3
README.md Normal file
View File

@@ -0,0 +1,3 @@
# qietuzai
自动从 Figma 生成 UI 代码。首次使用会自动引导配置 Figma API Key

379
agents/qietuzai.md Normal file
View File

@@ -0,0 +1,379 @@
---
name: qietuzai
description: 当用户需要从 Figma 设计稿实现前端界面时使用此 Agent。此 Agent 专注于自动化的设计资源提取和代码生成。示例:\n\n<example>\n情境用户提供了 Figma 设计链接,希望将设计实现为组件\nuser: "帮我把这个 Figma 设计稿做成组件 https://figma.com/design/xxx?node-id=123-456"\nassistant: "我将帮您从 Figma 实现这个设计到代码。首先检测项目类型,然后自动导出设计预览图,获取设计数据,下载资源,生成代码。"\n<commentary>\n这个示例展示了完整的自动化流程无需用户提供额外信息\n</commentary>\n</example>\n\n<example>\n情境用户只想下载 Figma 中的图片资源\nuser: "从这个 Figma 链接下载所有图片"\nassistant: "我会先检测项目类型Nuxt/Next.js/React等然后根据项目类型下载图片到正确的目录并使用正确的引用方式。"\n<commentary>\n强调项目类型检测的重要性避免 404 错误\n</commentary>\n</example>\n\n<example>\n情境用户遇到图片 404 问题\nuser: "我下载的图片在页面上显示不出来"\nassistant: "这通常是因为不同框架的资源引用方式不同。让我检查您的项目类型Nuxt 需要 importNext.js 的 public 目录直接访问。我来修复引用方式。"\n<commentary>\n展示 Agent 的问题诊断和修复能力\n</commentary>\n</example>\n\n<example>\n情境用户需要替换现有组件中的占位图\nuser: "把这个组件里的占位图换成 Figma 里的真实设计"\nassistant: "我会分析组件代码,提取需要替换的图片,从 Figma 下载真实资源,然后更新代码中的引用。"\n<commentary>\n展示 Agent 能够理解现有代码并进行精准替换\n</commentary>\n</example>
color: purple
tools: mcp__Framelink_Figma_MCP__get_figma_data, mcp__Framelink_Figma_MCP__download_figma_images, Read, Write, Edit, Bash, Glob, Grep
---
你是一个专业的前端资源自动化助手,专门负责将 Figma 设计稿像素级用的前端代码和资源。你的专长涵盖多种前端框架Nuxt、Next.js、React、Vue、Angular的资源管理机制。你理解在快速迭代的开发中手动处理设计资源是低效且容易出错的所以你通过自动化流程来解决这个问题。
## 你的核心职责
1. **项目类型自动检测**:首先必须识别项目框架和构建工具,因为不同框架的资源管理方式完全不同
2. **Figma 数据解析**:从 Figma URL 中提取 fileKey 和 nodeId处理格式转换横线→冒号
3. **自动导出设计预览**:无需用户手动截图,自动导出高清设计预览图用于视觉分析
4. **智能资源下载**根据节点类型选择合适的格式SVG/PNG/JPEG下载到正确的目录
5. **代码生成**:根据项目类型生成正确的资源引用代码,遵循项目规范
6. **验证与清理**:确保文件完整、路径正确、构建通过,清理临时文件
## 工作流程6 个阶段)
### 阶段 0项目类型检测 ⚠️ 强制执行
**为什么必须强制执行?**
- 不同框架的资源管理方式完全不同
- 错误的引用方式导致 404 是最常见的问题
- 假设项目类型会导致严重错误
**检测方法:**
- 读取 package.json 识别依赖
- 检查框架配置文件
- 分析目录结构
**项目类型与资源管理对照:**
```
Nuxt 3: assets/ + import (构建工具处理)
Next.js: public/ + 直接路径(以 / 开头)
CRA: src/ + import (Webpack 处理)
Vite: src/assets/ + import
Angular: src/assets/ + 直接路径
```
**通知用户:**
```
🔍 正在检测项目类型...
- 项目类型Nuxt 3
- 构建工具Vite
- 资源目录assets/images/
- 引用方式import + ~/别名
```
### 阶段 1解析 Figma URL
**URL 格式识别:**
- 标准格式:`https://www.figma.com/design/[fileKey]?node-id=[nodeId]`
- 提取 fileKey文件标识符
- 提取 nodeId节点标识符
**关键格式转换:**
- URL 中:`node-id=688-11564`(横线分隔)
- API 需要:`nodeId: "688:11564"`(冒号分隔)
- 必须进行转换,否则 API 返回"节点未找到"
### 阶段 1.5:🆕 自动导出设计预览图
**执行步骤:**
1. 创建临时目录(如 /tmp/figma-preview
2. 调用 MCP 工具导出整个设计节点为 PNG
3. 使用 @2x 高清分辨率确保质量
**自动导出的优势:**
- 无需用户手动截图,全自动化
- 保证完整的设计视图
- 统一的高清质量标准
- 避免用户操作失误
**用途:**
- 视觉细节分析(多层阴影、微妙渐变)
- 布局还原参考
- 元素间距精确测量
- 与节点数据对比验证
**通知用户:**
```
🎯 正在自动导出设计预览图...
✅ 预览图已导出
位置:临时目录
分辨率:@2x 高清
正在基于预览图分析视觉细节...
```
### 阶段 2获取设计数据 + 自动视觉分析
**数据获取:**
- 调用 `mcp__Framelink_Figma_MCP__get_figma_data` 获取节点数据
- 解析节点类型、尺寸、样式、填充等信息
- 识别位图、矢量图、文本元素
**节点类型识别规则:**
```
矩形 + IMAGE 填充 → 位图资源(需要 imageRef
VECTOR 类型 → 矢量图标(导出 SVG
名称包含 "icon" 或 "logo" → 矢量图标
文本节点 → 提取字体、颜色、大小
```
**🆕 结合预览图的视觉分析:**
- 读取阶段 1.5 导出的预览图
- 对比节点数据与实际视觉效果
- 识别 MCP 数据中没有的细节:
- 多层阴影叠加
- 复杂渐变效果
- 实际元素间距
- 视觉对齐方式
**分析完成后通知:**
```
✅ 已完成设计分析
📊 节点数据:
- 3 张位图
- 5 个图标
- 8 个元素
🎯 视觉分析(基于预览图):
✓ 布局结构已识别
✓ 视觉细节已提取
✓ 间距和对齐已测量
💡 如需补充交互状态截图hover/active/disabled可额外提供
(可选,不提供将基于设计推测)
```
### 阶段 3智能下载资源
**格式决策树:**
```
节点类型判断:
├─ VECTOR → SVG 格式图标、Logo
├─ IMAGE
│ ├─ 尺寸 > 2000x2000 → 考虑 WebP压缩
│ ├─ 需要透明? → PNG
│ └─ 普通照片? → JPEG
└─ FRAME with background → 背景图PNG/JPEG
```
**下载配置要点:**
- ⚠️ 必须使用绝对路径(通过 `pwd` 命令获取)
- 根据阶段 0 检测的项目类型选择目标目录
- 设置合适的文件名kebab-case语义化
- 位图必须提供 imageRef
- 设置 @2x 分辨率pngScale: 2
**目录规划示例:**
```
根据项目类型:
Nuxt 3 → assets/images/
Next.js → public/images/
CRA/Vite → src/assets/images/
建议分类:
├── hero/(首屏资源)
├── features/(功能模块)
└── icons/(图标)
```
**文件大小验证:**
- PNG 照片:< 500KB
- SVG 图标:< 10KB
- JPEG 照片:< 300KB
- 超出建议优化或转换格式
### 阶段 4生成代码
**代码生成原则:**
- 根据阶段 0 检测的项目类型使用正确的引用方式
- 遵循项目的代码规范和风格(通过读取现有文件学习)
- 生成必要的类型定义TypeScript 项目)
- 添加合适的样式代码(响应式、适配)
**不同框架的处理策略:**
**Nuxt 3**
- 使用 import 语句导入
- 路径使用 `~/assets/` 别名
- 在模板中绑定到响应式变量
- 样式支持 scoped
**Next.js**
- 优先使用 Next Image 组件(性能优化)
- public 目录资源使用绝对路径(以 / 开头)
- 必须指定 width 和 height
- 支持自动图片优化
**CRA**
- 使用 import 语句导入
- 相对路径引入(从 ./assets/ 开始)
- Webpack 自动处理资源哈希
- 在 JSX 中使用导入的变量
**Angular**
- 直接使用资源路径字符串
- 路径以 `/assets/` 开头
- 在组件 TypeScript 中定义路径变量
- 在模板中通过属性绑定
**VueVite**
- 使用 import 导入
- 相对路径从 `./assets/` 开始
- 在模板中绑定到响应式变量
### 阶段 5验证与清理
**验证清单:**
1. **文件完整性**:检查所有文件是否存在且未损坏
2. **大小合理性**:验证文件大小在合理范围
3. **引用正确性**:确保 import 路径或直接路径正确
4. **构建测试**:运行 `npm run build` 验证是否有错误
5. **清理临时文件**:删除预览图临时目录
**完成报告模板:**
```
✅ 任务完成
项目信息:
- 类型Nuxt 3
- 构建工具Vite
- 资源目录assets/images/
已完成:
1. 自动导出设计预览图
2. 下载了 3 张图片(共 856 KB
3. 图片已放置在 assets/images/hero/
4. 已更新 components/HeroSection.vue
验证:
✅ 图片文件存在且大小合理
✅ 引用语法正确
✅ 类型定义完整
✅ 样式已添加
✅ 构建测试通过
建议:图片已优化,可直接使用。
```
## 常见问题诊断与修复
### 问题 1图片 404
**诊断步骤:**
1. 检查项目类型是否正确识别
2. 检查文件是否在正确的目录
3. 检查引用方式是否符合框架要求
**修复策略:**
- 重新检测项目类型
- 使用正确的引用方式
- 更新组件代码
### 问题 2下载失败或位置错误
**原因:**
- 使用了相对路径localPath 必须是绝对路径)
**修复:**
- 使用 `pwd` 命令获取当前目录
- 拼接完整的绝对路径
- 验证目录存在
### 问题 3node-id 格式错误
**症状:**
- API 返回"节点未找到"或 404
**原因:**
- URL 格式688-11564和 API 格式688:11564不同
**修复:**
- 检测 URL 中的格式
- 将横线替换为冒号
- 使用正确格式调用 API
### 问题 4布局还原度低
**原因:**
- 仅依赖 MCP 数据,缺少视觉细节
**修复(新特性):**
- 使用阶段 1.5 的自动导出预览图
- 对比预览图与节点数据
- 识别多层阴影、复杂渐变
- 测量实际间距和对齐
### 问题 5图标模糊
**原因:**
- 矢量图错误地下载为位图PNG
**修复:**
- 检查节点类型VECTOR
- 检查节点名称(包含 icon/logo
- 强制使用 SVG 格式
- 重新下载
## 调试工具
### 1. 验证 MCP 连接
使用 Bash 工具运行相关命令检查 MCP 是否正常
### 2. 检查预览图
验证预览图是否成功导出,查看图片质量和完整性
### 3. 导出完整数据
将 Figma 返回的 JSON 数据保存到文件,便于详细分析
### 4. 批量验证文件
检查所有下载文件的大小、格式、完整性
### 5. 构建验证
运行构建命令,让构建工具暴露所有路径和引用错误
## 核心原则(必须遵守)
1.**阶段 0 强制执行**:永远先检测项目类型,不能假设
2.**🆕 自动导出预览**:阶段 1.5 自动导出设计预览图
3.**格式转换**URL 的 node-id横线转换为 API 的 nodeId冒号
4.**绝对路径**localPath 必须使用绝对路径,通过 pwd 获取
5.**正确引用**:根据项目类型使用正确的资源引用方式
6.**语义化命名**:文件名使用 kebab-case清晰描述用途
7.**格式选择**:矢量用 SVG位图用 PNG/JPEG
8.**清理临时文件**:完成后删除预览图等临时文件
9.**不要假设**:不要假设项目类型、框架版本、目录结构
10.**不要混淆**:不要混淆 public 和 assets 的用途和引用方式
## 沟通风格
### 开始任务时
- 明确说明将要执行的完整流程
- 显示检测到的项目信息
- 列出任务计划清单
### 执行过程中
- 实时报告当前阶段和进度
- 说明关键决策(为什么选择 SVG/PNG
- 提示用户可选的额外操作
### 完成时
- 提供详细的完成报告
- 说明已验证的内容
- 给出下一步建议(如有需要)
### 遇到问题时
- 清晰说明问题原因
- 提供修复步骤
- 解释为什么会出现这个问题
## 效率价值
**传统手动方式:**
- 时间30-45 分钟
- 错误率20%(路径错误、格式错误、遗漏资源)
- 需要:重复操作、手动截图、手动调整、反复验证
**使用此 Agent自动截图**
- 时间3-5 分钟
- 错误率2%
- 需要:仅提供 Figma URL
**效率提升9-15 倍**
## 你的目标
你的最终目标是让开发者从重复性的设计资源处理工作中解放出来,专注于更有创造性的开发任务。你通过自动化、智能化的流程,确保设计资源的正确下载、引用和使用,同时保证跨项目的一致性和可靠性。
你始终保持专业、高效、可靠的工作风格。你理解不同框架的差异,能够准确识别和适配。你重视细节,确保视觉还原度高。你注重验证,确保每个环节都正确无误。
记住:自动化的价值不仅在于速度,更在于消除人为错误和提高质量的一致性。每次任务都必须严格遵循 6 个阶段,不能跳过任何环节。

79
hooks/check-figma-api-key.sh Executable file
View File

@@ -0,0 +1,79 @@
#!/bin/bash
# 获取插件根目录
if [ -n "$CLAUDE_PLUGIN_ROOT" ]; then
PLUGIN_ROOT="$CLAUDE_PLUGIN_ROOT"
else
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
PLUGIN_ROOT="$( cd "$SCRIPT_DIR/.." && pwd )"
fi
MCP_JSON="$PLUGIN_ROOT/.mcp.json"
MCP_JSON_TEMPLATE="$PLUGIN_ROOT/.mcp.json.template"
# 检查 .mcp.json 是否存在,如果不存在则从模板复制
if [ ! -f "$MCP_JSON" ]; then
if [ -f "$MCP_JSON_TEMPLATE" ]; then
echo "📝 首次运行,正在从模板创建 .mcp.json 文件..."
cp "$MCP_JSON_TEMPLATE" "$MCP_JSON"
else
echo "❌ 错误:找不到 .mcp.json 和 .mcp.json.template 文件"
exit 1
fi
fi
# 检查 .mcp.json 中是否还包含未替换的 ${FIGMA_API_KEY} 占位符
if grep -q '\${FIGMA_API_KEY}' "$MCP_JSON"; then
# 包含占位符,说明还没配置
echo ""
echo "╔════════════════════════════════════════════════════════════╗"
echo "║ 🎨 切图仔 (Qietuzai) Plugin - 配置向导 ║"
echo "╚════════════════════════════════════════════════════════════╝"
echo ""
echo "⚠️ 检测到您还未配置 Figma API Key"
echo ""
echo "🌐 正在启动图形化配置界面..."
echo ""
# 获取脚本所在目录(使用 CLAUDE_PLUGIN_ROOT 如果可用)
if [ -n "$CLAUDE_PLUGIN_ROOT" ]; then
SCRIPT_DIR="$CLAUDE_PLUGIN_ROOT/hooks"
else
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
fi
# 启动 Python HTTP 服务器(后台运行)
python3 "$SCRIPT_DIR/setup-server.py" > /dev/null 2>&1 &
SERVER_PID=$!
# 等待服务器启动
sleep 2
# 打开浏览器到配置页面
CONFIG_URL="http://localhost:3456"
echo "✨ 配置页面已在浏览器中打开: $CONFIG_URL"
echo ""
echo "📝 请在浏览器中完成以下步骤:"
echo " 1. 访问 Figma 设置页面获取 API Key"
echo " 2. 在表单中输入您的 API Key"
echo " 3. 点击保存"
echo " 4. 重启 Claude Code"
echo ""
echo "💡 如果浏览器没有自动打开,请手动访问: $CONFIG_URL"
echo ""
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
open "$CONFIG_URL" 2>/dev/null
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
xdg-open "$CONFIG_URL" 2>/dev/null
elif [[ "$OSTYPE" == "msys" ]] || [[ "$OSTYPE" == "win32" ]]; then
# Windows
start "$CONFIG_URL" 2>/dev/null
fi
else
# API Key 已配置,静默通过
exit 0
fi

14
hooks/hooks.json Normal file
View File

@@ -0,0 +1,14 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "bash ${CLAUDE_PLUGIN_ROOT}/hooks/check-figma-api-key.sh"
}
]
}
]
}
}

597
hooks/setup-server.py Executable file
View File

@@ -0,0 +1,597 @@
#!/usr/bin/env python3
"""
Qietuzai Plugin Setup Server
提供图形化界面配置 Figma API Key
"""
import http.server
import socketserver
import urllib.parse
import json
import os
import sys
from pathlib import Path
PORT = 3456
# HTML 页面
HTML_FORM = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>切图仔 Plugin - 配置向导</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 600px;
width: 100%;
padding: 40px;
}
.header {
text-align: center;
margin-bottom: 32px;
}
.icon {
font-size: 64px;
margin-bottom: 16px;
}
h1 {
color: #1a202c;
font-size: 28px;
margin-bottom: 8px;
}
.subtitle {
color: #718096;
font-size: 14px;
}
.step {
background: #f7fafc;
border-left: 4px solid #667eea;
padding: 16px;
margin-bottom: 24px;
border-radius: 4px;
}
.step-title {
color: #2d3748;
font-weight: 600;
margin-bottom: 8px;
display: flex;
align-items: center;
gap: 8px;
}
.step-content {
color: #4a5568;
font-size: 14px;
line-height: 1.6;
}
.step-content a {
color: #667eea;
text-decoration: none;
}
.step-content a:hover {
text-decoration: underline;
}
.form-group {
margin-bottom: 24px;
}
label {
display: block;
color: #2d3748;
font-weight: 600;
margin-bottom: 8px;
font-size: 14px;
}
input[type="text"],
input[type="password"] {
width: 100%;
padding: 12px 16px;
border: 2px solid #e2e8f0;
border-radius: 8px;
font-size: 14px;
transition: all 0.2s;
font-family: 'Monaco', 'Menlo', monospace;
}
input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.hint {
color: #718096;
font-size: 12px;
margin-top: 8px;
}
.button-group {
display: flex;
gap: 12px;
margin-top: 32px;
}
button {
flex: 1;
padding: 14px 24px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #667eea;
color: white;
}
.btn-primary:hover {
background: #5568d3;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: #e2e8f0;
color: #4a5568;
}
.btn-secondary:hover {
background: #cbd5e0;
}
.error {
background: #fed7d7;
border: 1px solid #fc8181;
color: #c53030;
padding: 12px;
border-radius: 8px;
margin-bottom: 16px;
font-size: 14px;
}
.loading {
display: none;
text-align: center;
margin-top: 16px;
}
.spinner {
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="icon">🎨</div>
<h1>切图仔 Plugin</h1>
<p class="subtitle">配置 Figma API Key</p>
</div>
<div class="step">
<div class="step-title">
<span>📝</span>
<span>第 1 步:获取 Figma API Key</span>
</div>
<div class="step-content">
访问 <a href="https://www.figma.com/settings" target="_blank">Figma 设置页面</a>
"Personal access tokens" 部分创建一个新的 token。
</div>
</div>
<div class="step">
<div class="step-title">
<span>🔑</span>
<span>第 2 步:输入 API Key</span>
</div>
<div class="step-content">
将刚才复制的 token 粘贴到下方输入框中。
</div>
</div>
<form id="setupForm">
<div class="form-group">
<label for="apiKey">Figma API Key *</label>
<input
type="password"
id="apiKey"
name="apiKey"
placeholder="figd_xxxxxxxxxxxx"
required
autocomplete="off"
>
<div class="hint">
💡 您的 API Key 将被安全地存储在插件配置文件中
</div>
</div>
<div class="button-group">
<button type="button" class="btn-secondary" onclick="window.close()">
取消
</button>
<button type="submit" class="btn-primary">
保存配置
</button>
</div>
<div class="loading" id="loading">
<div class="spinner"></div>
<p style="margin-top: 12px; color: #718096;">正在保存配置...</p>
</div>
</form>
</div>
<script>
document.getElementById('setupForm').addEventListener('submit', async (e) => {
e.preventDefault();
const apiKey = document.getElementById('apiKey').value;
const loading = document.getElementById('loading');
const submitBtn = e.target.querySelector('button[type="submit"]');
// 显示加载状态
submitBtn.disabled = true;
loading.style.display = 'block';
try {
const response = await fetch('/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ apiKey })
});
const result = await response.json();
if (result.success) {
// 跳转到成功页面
window.location.href = '/success?shell=' + encodeURIComponent(result.shell || 'bash');
} else {
alert('保存失败: ' + result.error);
submitBtn.disabled = false;
loading.style.display = 'none';
}
} catch (error) {
alert('保存失败: ' + error.message);
submitBtn.disabled = false;
loading.style.display = 'none';
}
});
</script>
</body>
</html>
"""
SUCCESS_HTML = """
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>配置成功</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.container {
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 600px;
width: 100%;
padding: 40px;
text-align: center;
}
.success-icon {
font-size: 64px;
margin-bottom: 24px;
}
h1 {
color: #1a202c;
font-size: 28px;
margin-bottom: 16px;
}
.message {
color: #4a5568;
font-size: 16px;
line-height: 1.6;
margin-bottom: 32px;
}
.info-box {
background: #f7fafc;
border: 1px solid #e2e8f0;
border-radius: 8px;
padding: 20px;
margin-bottom: 24px;
text-align: left;
}
.info-box h3 {
color: #2d3748;
font-size: 14px;
margin-bottom: 12px;
}
.info-box p {
color: #718096;
font-size: 14px;
line-height: 1.6;
}
.command {
background: #2d3748;
color: #48bb78;
padding: 12px 16px;
border-radius: 8px;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 13px;
margin: 8px 0;
text-align: left;
}
button {
background: #667eea;
color: white;
padding: 14px 32px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}
button:hover {
background: #5568d3;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
</style>
</head>
<body>
<div class="container">
<div class="success-icon">✅</div>
<h1>配置成功!</h1>
<p class="message">
您的 Figma API Key 已成功保存到系统环境变量中。
</p>
<div class="info-box">
<h3>📋 配置详情</h3>
<p>✅ API Key 已保存到插件配置文件 <strong>.mcp.json</strong></p>
<p>✅ 备份已添加到: <strong>{shell_config}</strong></p>
<div class="command">export FIGMA_API_KEY="****"</div>
</div>
<div class="info-box">
<h3>🔄 下一步</h3>
<p>请<strong>重启 Claude Code</strong> 使配置生效,然后即可开始使用切图仔 Plugin</p>
<p style="margin-top: 8px; font-size: 12px; color: #e53e3e;">⚠️ 注意:请勿将 <strong>.mcp.json</strong> 提交到 git以保护您的 API Key 安全</p>
</div>
<button onclick="window.close()">关闭此页面</button>
</div>
<script>
// 10 秒后自动关闭
setTimeout(() => {
window.close();
}, 10000);
</script>
</body>
</html>
"""
class SetupHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
if self.path == '/' or self.path == '/index.html':
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
self.wfile.write(HTML_FORM.encode('utf-8'))
elif self.path.startswith('/success'):
# 解析查询参数
query = urllib.parse.urlparse(self.path).query
params = urllib.parse.parse_qs(query)
shell = params.get('shell', ['bash'])[0]
# 确定配置文件路径
shell_configs = {
'zsh': '~/.zshrc',
'bash': '~/.bashrc',
'fish': '~/.config/fish/config.fish'
}
shell_config = shell_configs.get(shell, '~/.bashrc')
self.send_response(200)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
html = SUCCESS_HTML.replace('{shell_config}', shell_config)
self.wfile.write(html.encode('utf-8'))
else:
self.send_error(404)
def do_POST(self):
if self.path == '/save':
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
data = json.loads(post_data.decode('utf-8'))
api_key = data.get('apiKey', '').strip()
if not api_key:
self.send_json_response({'success': False, 'error': 'API Key 不能为空'})
return
# 检测 shell 类型
shell = os.environ.get('SHELL', '/bin/bash')
if 'zsh' in shell:
config_file = Path.home() / '.zshrc'
shell_name = 'zsh'
elif 'fish' in shell:
config_file = Path.home() / '.config' / 'fish' / 'config.fish'
shell_name = 'fish'
else:
config_file = Path.home() / '.bashrc'
shell_name = 'bash'
try:
# 1. 更新 .mcp.json 文件(替换占位符)
# 获取插件根目录
script_dir = Path(__file__).parent
plugin_root = script_dir.parent
mcp_json_path = plugin_root / '.mcp.json'
if not mcp_json_path.exists():
raise FileNotFoundError(f'.mcp.json 文件不存在: {mcp_json_path}')
# 读取并替换占位符
mcp_content = mcp_json_path.read_text()
if '${FIGMA_API_KEY}' in mcp_content:
mcp_content = mcp_content.replace('${FIGMA_API_KEY}', api_key)
mcp_json_path.write_text(mcp_content)
# 2. 同时也保存到 shell 配置文件(作为备份)
# 读取现有配置
if config_file.exists():
content = config_file.read_text()
else:
content = ''
# 检查是否已经存在 FIGMA_API_KEY
if 'FIGMA_API_KEY' in content:
# 更新现有的
import re
pattern = r'export FIGMA_API_KEY=.*'
if re.search(pattern, content):
content = re.sub(pattern, f'export FIGMA_API_KEY="{api_key}"', content)
else:
content += f'\nexport FIGMA_API_KEY="{api_key}"\n'
else:
# 添加新的
content += f'\n# Qietuzai Plugin - Figma API Key\nexport FIGMA_API_KEY="{api_key}"\n'
# 写入配置文件
config_file.parent.mkdir(parents=True, exist_ok=True)
config_file.write_text(content)
self.send_json_response({
'success': True,
'shell': shell_name,
'config_file': str(config_file),
'mcp_json': str(mcp_json_path)
})
# 配置成功后,延迟关闭服务器
import threading
def shutdown_server():
import time
time.sleep(2)
print('\n✅ 配置已保存,服务器即将关闭...')
os._exit(0)
threading.Thread(target=shutdown_server, daemon=True).start()
except Exception as e:
self.send_json_response({
'success': False,
'error': str(e)
})
else:
self.send_error(404)
def send_json_response(self, data):
self.send_response(200)
self.send_header('Content-type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps(data).encode('utf-8'))
def log_message(self, format, *args):
# 减少日志输出
pass
def main():
try:
with socketserver.TCPServer(("", PORT), SetupHandler) as httpd:
print(f"✨ 配置服务器已启动: http://localhost:{PORT}")
print(f"📝 请在浏览器中完成配置...")
print(f"⏹ 完成后服务器会自动关闭\n")
httpd.serve_forever()
except KeyboardInterrupt:
print("\n\n👋 服务器已关闭")
sys.exit(0)
except OSError as e:
if e.errno == 48: # Address already in use
print(f"❌ 端口 {PORT} 已被占用,请检查是否有其他配置向导正在运行")
sys.exit(1)
else:
raise
if __name__ == '__main__':
main()

26
hooks/test-setup.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/bin/bash
# 测试配置向导
# 用法: bash test-setup.sh
echo "🧪 测试切图仔 Plugin 配置向导"
echo ""
# 临时清除环境变量(仅用于测试)
unset FIGMA_API_KEY
# 设置 CLAUDE_PLUGIN_ROOT模拟 Claude Code 环境)
export CLAUDE_PLUGIN_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
echo "📁 Plugin 目录: $CLAUDE_PLUGIN_ROOT"
echo ""
# 运行配置检查脚本
bash "$CLAUDE_PLUGIN_ROOT/hooks/check-figma-api-key.sh"
echo ""
echo "✅ 测试完成!"
echo ""
echo "💡 提示:"
echo " - 如果浏览器打开了配置页面,说明脚本工作正常"
echo " - 配置成功后会在 ~/.claude/qietuzai-setup-done 创建标记文件"
echo " - 可以通过 'cat ~/.zshrc | grep FIGMA_API_KEY' 验证配置"

61
plugin.lock.json Normal file
View File

@@ -0,0 +1,61 @@
{
"$schema": "internal://schemas/plugin.lock.v1.json",
"pluginId": "gh:fiftyk/cc-plugins-market:plugins/qietuzai",
"normalized": {
"repo": null,
"ref": "refs/tags/v20251128.0",
"commit": "878dddb11a7874e4c1cb250d6d57384d02802270",
"treeHash": "f60f5511f74abd0d08db66338f7ee20b20766df0953dfde0734edfd87ec4b6d9",
"generatedAt": "2025-11-28T10:16:54.107030Z",
"toolVersion": "publish_plugins.py@0.2.0"
},
"origin": {
"remote": "git@github.com:zhongweili/42plugin-data.git",
"branch": "master",
"commit": "aa1497ed0949fd50e99e70d6324a29c5b34f9390",
"repoRoot": "/Users/zhongweili/projects/openmind/42plugin-data"
},
"manifest": {
"name": "qietuzai",
"description": "自动从 Figma 生成 UI 代码。首次使用会自动引导配置 Figma API Key",
"version": "1.0.0"
},
"content": {
"files": [
{
"path": "README.md",
"sha256": "eecfd379f53c0155f3db0660bbd6fa92bc9e708cfb93f87e848fb481a8366314"
},
{
"path": "agents/qietuzai.md",
"sha256": "22c61a786d721fdb554c7584d684312168d64518c854dc3392c85ce81bbbc65e"
},
{
"path": "hooks/test-setup.sh",
"sha256": "a9edde19001d1c939a7b154a0f4c1757c4b0342314386b7191963e41c25d4520"
},
{
"path": "hooks/hooks.json",
"sha256": "1a499ca6cb7c82435366b84016c101723393c87e825d20d5f908d131b7e3f1ac"
},
{
"path": "hooks/setup-server.py",
"sha256": "484342005365b0fa0b18e1cc3b846a8fd0e098c677cc8f64fc0d5b35f4d0e705"
},
{
"path": "hooks/check-figma-api-key.sh",
"sha256": "932af599f20143413ac8da99e4d1c92bdd00c8217160fe3db3b7f126ac5274a5"
},
{
"path": ".claude-plugin/plugin.json",
"sha256": "68837f0ff8e2107346d311d7e9ee3f871f490917a764e02fab615a19a0a8a5cd"
}
],
"dirSha256": "f60f5511f74abd0d08db66338f7ee20b20766df0953dfde0734edfd87ec4b6d9"
},
"security": {
"scannedAt": null,
"scannerVersion": null,
"flags": []
}
}