figcast 0.5.6

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/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # Figcast
2
+
3
+ Send HTML designs from Claude directly into Figma — instantly.
4
+
5
+ Figcast is a relay server that bridges Claude Code (or any AI agent) to the Figcast Figma plugin via MCP.
6
+
7
+ ---
8
+
9
+ ## Setup (one time)
10
+
11
+ **Step 1** — Open any terminal and run:
12
+
13
+ ```bash
14
+ npm install -g figcast
15
+ ```
16
+
17
+ **Step 2** — Run once to register:
18
+
19
+ ```bash
20
+ figcast
21
+ ```
22
+
23
+ It registers itself in Claude Code automatically and prints a confirmation. After this, Claude Code starts the server in the background every time it launches.
24
+
25
+ **Step 3** — Restart Claude Code.
26
+
27
+ **Step 4** — Open the Figcast Figma plugin → go to **MCP Connect** → click **Connect**.
28
+
29
+ Done. Claude can now send designs directly into Figma.
30
+
31
+ ---
32
+
33
+ ## How it works
34
+
35
+ ```
36
+ Claude Code ──MCP stdio──► figcast ──HTTP poll──► Figma plugin
37
+ ```
38
+
39
+ 1. Claude Code runs `figcast` as an MCP subprocess
40
+ 2. When you ask Claude to build a UI, it calls the `send_to_figma` tool
41
+ 3. The plugin polls `localhost:3131/poll` every 2 seconds and picks up the design
42
+ 4. The design is rendered as a Figma frame instantly
43
+
44
+ ---
45
+
46
+ ## If auto-registration fails
47
+
48
+ ```bash
49
+ claude mcp add --scope user figcast -- figcast
50
+ ```
51
+
52
+ ---
53
+
54
+ Made by [Arzisoft](https://arzisoft.com) — Software Studio · AI Consulting
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "figcast",
3
+ "version": "0.5.6",
4
+ "description": "Relay server for Figcast — connects Claude Code to Figma via MCP",
5
+ "main": "server.js",
6
+ "bin": {
7
+ "figcast": "server.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node server.js"
11
+ },
12
+ "keywords": [
13
+ "figma",
14
+ "mcp",
15
+ "claude",
16
+ "html",
17
+ "design",
18
+ "figcast",
19
+ "arzisoft"
20
+ ],
21
+ "author": "Arzisoft",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/Arzisoft/html-to-design.git"
26
+ },
27
+ "homepage": "https://github.com/Arzisoft/html-to-design",
28
+ "engines": {
29
+ "node": ">=16"
30
+ }
31
+ }
@@ -0,0 +1,67 @@
1
+ // Arzisoft HTML to Design — CLI Sender
2
+ // Usage: node send-to-figma.js <path-to-html> [frame-name] [frame-width]
3
+
4
+ const fs = require('fs');
5
+ const http = require('http');
6
+ const path = require('path');
7
+
8
+ const filePath = process.argv[2];
9
+ const frameName = process.argv[3] || (filePath ? path.basename(filePath, '.html') : 'Arzisoft Frame');
10
+ const frameWidth = parseInt(process.argv[4]) || 1440;
11
+
12
+ if (!filePath) {
13
+ console.error('Usage: node send-to-figma.js <path-to-html> [frame-name] [frame-width]');
14
+ process.exit(1);
15
+ }
16
+
17
+ if (!fs.existsSync(filePath)) {
18
+ console.error('File not found: ' + filePath);
19
+ process.exit(1);
20
+ }
21
+
22
+ const html = fs.readFileSync(filePath, 'utf8');
23
+
24
+ const body = JSON.stringify({
25
+ frameName,
26
+ frameWidth,
27
+ html,
28
+ css: '',
29
+ centerOnViewport: true,
30
+ selectAfterGeneration: true
31
+ });
32
+
33
+ const req = http.request({
34
+ hostname: 'localhost',
35
+ port: 3131,
36
+ path: '/generate',
37
+ method: 'POST',
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ 'Content-Length': Buffer.byteLength(body)
41
+ }
42
+ }, res => {
43
+ let d = '';
44
+ res.on('data', c => d += c);
45
+ res.on('end', () => {
46
+ try {
47
+ const result = JSON.parse(d);
48
+ if (result.ok) {
49
+ console.log('✓ Queued "' + frameName + '" (' + frameWidth + 'px) — waiting for Figma plugin to pick up');
50
+ } else {
51
+ console.error('✗ ' + result.message);
52
+ process.exit(1);
53
+ }
54
+ } catch (e) {
55
+ console.error('✗ Could not parse server response');
56
+ process.exit(1);
57
+ }
58
+ });
59
+ });
60
+
61
+ req.on('error', () => {
62
+ console.error('✗ Could not reach relay server. Is it running? (node server.js)');
63
+ process.exit(1);
64
+ });
65
+
66
+ req.write(body);
67
+ req.end();
package/send.js ADDED
@@ -0,0 +1,53 @@
1
+ // Usage: node send.js <path-to-html-file> [frame-name] [frame-width]
2
+ const fs = require('fs');
3
+ const http = require('http');
4
+ const path = require('path');
5
+
6
+ const filePath = process.argv[2];
7
+ const frameName = process.argv[3] || path.basename(filePath, '.html');
8
+ const frameWidth = parseInt(process.argv[4]) || 1440;
9
+
10
+ if (!filePath) {
11
+ console.error('Usage: node send.js <path-to-html> [frame-name] [frame-width]');
12
+ process.exit(1);
13
+ }
14
+
15
+ const html = fs.readFileSync(filePath, 'utf8');
16
+
17
+ const body = JSON.stringify({
18
+ frameName,
19
+ frameWidth,
20
+ autoLayout: true,
21
+ centerOnViewport: true,
22
+ selectAfterGeneration: true,
23
+ parseInlineStyles: true,
24
+ parseCSSClasses: true,
25
+ html,
26
+ css: ''
27
+ });
28
+
29
+ const req = http.request({
30
+ hostname: 'localhost',
31
+ port: 3131,
32
+ path: '/design',
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ 'Content-Length': Buffer.byteLength(body)
37
+ }
38
+ }, res => {
39
+ let d = '';
40
+ res.on('data', c => d += c);
41
+ res.on('end', () => {
42
+ const result = JSON.parse(d);
43
+ if (result.ok) {
44
+ console.log('Sent to Figma: ' + frameName);
45
+ } else {
46
+ console.error('Error: ' + result.message);
47
+ }
48
+ });
49
+ });
50
+
51
+ req.on('error', () => console.error('Could not reach relay server. Is it running? (node server.js)'));
52
+ req.write(body);
53
+ req.end();
package/server.js ADDED
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env node
2
+ // Figcast — MCP Server + HTTP Relay
3
+ //
4
+ // Two modes (auto-detected):
5
+ // MCP mode — when started by Claude Code (stdin is piped). Implements
6
+ // MCP protocol over stdio AND runs HTTP on port 3131.
7
+ // HTTP mode — when run directly: node server.js. Registers itself in
8
+ // ~/.claude.json automatically on first run, then serves HTTP.
9
+
10
+ const http = require('http');
11
+ const readline = require('readline');
12
+ const os = require('os');
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+
16
+ const HTTP_PORT = 3131;
17
+
18
+ // ── Shared state ───────────────────────────────────────────────
19
+ let pending = null;
20
+ let lastStatus = null;
21
+
22
+ // ── Logging — always to stderr so MCP stdout stays clean ──────
23
+ function log(msg) {
24
+ process.stderr.write('[Figcast] ' + msg + '\n');
25
+ }
26
+
27
+ // ── HTTP Server (Figma plugin polls this) ──────────────────────
28
+ const CORS = {
29
+ 'Access-Control-Allow-Origin': '*',
30
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
31
+ 'Access-Control-Allow-Headers': 'Content-Type',
32
+ 'Content-Type': 'application/json',
33
+ };
34
+
35
+ function readBody(req) {
36
+ return new Promise((resolve, reject) => {
37
+ let body = '';
38
+ req.on('data', chunk => { body += chunk; });
39
+ req.on('end', () => { try { resolve(JSON.parse(body)); } catch(e) { reject(e); } });
40
+ req.on('error', reject);
41
+ });
42
+ }
43
+
44
+ http.createServer(async (req, res) => {
45
+ if (req.method === 'OPTIONS') { res.writeHead(204, CORS); res.end(); return; }
46
+
47
+ if (req.method === 'GET' && req.url === '/health') {
48
+ res.writeHead(200, CORS);
49
+ res.end(JSON.stringify({ ok: true, status: 'Arzisoft MCP server running.' }));
50
+ return;
51
+ }
52
+
53
+ if (req.method === 'GET' && req.url === '/poll') {
54
+ if (!pending) { res.writeHead(204, CORS); res.end(); return; }
55
+ const payload = pending;
56
+ pending = null;
57
+ res.writeHead(200, CORS);
58
+ res.end(JSON.stringify(payload));
59
+ return;
60
+ }
61
+
62
+ if (req.method === 'POST' && req.url === '/generate') {
63
+ try {
64
+ const body = await readBody(req);
65
+ pending = { ...body, receivedAt: new Date().toISOString() };
66
+ log('Queued: ' + (pending.frameName || 'Untitled'));
67
+ res.writeHead(200, CORS);
68
+ res.end(JSON.stringify({ ok: true, message: 'Design queued.' }));
69
+ } catch(e) {
70
+ res.writeHead(400, CORS);
71
+ res.end(JSON.stringify({ ok: false, message: 'Invalid JSON.' }));
72
+ }
73
+ return;
74
+ }
75
+
76
+ if (req.method === 'POST' && req.url === '/status') {
77
+ try {
78
+ const body = await readBody(req);
79
+ lastStatus = { ...body, receivedAt: new Date().toISOString() };
80
+ const icon = body.success ? '✓' : '✗';
81
+ log(icon + ' ' + (body.message || ''));
82
+ res.writeHead(200, CORS);
83
+ res.end(JSON.stringify({ ok: true }));
84
+ } catch(e) {
85
+ res.writeHead(400, CORS);
86
+ res.end(JSON.stringify({ ok: false }));
87
+ }
88
+ return;
89
+ }
90
+
91
+ res.writeHead(404, CORS);
92
+ res.end(JSON.stringify({ ok: false, message: 'Not found.' }));
93
+
94
+ }).on('error', function(err) {
95
+ if (err.code === 'EADDRINUSE') {
96
+ log('Port ' + HTTP_PORT + ' already in use — HTTP relay skipped (MCP still active)');
97
+ } else {
98
+ log('HTTP server error: ' + err.message);
99
+ }
100
+ }).listen(HTTP_PORT, '127.0.0.1', () => {
101
+ log('HTTP relay running on http://localhost:' + HTTP_PORT);
102
+ });
103
+
104
+ // ── MCP Tool definition ────────────────────────────────────────
105
+ const TOOLS = [{
106
+ name: 'send_to_figma',
107
+ description: 'Send HTML (and optional CSS) to the Figcast Figma plugin. The plugin must be open in Figma with the MCP Connect tab connected.',
108
+ inputSchema: {
109
+ type: 'object',
110
+ required: ['html'],
111
+ properties: {
112
+ html: {
113
+ type: 'string',
114
+ description: 'Full HTML to convert into a Figma frame.'
115
+ },
116
+ css: {
117
+ type: 'string',
118
+ description: 'Optional additional CSS to apply alongside the HTML.'
119
+ },
120
+ frameName: {
121
+ type: 'string',
122
+ description: 'Name for the Figma frame. Defaults to "Arzisoft Frame".'
123
+ },
124
+ frameWidth: {
125
+ type: 'number',
126
+ description: 'Frame width in pixels. Defaults to 1440.'
127
+ }
128
+ }
129
+ }
130
+ }];
131
+
132
+ // ── MCP stdio protocol (only when piped by Claude Code) ────────
133
+ if (!process.stdin.isTTY) {
134
+
135
+ function mcpSend(obj) {
136
+ process.stdout.write(JSON.stringify(obj) + '\n');
137
+ }
138
+
139
+ const rl = readline.createInterface({ input: process.stdin, terminal: false });
140
+
141
+ rl.on('line', line => {
142
+ line = line.trim();
143
+ if (!line) return;
144
+ let msg;
145
+ try { msg = JSON.parse(line); } catch(e) { return; }
146
+
147
+ if (msg.method === 'initialize') {
148
+ mcpSend({
149
+ jsonrpc: '2.0', id: msg.id,
150
+ result: {
151
+ protocolVersion: '2024-11-05',
152
+ capabilities: { tools: {} },
153
+ serverInfo: { name: 'figcast', version: '0.5.6' }
154
+ }
155
+ });
156
+
157
+ } else if (msg.method === 'notifications/initialized' || msg.method === 'initialized') {
158
+ // no response needed
159
+
160
+ } else if (msg.method === 'tools/list') {
161
+ mcpSend({ jsonrpc: '2.0', id: msg.id, result: { tools: TOOLS } });
162
+
163
+ } else if (msg.method === 'tools/call') {
164
+ const name = msg.params && msg.params.name;
165
+ const args = (msg.params && msg.params.arguments) || {};
166
+
167
+ if (name === 'send_to_figma') {
168
+ pending = {
169
+ html: args.html || '',
170
+ css: args.css || '',
171
+ frameName: args.frameName || 'Arzisoft Frame',
172
+ frameWidth: args.frameWidth || 1440,
173
+ centerOnViewport: true,
174
+ selectAfterGeneration: true,
175
+ receivedAt: new Date().toISOString()
176
+ };
177
+ log('Queued via MCP: ' + pending.frameName);
178
+ mcpSend({
179
+ jsonrpc: '2.0', id: msg.id,
180
+ result: {
181
+ content: [{
182
+ type: 'text',
183
+ text: '✓ Design queued for "' + pending.frameName + '". Make sure the Figma plugin is open and connected on the MCP Connect tab.'
184
+ }]
185
+ }
186
+ });
187
+ } else {
188
+ mcpSend({
189
+ jsonrpc: '2.0', id: msg.id,
190
+ error: { code: -32601, message: 'Unknown tool: ' + name }
191
+ });
192
+ }
193
+
194
+ } else if (msg.id !== undefined) {
195
+ mcpSend({
196
+ jsonrpc: '2.0', id: msg.id,
197
+ error: { code: -32601, message: 'Method not found: ' + msg.method }
198
+ });
199
+ }
200
+ });
201
+
202
+ rl.on('close', () => process.exit(0));
203
+
204
+ } else {
205
+ // Direct terminal run — self-register into ~/.claude.json, then serve HTTP
206
+
207
+ function selfRegister() {
208
+ const serverPath = __filename;
209
+ const claudeConfigPath = path.join(os.homedir(), '.claude.json');
210
+ try {
211
+ let config = {};
212
+ if (fs.existsSync(claudeConfigPath)) {
213
+ try { config = JSON.parse(fs.readFileSync(claudeConfigPath, 'utf8')); } catch(e) { config = {}; }
214
+ }
215
+ if (config.mcpServers && config.mcpServers['figcast']) {
216
+ const existing = config.mcpServers['figcast'].args;
217
+ if (existing && existing[0] === serverPath) return 'already';
218
+ // Path changed (e.g. user moved the file) — update it
219
+ config.mcpServers['figcast'].args = [serverPath];
220
+ fs.writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2));
221
+ return 'updated';
222
+ }
223
+ if (!config.mcpServers) config.mcpServers = {};
224
+ config.mcpServers['figcast'] = {
225
+ type: 'stdio', command: 'node', args: [serverPath], env: {}
226
+ };
227
+ fs.writeFileSync(claudeConfigPath, JSON.stringify(config, null, 2));
228
+ return 'registered';
229
+ } catch(e) {
230
+ return 'failed:' + e.message;
231
+ }
232
+ }
233
+
234
+ process.stderr.write('\n Figcast — Relay Server\n');
235
+
236
+ const regResult = selfRegister();
237
+ if (regResult === 'registered') {
238
+ process.stderr.write('\n ✓ Registered in Claude Code automatically.\n');
239
+ process.stderr.write(' Restart Claude Code, then click Connect in the Figma plugin.\n');
240
+ } else if (regResult === 'updated') {
241
+ process.stderr.write('\n ✓ Updated path in Claude Code config (file was moved).\n');
242
+ process.stderr.write(' Restart Claude Code if it was already running.\n');
243
+ } else if (regResult === 'already') {
244
+ process.stderr.write(' ✓ Already registered in Claude Code.\n');
245
+ } else {
246
+ process.stderr.write(' ⚠ Could not auto-register: ' + regResult.replace('failed:', '') + '\n');
247
+ process.stderr.write(' Run manually: claude mcp add --scope user figcast -- node "' + __filename + '"\n');
248
+ }
249
+
250
+ process.stderr.write('\n HTTP relay running at http://localhost:' + HTTP_PORT + '\n');
251
+ process.stderr.write(' Send a design:\n');
252
+ process.stderr.write(' node send-to-figma.js <file.html> [name] [width]\n\n');
253
+ }
package/start.bat ADDED
@@ -0,0 +1,9 @@
1
+ @echo off
2
+ title Arzisoft HTML to Design — Relay Server
3
+ cd /d "%~dp0"
4
+ echo.
5
+ echo Arzisoft HTML to Design — Relay Server
6
+ echo Starting on http://localhost:3131
7
+ echo.
8
+ node server.js
9
+ pause