figma-mcp-lightweight 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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,131 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.6.1] - 2025-08-02
9
+
10
+ ### Fixed
11
+ - **`set_stroke_color` Tool**: Corrected a validation rule that incorrectly rejected a `strokeWeight` of `0`. This change allows for the creation of invisible strokes, aligning the tool's behavior with Figma's capabilities. (Thanks to [Taylor Smits](https://github.com/smitstay) - [PR #16](https://github.com/arinspunk/claude-talk-to-figma-mcp/pull/16))
12
+
13
+ ## [0.6.0] - 2025-07-15
14
+
15
+ ### Added
16
+ - **🚀 DXT Package Support**: Complete implementation of Anthropic's Desktop Extensions format for Claude Desktop
17
+ - **📦 Automated CI/CD Pipeline**: GitHub Actions workflow for automatic DXT package generation and release distribution
18
+ - **🔧 DXT Build Scripts**: New npm scripts for DXT packaging (`pack`, `build:dxt`, `sync-version`)
19
+ - **📋 .dxtignore Configuration**: Optimized package exclusions for minimal DXT file size (11.6MB compressed)
20
+ - **🎯 Dual Distribution Strategy**: NPM registry for developers + DXT packages for end users
21
+
22
+ ### Changed
23
+ - **⚡ Installation Experience**: Reduced setup time from 15-30 minutes to 2-5 minutes via one-click DXT installation
24
+ - **📖 Documentation**: Enhanced README with comprehensive DXT installation instructions and troubleshooting
25
+ - **🏗️ Build Process**: Improved version synchronization between package.json and manifest.json
26
+ - **🔄 Release Workflow**: Automated DXT package attachment to GitHub releases
27
+
28
+ ### Technical Details
29
+ - Added `@anthropic-ai/dxt@^0.2.0` development dependency for DXT packaging
30
+ - Implemented robust error handling and validation in CI/CD pipeline
31
+ - Enhanced build artifacts with 90-day retention for testing and rollback capabilities
32
+ - Established quality gates ensuring DXT packages only build after successful test suites
33
+
34
+ ### Credits
35
+ - **DXT Implementation**: [Taylor Smits](https://github.com/smitstay) - [PR #17](https://github.com/arinspunk/claude-talk-to-figma-mcp/pull/17)
36
+
37
+ ## [0.5.3] - 2025-06-20
38
+
39
+ ### Added
40
+ - Added Windows-specific build command (`build:win`: `tsup`) for improved cross-platform compatibility
41
+ - Enhanced build process to support development on Windows systems without chmod dependency
42
+
43
+ ### Fixed
44
+ - Resolved Windows build compatibility issues where `chmod` command would fail on Windows systems
45
+ - Improved developer experience for Windows users by providing dedicated build script
46
+
47
+ ### Changed
48
+ - Separated Unix/Linux build process (with executable permissions) from Windows build process
49
+ - Updated installation documentation to reflect platform-specific build commands
50
+
51
+ ## [0.5.2] - 2025-06-19
52
+
53
+ ### Fixed
54
+ - Fixed critical opacity handling bug in `set_stroke_color` where `a: 0` (transparent) was incorrectly converted to `a: 1` (opaque)
55
+ - Fixed stroke weight handling where `strokeWeight: 0` (no border) was incorrectly converted to `strokeWeight: 1`
56
+ - Resolved problematic `||` operator usage that affected falsy values in color and stroke operations
57
+
58
+ ### Added
59
+ - Extended `applyDefault()` utility function to handle stroke weight defaults safely
60
+ - Added `FIGMA_DEFAULTS.stroke.weight` constant for centralized stroke configuration
61
+ - Comprehensive test suite for `set_stroke_color` covering edge cases and integration scenarios
62
+ - Enhanced validation for RGB components in stroke operations
63
+
64
+ ### Changed
65
+ - Improved architectural consistency by applying the same safe defaults pattern from `set_fill_color` to `set_stroke_color`
66
+ - Enhanced separation of concerns between MCP layer (business logic) and Figma plugin (pure translator)
67
+ - Renamed `weight` parameter to `strokeWeight` for better clarity and consistency
68
+ - Updated Figma plugin to expect complete data from MCP layer instead of handling defaults internally
69
+
70
+ ### Technical Details
71
+ - Replaced `strokeWeight: strokeWeight || 1` with `applyDefault(strokeWeight, FIGMA_DEFAULTS.stroke.weight)`
72
+ - Enhanced type safety with proper `Color` and `ColorWithDefaults` interface usage
73
+ - Improved error messages and validation for better debugging experience
74
+
75
+ ## [0.5.1] - 2025-06-15
76
+
77
+ ### Fixed
78
+ - Fixed opacity handling in `set_fill_color` to properly respect alpha values
79
+ - Added `applyColorDefaults` function to ensure appropriate default values for colors
80
+
81
+ ### Added
82
+ - Added automated tests for color functions and node manipulation
83
+
84
+ ### Changed
85
+ - Improved TypeScript typing for colors and related properties
86
+ - General code cleanup and better utility organization
87
+
88
+ ## [0.5.0] - 2025-05-28
89
+
90
+ ### Changed
91
+ - Implemented modular tool structure for better maintainability
92
+ - Enhanced handling of complex operations with timeouts and chunking
93
+ - Improved error handling and recovery for all tools
94
+ - Improved TypeScript typing and standardized error handling
95
+
96
+ ### Fixed
97
+ - Fixed channel connection issues with improved state management
98
+ - Resolved timeout problems in `flatten_node`, `create_component_instance`, and `set_effect_style_id`
99
+ - Enhanced remote component access with better error handling
100
+
101
+ ### Added
102
+ - Comprehensive documentation of tool categories and capabilities
103
+
104
+ ## [0.4.0] - 2025-04-15
105
+
106
+ ### Added
107
+ - New tools for creating advanced shapes:
108
+ - `create_ellipse`: Creation of ellipses and circles
109
+ - `create_polygon`: Creation of polygons with customizable sides
110
+ - `create_star`: Creation of stars with customizable points and inner radius
111
+ - `create_vector`: Creation of complex vector shapes
112
+ - `create_line`: Creation of straight lines
113
+ - Advanced text and font manipulation capabilities
114
+ - New commands for controlling typography: font styles, spacing, text case, and more
115
+ - Support for accessing team library components
116
+ - Improved error handling and timeout management
117
+ - Enhanced text scanning capabilities
118
+
119
+ ### Changed
120
+ - Improvements in documentation and usage examples
121
+
122
+ ## [0.3.0] - 2025-03-10
123
+
124
+ ### Added
125
+ - Added `set_auto_layout` command to configure auto layout properties for frames and groups
126
+ - Support for settings for layout direction, padding, item spacing, alignment and more
127
+
128
+ ## [0.2.0] - 2025-02-01
129
+
130
+ ### Added
131
+ - Initial public release with Claude Desktop support
package/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Github User sonnylazuardi (original: cursor-talk-to-figma-mcp)
4
+ Copyright (c) 2025 Github User arinspunk (modification: Cursor to Claude)
5
+ Copyright (c) 2025 Igor Halilović (modification: lightweight fork with full Figma API access)
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
package/TESTING.md ADDED
@@ -0,0 +1,216 @@
1
+ # Testing Guide for Claude Talk to Figma MCP
2
+
3
+ This document provides a detailed guide for testing the Claude Talk to Figma MCP project, including both automated tests and manual integration tests.
4
+
5
+ ## Testing Approaches
6
+
7
+ The project uses two complementary testing approaches:
8
+
9
+ 1. **Automated Tests**: Unit and component integration tests using Jest
10
+ 2. **Manual Integration Tests**: End-to-end tests for the complete Claude-MCP-Figma workflow
11
+
12
+ ## Prerequisites
13
+
14
+ Before starting the tests, make sure you have:
15
+
16
+ - Claude Desktop installed
17
+ - Figma account with plugin creation access
18
+ - Bun installed (v1.0.0 or higher)
19
+ - Permissions to install plugins in Figma
20
+
21
+ ## Automated Tests
22
+
23
+ ### Running Automated Tests
24
+
25
+ ```bash
26
+ # Run all automated tests
27
+ bun run test
28
+
29
+ # Run in watch mode (re-runs on file changes)
30
+ bun run test:watch
31
+
32
+ # Run with coverage report
33
+ bun run test:coverage
34
+ ```
35
+
36
+ ### Test Categories
37
+
38
+ 1. **Unit Tests** (`tests/unit/`):
39
+ - Test individual functions and utilities in isolation
40
+ - Verify edge cases and error handling
41
+ - Example: `defaults.test.ts` - Tests the proper handling of falsy values
42
+
43
+ 2. **Integration Tests** (`tests/integration/`):
44
+ - Test interactions between multiple components
45
+ - Verify that components work together correctly
46
+ - Example: `set-fill-color.test.ts` - Tests the opacity handling in fill colors
47
+
48
+ ### Adding New Tests
49
+
50
+ 1. For unit tests:
51
+ - Create a file in the appropriate directory under `tests/unit/`
52
+ - Name the file `*.test.ts` to be detected by Jest
53
+
54
+ 2. For integration tests:
55
+ - Create a file in the `tests/integration/` directory
56
+ - Use the test fixtures in `tests/fixtures/` for test data
57
+
58
+ ## Manual Integration Tests
59
+
60
+ These tests verify the complete end-to-end workflow between Claude Desktop, the MCP server, and Figma.
61
+
62
+ ### Running Integration Tests
63
+
64
+ ```bash
65
+ bun run test:integration
66
+ ```
67
+
68
+ This script will guide you through the complete testing process.
69
+
70
+ ## Test Cases
71
+
72
+ ### 1. Environment Setup
73
+
74
+ | Test case | Steps | Expected result |
75
+ | -------------- | ----- | ------------------ |
76
+ | Dependencies installation | Run `bun install` | All dependencies are installed without errors |
77
+ | Claude configuration | Run `bun run configure-claude` | Script executed correctly, successful configuration message |
78
+ | Verify configuration | Check `claude_desktop_config.json` file | Contains configuration for "ClaudeTalkToFigma" |
79
+
80
+ ### 2. WebSocket Server Configuration
81
+
82
+ | Test case | Steps | Expected result |
83
+ | -------------- | ----- | ------------------ |
84
+ | Start WebSocket server | Run `bun socket` | Server starts on port 3055, shows confirmation message |
85
+ | Verify server status | Access `http://localhost:3055/status` | Returns JSON with "running" status and statistics |
86
+ | Test reconnection | Stop and restart the server | Client reconnects automatically |
87
+
88
+ ### 3. Figma Plugin Setup
89
+
90
+ #### Install the Figma Plugin
91
+
92
+ 1. Open Figma and go to **Menu > Plugins > Development > New Plugin**
93
+ 2. Select "Link existing plugin"
94
+ 3. Navigate to and select the folder `src/claude_mcp_plugin` from this repository
95
+
96
+ #### Connect Plugin to WebSocket Server
97
+
98
+ 1. The plugin will ask for a port number (default: 3055)
99
+ 2. Enter the port number where your WebSocket server is running
100
+ 3. Click "Connect"
101
+ 4. You should see a "Connected to Claude MCP server" message
102
+
103
+ #### Integration Test
104
+
105
+ To test if the Figma plugin is correctly communicating with the Claude MCP server:
106
+
107
+ 1. Start the WebSocket server
108
+ 2. Open Figma and run the Claude MCP Plugin from your Development plugins
109
+ 3. Connect to the WebSocket server
110
+ 4. Open Claude Desktop and select the "ClaudeTalkToFigma" MCP
111
+ 5. Test a simple command in Claude like: "Can you show me information about my current Figma document?"
112
+
113
+ Claude should be able to communicate with Figma and return information about the document.
114
+
115
+ ### 4. Claude-MCP-Figma Integration Tests
116
+
117
+ | Test case | Steps | Expected result |
118
+ | -------------- | ----- | ------------------ |
119
+ | Get document info | Ask Claude about the open document | Claude returns information about the document |
120
+ | Get selection | Select element in Figma and ask Claude | Claude returns details of the selected element |
121
+ | Create element | Ask Claude to create a rectangle | Rectangle created in Figma document |
122
+ | Modify element | Ask Claude to change color of an element | Element color changed correctly |
123
+ | Complex operation | Ask Claude to find text and modify it | Text correctly modified in multiple nodes |
124
+
125
+ ## Common Problems and Solutions
126
+
127
+ ### Connection Problems
128
+
129
+ | Problem | Possible cause | Solution |
130
+ | -------- | ------------- | -------- |
131
+ | "Cannot connect to WebSocket server" | Server is not running | Run `bun socket` in terminal |
132
+ | "Connection error: port in use" | Port 3055 is occupied | Free the port or change port configuration |
133
+ | "Cannot connect from plugin" | CORS restrictions | Verify that the plugin uses the correct domain |
134
+ | "Connection rejected" | Firewall blocking connection | Allow connections to port 3055 in firewall |
135
+
136
+ ### Problems with Claude Desktop
137
+
138
+ | Problem | Possible cause | Solution |
139
+ | -------- | ------------- | -------- |
140
+ | "MCP does not appear in Claude Desktop" | Incorrect configuration | Verify configuration file and run `bun run configure-claude` |
141
+ | "Claude does not respond to Figma commands" | MCP not selected | Select "ClaudeTalkToFigma" in the MCPs menu |
142
+ | "Error executing MCP command" | Missing dependencies | Reinstall with `bun install` |
143
+ | "Claude cannot execute commands in Figma" | Channel not joined | Verify that `join_channel` was executed |
144
+
145
+ ### Problems with Figma
146
+
147
+ | Problem | Possible cause | Solution |
148
+ | -------- | ------------- | -------- |
149
+ | "Plugin does not appear in Figma" | Incorrect import | Verify path and reimport the plugin |
150
+ | "Error executing commands in Figma" | Insufficient permissions | Verify permissions in manifest.json |
151
+ | "Cannot modify elements" | Document in read-only mode | Open document in edit mode |
152
+ | "Error creating elements" | Incorrect selection | Verify that the target page or frame is selected |
153
+
154
+ ## Diagnostics and Debugging
155
+
156
+ ### Diagnostic Tools
157
+
158
+ 1. **WebSocket Server Logs**:
159
+ - Detailed logs are shown in the terminal where you run `bun socket`
160
+ - Look for ERROR or WARN messages to identify problems
161
+
162
+ 2. **Status Endpoint**:
163
+ - Access `http://localhost:3055/status` to verify statistics
164
+ - Check active connections and accumulated errors
165
+
166
+ 3. **Figma Console**:
167
+ - Open the development console in Figma (F12 or Cmd+Option+I)
168
+ - Review error messages related to the plugin
169
+
170
+ 4. **Configuration Verification**:
171
+ - Examine `claude_desktop_config.json` to confirm correct configuration
172
+
173
+ ### Systematic Debugging Steps
174
+
175
+ 1. **Verify Individual Components**:
176
+ - Confirm that the WebSocket server is running
177
+ - Verify that the Figma plugin can be opened
178
+ - Check that Claude Desktop recognizes the MCP
179
+
180
+ 2. **Test Communication in Parts**:
181
+ - Test the plugin's connection to the WebSocket directly
182
+ - Verify that Claude can execute basic MCP commands
183
+ - Confirm that commands reach the Figma plugin
184
+
185
+ 3. **Restart Components in Order**:
186
+ - Restart the WebSocket server
187
+ - Reload the plugin in Figma
188
+ - Restart Claude Desktop
189
+
190
+ 4. **Update Versions**:
191
+ - Make sure you have the latest versions of all dependencies
192
+ - Verify compatibility with the current version of Figma
193
+
194
+ ## Comprehensive Testing Checklist
195
+
196
+ - [ ] Claude Desktop configuration completed
197
+ - [ ] WebSocket server started and running
198
+ - [ ] Figma plugin installed and connected
199
+ - [ ] Claude Desktop can get document information
200
+ - [ ] Claude Desktop can get current selection
201
+ - [ ] Claude Desktop can create new elements
202
+ - [ ] Claude Desktop can modify existing elements
203
+ - [ ] Claude Desktop can scan and modify text
204
+ - [ ] The system recovers correctly from disconnections
205
+ - [ ] Errors are handled and reported correctly
206
+ - [ ] Automated tests pass successfully
207
+ - [ ] Set fill color handles transparency correctly
208
+
209
+ ## Troubleshooting Automated Tests
210
+
211
+ | Problem | Possible cause | Solution |
212
+ | -------- | ------------- | -------- |
213
+ | Jest tests fail to run | Missing dependencies | Run `bun install` to install all dependencies |
214
+ | Test timeouts | Slow machine or heavy CPU load | Increase timeout in Jest configuration |
215
+ | Mocks not working | Incorrect import paths | Verify mock paths match actual module paths |
216
+ | Type errors in tests | TypeScript configuration issue | Check `tsconfig.json` and Jest TypeScript settings |
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+
3
+ // src/socket.ts
4
+ var logger = {
5
+ info: (message, ...args) => {
6
+ console.log(`[INFO] ${message}`, ...args);
7
+ },
8
+ debug: (message, ...args) => {
9
+ console.log(`[DEBUG] ${message}`, ...args);
10
+ },
11
+ warn: (message, ...args) => {
12
+ console.warn(`[WARN] ${message}`, ...args);
13
+ },
14
+ error: (message, ...args) => {
15
+ console.error(`[ERROR] ${message}`, ...args);
16
+ }
17
+ };
18
+ var channels = /* @__PURE__ */ new Map();
19
+ var stats = {
20
+ totalConnections: 0,
21
+ activeConnections: 0,
22
+ messagesSent: 0,
23
+ messagesReceived: 0,
24
+ errors: 0
25
+ };
26
+ function handleConnection(ws) {
27
+ stats.totalConnections++;
28
+ stats.activeConnections++;
29
+ const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
30
+ ws.data = { clientId };
31
+ logger.info(`New client connected: ${clientId}`);
32
+ try {
33
+ ws.send(JSON.stringify({
34
+ type: "system",
35
+ message: "Please join a channel to start communicating with Figma"
36
+ }));
37
+ } catch (error) {
38
+ logger.error(`Failed to send welcome message to client ${clientId}:`, error);
39
+ stats.errors++;
40
+ }
41
+ ws.close = () => {
42
+ logger.info(`Client disconnected: ${clientId}`);
43
+ stats.activeConnections--;
44
+ channels.forEach((clients, channelName) => {
45
+ if (clients.has(ws)) {
46
+ clients.delete(ws);
47
+ logger.debug(`Removed client ${clientId} from channel: ${channelName}`);
48
+ try {
49
+ clients.forEach((client) => {
50
+ if (client.readyState === WebSocket.OPEN) {
51
+ client.send(JSON.stringify({
52
+ type: "system",
53
+ message: "A client has left the channel",
54
+ channel: channelName
55
+ }));
56
+ stats.messagesSent++;
57
+ }
58
+ });
59
+ } catch (error) {
60
+ logger.error(`Error notifying channel ${channelName} about client disconnect:`, error);
61
+ stats.errors++;
62
+ }
63
+ }
64
+ });
65
+ };
66
+ }
67
+ var server = Bun.serve({
68
+ port: 3055,
69
+ // uncomment this to allow connections in windows wsl
70
+ // hostname: "0.0.0.0",
71
+ fetch(req, server2) {
72
+ const url = new URL(req.url);
73
+ logger.debug(`Received ${req.method} request to ${url.pathname}`);
74
+ if (req.method === "OPTIONS") {
75
+ return new Response(null, {
76
+ headers: {
77
+ "Access-Control-Allow-Origin": "*",
78
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
79
+ "Access-Control-Allow-Headers": "Content-Type, Authorization"
80
+ }
81
+ });
82
+ }
83
+ if (url.pathname === "/status") {
84
+ return new Response(JSON.stringify({
85
+ status: "running",
86
+ uptime: process.uptime(),
87
+ stats
88
+ }), {
89
+ headers: {
90
+ "Content-Type": "application/json",
91
+ "Access-Control-Allow-Origin": "*"
92
+ }
93
+ });
94
+ }
95
+ try {
96
+ const success = server2.upgrade(req, {
97
+ headers: {
98
+ "Access-Control-Allow-Origin": "*"
99
+ }
100
+ });
101
+ if (success) {
102
+ return;
103
+ }
104
+ } catch (error) {
105
+ logger.error("Failed to upgrade WebSocket connection:", error);
106
+ stats.errors++;
107
+ return new Response("Failed to upgrade to WebSocket", { status: 500 });
108
+ }
109
+ return new Response("Claude to Figma WebSocket server running. Try connecting with a WebSocket client.", {
110
+ headers: {
111
+ "Content-Type": "text/plain",
112
+ "Access-Control-Allow-Origin": "*"
113
+ }
114
+ });
115
+ },
116
+ websocket: {
117
+ open: handleConnection,
118
+ message(ws, message) {
119
+ try {
120
+ stats.messagesReceived++;
121
+ const clientId = ws.data?.clientId || "unknown";
122
+ logger.debug(`Received message from client ${clientId}:`, typeof message === "string" ? message : "<binary>");
123
+ const data = JSON.parse(message);
124
+ if (data.type === "join") {
125
+ const channelName = data.channel;
126
+ if (!channelName || typeof channelName !== "string") {
127
+ logger.warn(`Client ${clientId} attempted to join without a valid channel name`);
128
+ ws.send(JSON.stringify({
129
+ type: "error",
130
+ message: "Channel name is required"
131
+ }));
132
+ stats.messagesSent++;
133
+ return;
134
+ }
135
+ if (!channels.has(channelName)) {
136
+ logger.info(`Creating new channel: ${channelName}`);
137
+ channels.set(channelName, /* @__PURE__ */ new Set());
138
+ }
139
+ const channelClients = channels.get(channelName);
140
+ channelClients.add(ws);
141
+ logger.info(`Client ${clientId} joined channel: ${channelName}`);
142
+ try {
143
+ ws.send(JSON.stringify({
144
+ type: "system",
145
+ message: `Joined channel: ${channelName}`,
146
+ channel: channelName
147
+ }));
148
+ stats.messagesSent++;
149
+ ws.send(JSON.stringify({
150
+ type: "system",
151
+ message: {
152
+ id: data.id,
153
+ result: "Connected to channel: " + channelName
154
+ },
155
+ channel: channelName
156
+ }));
157
+ stats.messagesSent++;
158
+ logger.debug(`Connection confirmation sent to client ${clientId} for channel ${channelName}`);
159
+ } catch (error) {
160
+ logger.error(`Failed to send join confirmation to client ${clientId}:`, error);
161
+ stats.errors++;
162
+ }
163
+ try {
164
+ let notificationCount = 0;
165
+ channelClients.forEach((client) => {
166
+ if (client !== ws && client.readyState === WebSocket.OPEN) {
167
+ client.send(JSON.stringify({
168
+ type: "system",
169
+ message: "A new client has joined the channel",
170
+ channel: channelName
171
+ }));
172
+ stats.messagesSent++;
173
+ notificationCount++;
174
+ }
175
+ });
176
+ if (notificationCount > 0) {
177
+ logger.debug(`Notified ${notificationCount} other clients in channel ${channelName}`);
178
+ }
179
+ } catch (error) {
180
+ logger.error(`Error notifying channel about new client:`, error);
181
+ stats.errors++;
182
+ }
183
+ return;
184
+ }
185
+ if (data.type === "message") {
186
+ const channelName = data.channel;
187
+ if (!channelName || typeof channelName !== "string") {
188
+ logger.warn(`Client ${clientId} sent message without a valid channel name`);
189
+ ws.send(JSON.stringify({
190
+ type: "error",
191
+ message: "Channel name is required"
192
+ }));
193
+ stats.messagesSent++;
194
+ return;
195
+ }
196
+ const channelClients = channels.get(channelName);
197
+ if (!channelClients || !channelClients.has(ws)) {
198
+ logger.warn(`Client ${clientId} attempted to send to channel ${channelName} without joining first`);
199
+ ws.send(JSON.stringify({
200
+ type: "error",
201
+ message: "You must join the channel first"
202
+ }));
203
+ stats.messagesSent++;
204
+ return;
205
+ }
206
+ try {
207
+ let broadcastCount = 0;
208
+ channelClients.forEach((client) => {
209
+ if (client.readyState === WebSocket.OPEN) {
210
+ logger.debug(`Broadcasting message to client in channel ${channelName}`);
211
+ client.send(JSON.stringify({
212
+ type: "broadcast",
213
+ message: data.message,
214
+ sender: client === ws ? "You" : "User",
215
+ channel: channelName
216
+ }));
217
+ stats.messagesSent++;
218
+ broadcastCount++;
219
+ }
220
+ });
221
+ logger.info(`Broadcasted message to ${broadcastCount} clients in channel ${channelName}`);
222
+ } catch (error) {
223
+ logger.error(`Error broadcasting message to channel ${channelName}:`, error);
224
+ stats.errors++;
225
+ }
226
+ }
227
+ if (data.type === "progress_update") {
228
+ const channelName = data.channel;
229
+ if (!channelName || typeof channelName !== "string") {
230
+ logger.warn(`Client ${clientId} sent progress update without a valid channel name`);
231
+ return;
232
+ }
233
+ const channelClients = channels.get(channelName);
234
+ if (!channelClients) {
235
+ logger.warn(`Progress update for non-existent channel: ${channelName}`);
236
+ return;
237
+ }
238
+ logger.debug(`Progress update for command ${data.id} in channel ${channelName}: ${data.message?.data?.status || "unknown"} - ${data.message?.data?.progress || 0}%`);
239
+ try {
240
+ channelClients.forEach((client) => {
241
+ if (client.readyState === WebSocket.OPEN) {
242
+ client.send(JSON.stringify(data));
243
+ stats.messagesSent++;
244
+ }
245
+ });
246
+ } catch (error) {
247
+ logger.error(`Error broadcasting progress update:`, error);
248
+ stats.errors++;
249
+ }
250
+ }
251
+ } catch (err) {
252
+ stats.errors++;
253
+ logger.error("Error handling message:", err);
254
+ try {
255
+ ws.send(JSON.stringify({
256
+ type: "error",
257
+ message: "Error processing your message: " + (err instanceof Error ? err.message : String(err))
258
+ }));
259
+ stats.messagesSent++;
260
+ } catch (sendError) {
261
+ logger.error("Failed to send error message to client:", sendError);
262
+ }
263
+ }
264
+ },
265
+ close(ws, code, reason) {
266
+ const clientId = ws.data?.clientId || "unknown";
267
+ logger.info(`WebSocket closed for client ${clientId}: Code ${code}, Reason: ${reason || "No reason provided"}`);
268
+ channels.forEach((clients, channelName) => {
269
+ if (clients.delete(ws)) {
270
+ logger.debug(`Removed client ${clientId} from channel ${channelName} due to connection close`);
271
+ }
272
+ });
273
+ stats.activeConnections--;
274
+ },
275
+ drain(ws) {
276
+ const clientId = ws.data?.clientId || "unknown";
277
+ logger.debug(`WebSocket backpressure relieved for client ${clientId}`);
278
+ }
279
+ }
280
+ });
281
+ logger.info(`Claude to Figma WebSocket server running on port ${server.port}`);
282
+ logger.info(`Status endpoint available at http://localhost:${server.port}/status`);
283
+ setInterval(() => {
284
+ logger.info("Server stats:", {
285
+ channels: channels.size,
286
+ ...stats
287
+ });
288
+ }, 5 * 60 * 1e3);
289
+ //# sourceMappingURL=socket.cjs.map