palette-mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,126 @@
1
+ export interface FigmaNode {
2
+ id: string;
3
+ name: string;
4
+ type: string;
5
+ visible?: boolean;
6
+ children?: FigmaNode[];
7
+ absoluteBoundingBox?: {
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ };
13
+ fills?: Array<{
14
+ type: string;
15
+ color?: {
16
+ r: number;
17
+ g: number;
18
+ b: number;
19
+ a: number;
20
+ };
21
+ gradientStops?: Array<{
22
+ position: number;
23
+ color: {
24
+ r: number;
25
+ g: number;
26
+ b: number;
27
+ a: number;
28
+ };
29
+ }>;
30
+ }>;
31
+ strokes?: Array<{
32
+ type: string;
33
+ color?: {
34
+ r: number;
35
+ g: number;
36
+ b: number;
37
+ a: number;
38
+ };
39
+ strokeWeight?: number;
40
+ }>;
41
+ cornerRadius?: number;
42
+ characters?: string;
43
+ style?: {
44
+ fontFamily?: string;
45
+ fontSize?: number;
46
+ fontWeight?: number;
47
+ textAlignHorizontal?: string;
48
+ textAlignVertical?: string;
49
+ };
50
+ layoutMode?: 'NONE' | 'HORIZONTAL' | 'VERTICAL';
51
+ primaryAxisSizingMode?: 'FIXED' | 'AUTO';
52
+ counterAxisSizingMode?: 'FIXED' | 'AUTO';
53
+ paddingLeft?: number;
54
+ paddingRight?: number;
55
+ paddingTop?: number;
56
+ paddingBottom?: number;
57
+ itemSpacing?: number;
58
+ }
59
+ export interface FigmaFile {
60
+ document: FigmaNode;
61
+ components: Record<string, FigmaNode>;
62
+ styles: Record<string, any>;
63
+ name: string;
64
+ lastModified: string;
65
+ thumbnailUrl: string;
66
+ }
67
+ export interface FigmaAnalysis {
68
+ totalNodes: number;
69
+ componentCount: number;
70
+ frameCount: number;
71
+ textCount: number;
72
+ availableComponents: string[];
73
+ suggestedMappings: Array<{
74
+ figmaComponent: string;
75
+ designSystemComponent: string;
76
+ confidence: number;
77
+ }>;
78
+ }
79
+ export declare class FigmaService {
80
+ private accessToken;
81
+ private baseUrl;
82
+ private mcpClient;
83
+ private useMCP;
84
+ constructor(useMCP?: boolean, mcpBaseUrl?: string);
85
+ /**
86
+ * Figma URL에서 파일 ID 추출
87
+ */
88
+ private extractFileId;
89
+ /**
90
+ * Figma URL에서 node-id 추출
91
+ */
92
+ extractNodeId(url: string): string | undefined;
93
+ /**
94
+ * MCP 응답을 FigmaFile 형식으로 변환
95
+ */
96
+ private transformMCPResponseToFigmaFile;
97
+ /**
98
+ * Figma 파일 데이터 가져오기
99
+ * MCP 서버를 우선 사용하고, 실패 시 기존 REST API로 폴백
100
+ */
101
+ getFigmaData(url: string, nodeId?: string): Promise<FigmaFile>;
102
+ /**
103
+ * Figma 파일 구조 분석
104
+ */
105
+ analyzeFigmaFile(url: string): Promise<string>;
106
+ /**
107
+ * 파일 구조 분석 및 컴포넌트 정보 추출
108
+ */
109
+ private analyzeFileStructure;
110
+ /**
111
+ * 파일에 있는 다른 유형의 노드 수 세기
112
+ */
113
+ private countNodes;
114
+ /**
115
+ * Figma 컴포넌트와 디자인 시스템 컴포넌트 간의 매핑 제안
116
+ */
117
+ private suggestComponentMappings;
118
+ /**
119
+ * 분석 결과 표시 형식 지정
120
+ */
121
+ private formatAnalysis;
122
+ /**
123
+ * Figma 파일에서 디자인 토큰 추출
124
+ */
125
+ extractDesignTokens(file: FigmaFile): Record<string, any>;
126
+ }
@@ -0,0 +1,295 @@
1
+ import axios from 'axios';
2
+ import { FigmaMCPClient } from '../utils/figma-mcp-client.js';
3
+ export class FigmaService {
4
+ accessToken;
5
+ baseUrl = 'https://api.figma.com/v1';
6
+ mcpClient = null;
7
+ useMCP;
8
+ constructor(useMCP = true, mcpBaseUrl) {
9
+ this.accessToken = process.env.FIGMA_ACCESS_TOKEN || '';
10
+ this.useMCP = useMCP;
11
+ if (useMCP) {
12
+ const mcpUrl = mcpBaseUrl || process.env.FIGMA_MCP_SERVER_URL || 'http://127.0.0.1:3845/mcp';
13
+ this.mcpClient = new FigmaMCPClient(mcpUrl);
14
+ }
15
+ if (!this.accessToken) {
16
+ console.warn('환경 변수에서 FIGMA_ACCESS_TOKEN을 찾을 수 없습니다.');
17
+ }
18
+ }
19
+ /**
20
+ * Figma URL에서 파일 ID 추출
21
+ */
22
+ extractFileId(url) {
23
+ // /file/ 또는 /design/ 경로에서 파일 ID 추출
24
+ const match = url.match(/\/(?:file|design)\/([a-zA-Z0-9]+)/);
25
+ if (match) {
26
+ return match[1];
27
+ }
28
+ // 이미 파일 ID인 경우
29
+ if (/^[a-zA-Z0-9]+$/.test(url)) {
30
+ return url;
31
+ }
32
+ throw new Error('잘못된 Figma URL 형식입니다.');
33
+ }
34
+ /**
35
+ * Figma URL에서 node-id 추출
36
+ */
37
+ extractNodeId(url) {
38
+ const match = url.match(/[?&]node-id=([^&]+)/);
39
+ if (match) {
40
+ // node-id는 URL 인코딩되어 있을 수 있으므로 디코딩
41
+ return decodeURIComponent(match[1]);
42
+ }
43
+ return undefined;
44
+ }
45
+ /**
46
+ * MCP 응답을 FigmaFile 형식으로 변환
47
+ */
48
+ transformMCPResponseToFigmaFile(mcpData) {
49
+ // MCP 응답 형식에 따라 변환 로직 구현
50
+ // Figma MCP 서버의 응답 구조에 맞게 조정 필요
51
+ if (!mcpData) {
52
+ throw new Error('MCP 응답 데이터가 없습니다.');
53
+ }
54
+ // MCP 응답이 이미 FigmaFile 형식인 경우
55
+ if (mcpData.document) {
56
+ return {
57
+ document: mcpData.document,
58
+ components: mcpData.components || {},
59
+ styles: mcpData.styles || {},
60
+ name: mcpData.name || 'Untitled',
61
+ lastModified: mcpData.lastModified || new Date().toISOString(),
62
+ thumbnailUrl: mcpData.thumbnailUrl || '',
63
+ };
64
+ }
65
+ // MCP 응답이 다른 형식인 경우 변환
66
+ // content 배열에서 데이터 추출 (MCP 도구 응답 형식)
67
+ if (mcpData.content && Array.isArray(mcpData.content)) {
68
+ const textContent = mcpData.content.find((c) => c.type === 'text');
69
+ if (textContent) {
70
+ try {
71
+ const parsed = JSON.parse(textContent.text);
72
+ return this.transformMCPResponseToFigmaFile(parsed);
73
+ }
74
+ catch {
75
+ // JSON이 아닌 경우 처리
76
+ }
77
+ }
78
+ }
79
+ // 기본 구조로 변환 시도
80
+ return {
81
+ document: mcpData.document || mcpData.node || { id: 'root', name: 'Document', type: 'DOCUMENT', children: [] },
82
+ components: mcpData.components || {},
83
+ styles: mcpData.styles || {},
84
+ name: mcpData.name || 'Untitled',
85
+ lastModified: mcpData.lastModified || new Date().toISOString(),
86
+ thumbnailUrl: mcpData.thumbnailUrl || '',
87
+ };
88
+ }
89
+ /**
90
+ * Figma 파일 데이터 가져오기
91
+ * MCP 서버를 우선 사용하고, 실패 시 기존 REST API로 폴백
92
+ */
93
+ async getFigmaData(url, nodeId) {
94
+ const fileId = this.extractFileId(url);
95
+ // MCP 클라이언트 사용 시도
96
+ if (this.useMCP && this.mcpClient !== null) {
97
+ try {
98
+ const isAvailable = await this.mcpClient.isAvailable();
99
+ if (isAvailable) {
100
+ const mcpData = nodeId
101
+ ? await this.mcpClient.getNodeData(fileId, nodeId)
102
+ : await this.mcpClient.getFileData(fileId, nodeId);
103
+ if (mcpData) {
104
+ try {
105
+ return this.transformMCPResponseToFigmaFile(mcpData);
106
+ }
107
+ catch (error) {
108
+ console.warn('MCP 응답 변환 실패, REST API로 폴백:', error);
109
+ // 폴백으로 REST API 사용
110
+ }
111
+ }
112
+ }
113
+ }
114
+ catch (error) {
115
+ console.warn('Figma MCP 서버 연결 실패, REST API로 폴백:', error);
116
+ // 폴백으로 REST API 사용
117
+ }
118
+ }
119
+ // REST API 폴백
120
+ if (!this.accessToken) {
121
+ throw new Error('Figma 액세스 토큰이 필요합니다. MCP 서버도 사용할 수 없습니다.');
122
+ }
123
+ try {
124
+ const response = await axios.get(`${this.baseUrl}/files/${fileId}`, {
125
+ headers: {
126
+ 'X-Figma-Token': this.accessToken,
127
+ },
128
+ params: {
129
+ ids: nodeId || undefined,
130
+ },
131
+ });
132
+ return {
133
+ document: response.data.document,
134
+ components: response.data.components || {},
135
+ styles: response.data.styles || {},
136
+ name: response.data.name,
137
+ lastModified: response.data.lastModified,
138
+ thumbnailUrl: response.data.thumbnailUrl,
139
+ };
140
+ }
141
+ catch (error) {
142
+ if (axios.isAxiosError(error)) {
143
+ throw new Error(`Figma API error: ${error.response?.data?.message || error.message}`);
144
+ }
145
+ throw error;
146
+ }
147
+ }
148
+ /**
149
+ * Figma 파일 구조 분석
150
+ */
151
+ async analyzeFigmaFile(url) {
152
+ const fileData = await this.getFigmaData(url);
153
+ const analysis = this.analyzeFileStructure(fileData);
154
+ return this.formatAnalysis(analysis);
155
+ }
156
+ /**
157
+ * 파일 구조 분석 및 컴포넌트 정보 추출
158
+ */
159
+ analyzeFileStructure(file) {
160
+ const stats = this.countNodes(file.document);
161
+ const availableComponents = Object.keys(file.components);
162
+ return {
163
+ totalNodes: stats.total,
164
+ componentCount: stats.components,
165
+ frameCount: stats.frames,
166
+ textCount: stats.text,
167
+ availableComponents,
168
+ suggestedMappings: this.suggestComponentMappings(availableComponents),
169
+ };
170
+ }
171
+ /**
172
+ * 파일에 있는 다른 유형의 노드 수 세기
173
+ */
174
+ countNodes(node) {
175
+ let total = 1;
176
+ let components = node.type === 'COMPONENT' ? 1 : 0;
177
+ let frames = node.type === 'FRAME' ? 1 : 0;
178
+ let text = node.type === 'TEXT' ? 1 : 0;
179
+ if (node.children) {
180
+ for (const child of node.children) {
181
+ const childStats = this.countNodes(child);
182
+ total += childStats.total;
183
+ components += childStats.components;
184
+ frames += childStats.frames;
185
+ text += childStats.text;
186
+ }
187
+ }
188
+ return { total, components, frames, text };
189
+ }
190
+ /**
191
+ * Figma 컴포넌트와 디자인 시스템 컴포넌트 간의 매핑 제안
192
+ */
193
+ suggestComponentMappings(figmaComponents) {
194
+ const mappings = [];
195
+ // 일반적인 컴포넌트 이름 매핑
196
+ const commonMappings = {
197
+ 'button': ['Button', 'Btn', 'PrimaryButton', 'SecondaryButton'],
198
+ 'input': ['Input', 'TextField', 'TextInput'],
199
+ 'card': ['Card', 'Panel', 'Container'],
200
+ 'modal': ['Modal', 'Dialog', 'Popup'],
201
+ 'header': ['Header', 'Navbar', 'Navigation'],
202
+ 'footer': ['Footer', 'BottomBar'],
203
+ 'sidebar': ['Sidebar', 'Drawer', 'Navigation'],
204
+ 'form': ['Form', 'FormGroup'],
205
+ 'table': ['Table', 'DataTable'],
206
+ 'list': ['List', 'ListItem'],
207
+ 'avatar': ['Avatar', 'ProfileImage'],
208
+ 'badge': ['Badge', 'Tag', 'Label'],
209
+ 'tooltip': ['Tooltip', 'Popover'],
210
+ 'dropdown': ['Dropdown', 'Select'],
211
+ 'checkbox': ['Checkbox', 'CheckBox'],
212
+ 'radio': ['Radio', 'RadioButton'],
213
+ 'switch': ['Switch', 'Toggle'],
214
+ 'slider': ['Slider', 'Range'],
215
+ 'progress': ['Progress', 'ProgressBar'],
216
+ 'spinner': ['Spinner', 'Loader'],
217
+ 'alert': ['Alert', 'Notification'],
218
+ 'breadcrumb': ['Breadcrumb', 'Breadcrumbs'],
219
+ 'pagination': ['Pagination', 'Pager'],
220
+ 'tabs': ['Tabs', 'TabList'],
221
+ 'accordion': ['Accordion', 'Collapse'],
222
+ 'carousel': ['Carousel', 'Slider'],
223
+ 'stepper': ['Stepper', 'Steps'],
224
+ };
225
+ for (const figmaComponent of figmaComponents) {
226
+ const lowerName = figmaComponent.toLowerCase();
227
+ for (const [key, possibleNames] of Object.entries(commonMappings)) {
228
+ for (const possibleName of possibleNames) {
229
+ if (lowerName.includes(key) || lowerName.includes(possibleName.toLowerCase())) {
230
+ mappings.push({
231
+ figmaComponent,
232
+ designSystemComponent: possibleName,
233
+ confidence: 0.8,
234
+ });
235
+ break;
236
+ }
237
+ }
238
+ }
239
+ }
240
+ return mappings;
241
+ }
242
+ /**
243
+ * 분석 결과 표시 형식 지정
244
+ */
245
+ formatAnalysis(analysis) {
246
+ let result = `## Figma File Analysis\n\n`;
247
+ result += `**File Statistics:**\n`;
248
+ result += `- Total Nodes: ${analysis.totalNodes}\n`;
249
+ result += `- Components: ${analysis.componentCount}\n`;
250
+ result += `- Frames: ${analysis.frameCount}\n`;
251
+ result += `- Text Elements: ${analysis.textCount}\n\n`;
252
+ if (analysis.availableComponents.length > 0) {
253
+ result += `**Available Components:**\n`;
254
+ analysis.availableComponents.forEach(comp => {
255
+ result += `- ${comp}\n`;
256
+ });
257
+ result += `\n`;
258
+ }
259
+ if (analysis.suggestedMappings.length > 0) {
260
+ result += `**Suggested Component Mappings:**\n`;
261
+ analysis.suggestedMappings.forEach(mapping => {
262
+ result += `- ${mapping.figmaComponent} → ${mapping.designSystemComponent} (${Math.round(mapping.confidence * 100)}% confidence)\n`;
263
+ });
264
+ }
265
+ return result;
266
+ }
267
+ /**
268
+ * Figma 파일에서 디자인 토큰 추출
269
+ */
270
+ extractDesignTokens(file) {
271
+ const tokens = {
272
+ colors: {},
273
+ typography: {},
274
+ spacing: {},
275
+ borderRadius: {},
276
+ };
277
+ // 스타일에서 색상 추출
278
+ for (const [key, style] of Object.entries(file.styles)) {
279
+ if (style.styleType === 'FILL') {
280
+ tokens.colors[key] = style.description || key;
281
+ }
282
+ }
283
+ // 타이포그래피 추출
284
+ for (const [key, style] of Object.entries(file.styles)) {
285
+ if (style.styleType === 'TEXT') {
286
+ tokens.typography[key] = {
287
+ fontFamily: style.fontFamily,
288
+ fontSize: style.fontSize,
289
+ fontWeight: style.fontWeight,
290
+ };
291
+ }
292
+ }
293
+ return tokens;
294
+ }
295
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Figma Desktop MCP 서버와 통신하는 클라이언트
3
+ * HTTP 기반 MCP 서버와 JSON-RPC 2.0 프로토콜로 통신
4
+ */
5
+ export declare class FigmaMCPClient {
6
+ private client;
7
+ private baseUrl;
8
+ private requestId;
9
+ constructor(baseUrl?: string);
10
+ /**
11
+ * JSON-RPC 2.0 요청 생성
12
+ */
13
+ private createRequest;
14
+ /**
15
+ * MCP 서버에 요청 전송
16
+ * HTTP 기반 MCP 서버는 JSON-RPC 2.0 프로토콜을 사용
17
+ */
18
+ private sendRequest;
19
+ /**
20
+ * 사용 가능한 도구 목록 가져오기
21
+ */
22
+ listTools(): Promise<any[]>;
23
+ /**
24
+ * MCP 도구 호출
25
+ */
26
+ private callTool;
27
+ /**
28
+ * Figma 파일 데이터 가져오기
29
+ * Figma MCP 서버의 도구를 사용하여 파일 정보 가져오기
30
+ */
31
+ getFileData(fileId: string, nodeId?: string): Promise<any>;
32
+ /**
33
+ * Figma 노드 정보 가져오기
34
+ */
35
+ getNodeData(fileId: string, nodeId: string): Promise<any>;
36
+ /**
37
+ * MCP 서버 연결 상태 확인
38
+ */
39
+ isAvailable(): Promise<boolean>;
40
+ }
@@ -0,0 +1,171 @@
1
+ import axios from 'axios';
2
+ /**
3
+ * Figma Desktop MCP 서버와 통신하는 클라이언트
4
+ * HTTP 기반 MCP 서버와 JSON-RPC 2.0 프로토콜로 통신
5
+ */
6
+ export class FigmaMCPClient {
7
+ client;
8
+ baseUrl;
9
+ requestId = 0;
10
+ constructor(baseUrl = 'http://127.0.0.1:3845/mcp') {
11
+ this.baseUrl = baseUrl;
12
+ this.client = axios.create({
13
+ baseURL: baseUrl,
14
+ timeout: 30000,
15
+ headers: {
16
+ 'Content-Type': 'application/json',
17
+ },
18
+ });
19
+ }
20
+ /**
21
+ * JSON-RPC 2.0 요청 생성
22
+ */
23
+ createRequest(method, params) {
24
+ return {
25
+ jsonrpc: '2.0',
26
+ id: ++this.requestId,
27
+ method,
28
+ params: params || {},
29
+ };
30
+ }
31
+ /**
32
+ * MCP 서버에 요청 전송
33
+ * HTTP 기반 MCP 서버는 JSON-RPC 2.0 프로토콜을 사용
34
+ */
35
+ async sendRequest(method, params) {
36
+ try {
37
+ const request = this.createRequest(method, params);
38
+ // HTTP 기반 MCP 서버에 POST 요청
39
+ const response = await this.client.post('', request);
40
+ if (response.data.error) {
41
+ throw new Error(`MCP Error: ${response.data.error.message || 'Unknown error'}`);
42
+ }
43
+ return response.data.result;
44
+ }
45
+ catch (error) {
46
+ if (axios.isAxiosError(error)) {
47
+ // 연결 실패 시 null 반환 (폴백을 위해)
48
+ if (error.code === 'ECONNREFUSED' ||
49
+ error.code === 'ETIMEDOUT' ||
50
+ error.response?.status === 404) {
51
+ console.warn(`Figma MCP 서버 연결 실패 (${error.code || error.response?.status}), REST API로 폴백합니다.`);
52
+ return null;
53
+ }
54
+ throw new Error(`Figma MCP connection error: ${error.message}`);
55
+ }
56
+ throw error;
57
+ }
58
+ }
59
+ /**
60
+ * 사용 가능한 도구 목록 가져오기
61
+ */
62
+ async listTools() {
63
+ const result = await this.sendRequest('tools/list');
64
+ return result?.tools || [];
65
+ }
66
+ /**
67
+ * MCP 도구 호출
68
+ */
69
+ async callTool(toolName, args) {
70
+ try {
71
+ const result = await this.sendRequest('tools/call', {
72
+ name: toolName,
73
+ arguments: args,
74
+ });
75
+ return result;
76
+ }
77
+ catch (error) {
78
+ console.warn(`Figma MCP 도구 ${toolName} 호출 실패:`, error);
79
+ return null;
80
+ }
81
+ }
82
+ /**
83
+ * Figma 파일 데이터 가져오기
84
+ * Figma MCP 서버의 도구를 사용하여 파일 정보 가져오기
85
+ */
86
+ async getFileData(fileId, nodeId) {
87
+ try {
88
+ // 먼저 도구 목록을 확인
89
+ const tools = await this.listTools();
90
+ if (!tools || tools.length === 0) {
91
+ return null;
92
+ }
93
+ // 파일 데이터를 가져오는 도구 찾기
94
+ // Figma Desktop MCP 서버가 제공하는 일반적인 도구 이름들 시도
95
+ const possibleToolNames = [
96
+ 'get_file',
97
+ 'getFile',
98
+ 'fetch_file',
99
+ 'fetchFile',
100
+ 'get_figma_file',
101
+ 'figma_get_file',
102
+ ];
103
+ for (const toolName of possibleToolNames) {
104
+ const tool = tools.find((t) => t.name === toolName);
105
+ if (tool) {
106
+ const params = { fileId };
107
+ if (nodeId) {
108
+ params.nodeId = nodeId;
109
+ }
110
+ const result = await this.callTool(toolName, params);
111
+ if (result) {
112
+ return result;
113
+ }
114
+ }
115
+ }
116
+ // 도구를 찾지 못한 경우 null 반환 (폴백 사용)
117
+ return null;
118
+ }
119
+ catch (error) {
120
+ console.warn('Figma MCP 파일 데이터 가져오기 실패:', error);
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Figma 노드 정보 가져오기
126
+ */
127
+ async getNodeData(fileId, nodeId) {
128
+ try {
129
+ const tools = await this.listTools();
130
+ if (!tools || tools.length === 0) {
131
+ return null;
132
+ }
133
+ // 노드 데이터를 가져오는 도구 찾기
134
+ const possibleToolNames = [
135
+ 'get_node',
136
+ 'getNode',
137
+ 'fetch_node',
138
+ 'fetchNode',
139
+ 'get_figma_node',
140
+ 'figma_get_node',
141
+ ];
142
+ for (const toolName of possibleToolNames) {
143
+ const tool = tools.find((t) => t.name === toolName);
144
+ if (tool) {
145
+ const result = await this.callTool(toolName, { fileId, nodeId });
146
+ if (result) {
147
+ return result;
148
+ }
149
+ }
150
+ }
151
+ // 노드 전용 도구가 없으면 파일 도구에 nodeId를 전달하여 시도
152
+ return await this.getFileData(fileId, nodeId);
153
+ }
154
+ catch (error) {
155
+ console.warn('Figma MCP 노드 데이터 가져오기 실패:', error);
156
+ return null;
157
+ }
158
+ }
159
+ /**
160
+ * MCP 서버 연결 상태 확인
161
+ */
162
+ async isAvailable() {
163
+ try {
164
+ await this.listTools();
165
+ return true;
166
+ }
167
+ catch (error) {
168
+ return false;
169
+ }
170
+ }
171
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * 요청 ID 생성 (타임스탬프 + 랜덤 문자열)
3
+ */
4
+ export declare function generateRequestId(): string;
5
+ /**
6
+ * 요청별 폴더 경로 생성
7
+ */
8
+ export declare function getRequestFolderPath(requestId: string): string;
9
+ /**
10
+ * 요청 폴더 생성
11
+ */
12
+ export declare function createRequestFolder(requestId: string): Promise<string>;
13
+ /**
14
+ * 파일 저장
15
+ */
16
+ export declare function saveFile(requestId: string, filename: string, content: string): Promise<string>;
17
+ /**
18
+ * 바이너리 파일 저장 (이미지 등)
19
+ */
20
+ export declare function saveBinaryFile(requestId: string, filename: string, content: Buffer): Promise<string>;
21
+ /**
22
+ * 요청 메타데이터 저장
23
+ */
24
+ export interface RequestMetadata {
25
+ requestId: string;
26
+ type: 'react' | 'vue';
27
+ componentName: string;
28
+ figmaUrl: string;
29
+ nodeId?: string;
30
+ createdAt: string;
31
+ files: Array<{
32
+ name: string;
33
+ path: string;
34
+ }>;
35
+ }
36
+ export declare function saveMetadata(requestId: string, metadata: Omit<RequestMetadata, 'requestId' | 'createdAt' | 'files'>): Promise<string>;