Initial commit
This commit is contained in:
14
.claude-plugin/plugin.json
Normal file
14
.claude-plugin/plugin.json
Normal 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
3
README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# qietuzai
|
||||||
|
|
||||||
|
自动从 Figma 生成 UI 代码。首次使用会自动引导配置 Figma API Key
|
||||||
379
agents/qietuzai.md
Normal file
379
agents/qietuzai.md
Normal 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 需要 import,Next.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 中定义路径变量
|
||||||
|
- 在模板中通过属性绑定
|
||||||
|
|
||||||
|
**Vue(Vite):**
|
||||||
|
- 使用 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` 命令获取当前目录
|
||||||
|
- 拼接完整的绝对路径
|
||||||
|
- 验证目录存在
|
||||||
|
|
||||||
|
### 问题 3:node-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
79
hooks/check-figma-api-key.sh
Executable 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
14
hooks/hooks.json
Normal 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
597
hooks/setup-server.py
Executable 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
26
hooks/test-setup.sh
Executable 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
61
plugin.lock.json
Normal 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": []
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user