@createlex/figgen 1.4.2 → 1.4.4
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 +5 -5
- package/companion/bridge-server.cjs +2 -2
- package/companion/createlex-auth.cjs +3 -3
- package/companion/login.mjs +4 -4
- package/companion/mcp-server.mjs +17 -17
- package/companion/server.js +1 -1
- package/companion/setup.cjs +7 -7
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -37,14 +37,14 @@ If no BYOK keys are set, falls back to the CreateLex hosted pattern matcher. Req
|
|
|
37
37
|
## Install
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
npx @createlex/
|
|
40
|
+
npx @createlex/figgen start --project /path/to/MyApp/MyApp
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
Or install globally:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
npm install -g @createlex/
|
|
47
|
-
|
|
46
|
+
npm install -g @createlex/figgen
|
|
47
|
+
figgen start --project /path/to/MyApp/MyApp
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
## MCP client configuration
|
|
@@ -56,7 +56,7 @@ Add to your Claude Code / Cursor / Windsurf MCP config:
|
|
|
56
56
|
"mcpServers": {
|
|
57
57
|
"figma-swiftui": {
|
|
58
58
|
"command": "npx",
|
|
59
|
-
"args": ["@createlex/
|
|
59
|
+
"args": ["@createlex/figgen", "start"],
|
|
60
60
|
"env": {
|
|
61
61
|
"FIGMA_SWIFTUI_PROJECT_PATH": "/path/to/MyApp/MyApp",
|
|
62
62
|
"ANTHROPIC_API_KEY": "sk-ant-..."
|
|
@@ -153,7 +153,7 @@ After generation, the **Copy AI Prompt** button copies a ready-to-use prompt you
|
|
|
153
153
|
Only required for Tier 3 hosted generation:
|
|
154
154
|
|
|
155
155
|
```bash
|
|
156
|
-
npx @createlex/
|
|
156
|
+
npx @createlex/figgen login
|
|
157
157
|
```
|
|
158
158
|
|
|
159
159
|
Saves your session to `~/.createlex/auth.json`.
|
|
@@ -131,7 +131,7 @@ function startBridgeServer(options = {}) {
|
|
|
131
131
|
ok: false,
|
|
132
132
|
authRequired: true,
|
|
133
133
|
authRequiredReason,
|
|
134
|
-
loginCommand: 'npx @createlex/
|
|
134
|
+
loginCommand: 'npx @createlex/figgen login',
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
137
|
res.json({
|
|
@@ -265,7 +265,7 @@ function startBridgeServer(options = {}) {
|
|
|
265
265
|
return res.status(503).json({
|
|
266
266
|
ok: false,
|
|
267
267
|
error: 'No Figma plugin is connected to the bridge. Open the plugin in Figma first.',
|
|
268
|
-
hint: 'npx @createlex/
|
|
268
|
+
hint: 'npx @createlex/figgen start --project ./MyApp',
|
|
269
269
|
});
|
|
270
270
|
}
|
|
271
271
|
|
|
@@ -132,7 +132,7 @@ async function ensureAccessToken(explicitToken, apiBaseUrl) {
|
|
|
132
132
|
|
|
133
133
|
const auth = loadAuth();
|
|
134
134
|
if (!auth?.token) {
|
|
135
|
-
throw new Error(`CreateLex login required. Run "npx @createlex/
|
|
135
|
+
throw new Error(`CreateLex login required. Run "npx @createlex/figgen login" to create ${AUTH_FILE}.`);
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
const currentValidation = validateTokenFormat(auth.token);
|
|
@@ -146,7 +146,7 @@ async function ensureAccessToken(explicitToken, apiBaseUrl) {
|
|
|
146
146
|
|
|
147
147
|
const refreshToken = auth.refreshToken || auth.refresh_token;
|
|
148
148
|
if (currentValidation.reason !== 'token_expired' || !refreshToken) {
|
|
149
|
-
throw new Error(`CreateLex token is invalid: ${currentValidation.reason}. Run "npx @createlex/
|
|
149
|
+
throw new Error(`CreateLex token is invalid: ${currentValidation.reason}. Run "npx @createlex/figgen login" again.`);
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
const refreshed = await refreshSession(refreshToken, apiBaseUrl);
|
|
@@ -277,7 +277,7 @@ async function authorizeRuntimeStartup(options = {}) {
|
|
|
277
277
|
const startupPayload = {
|
|
278
278
|
deviceId: deviceInfo.deviceId,
|
|
279
279
|
deviceInfo,
|
|
280
|
-
clientName: '
|
|
280
|
+
clientName: 'figgen',
|
|
281
281
|
clientVersion: '1.0.0',
|
|
282
282
|
requestedTtlSeconds: 86400,
|
|
283
283
|
};
|
package/companion/login.mjs
CHANGED
|
@@ -61,10 +61,10 @@ function sendHtml(res, statusCode, title, body) {
|
|
|
61
61
|
async function main() {
|
|
62
62
|
const args = new Set(process.argv.slice(2));
|
|
63
63
|
if (args.has('--help') || args.has('-h')) {
|
|
64
|
-
console.log(`CreateLex
|
|
64
|
+
console.log(`CreateLex figgen login
|
|
65
65
|
|
|
66
66
|
Usage:
|
|
67
|
-
npx @createlex/
|
|
67
|
+
npx @createlex/figgen login
|
|
68
68
|
|
|
69
69
|
Options:
|
|
70
70
|
--no-open Print the CreateLex login URL instead of opening a browser
|
|
@@ -100,14 +100,14 @@ Options:
|
|
|
100
100
|
const error = url.searchParams.get('error') || '';
|
|
101
101
|
|
|
102
102
|
if (returnedState !== state) {
|
|
103
|
-
sendHtml(res, 400, 'Login Failed', '<p>The login callback state did not match. Close this window and run <code>npx @createlex/
|
|
103
|
+
sendHtml(res, 400, 'Login Failed', '<p>The login callback state did not match. Close this window and run <code>npx @createlex/figgen login</code> again.</p>');
|
|
104
104
|
clearTimeout(timeout);
|
|
105
105
|
reject(new Error('Login callback state mismatch.'));
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
if (error) {
|
|
110
|
-
sendHtml(res, 400, 'Login Failed', `<p>${error}</p><p>Close this window and run <code>npx @createlex/
|
|
110
|
+
sendHtml(res, 400, 'Login Failed', `<p>${error}</p><p>Close this window and run <code>npx @createlex/figgen login</code> again.</p>`);
|
|
111
111
|
clearTimeout(timeout);
|
|
112
112
|
reject(new Error(error));
|
|
113
113
|
return;
|
package/companion/mcp-server.mjs
CHANGED
|
@@ -102,7 +102,7 @@ function getPublicAuthState() {
|
|
|
102
102
|
|
|
103
103
|
function ensureRuntimeAuthorized() {
|
|
104
104
|
if (!runtimeAuthState.authorized) {
|
|
105
|
-
throw new Error('CreateLex authentication required. Run "npx @createlex/
|
|
105
|
+
throw new Error('CreateLex authentication required. Run "npx @createlex/figgen login" and ensure your subscription is active before starting figma-swiftui MCP.');
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
@@ -142,7 +142,7 @@ async function tryHostedSemanticGeneration({ nodeIds, generationMode, includeOve
|
|
|
142
142
|
return { ...byokResult, selection, imageCount: 0, imageNames: [], hosted: false };
|
|
143
143
|
}
|
|
144
144
|
} catch (byokError) {
|
|
145
|
-
console.error('[
|
|
145
|
+
console.error('[figgen] BYOK generation failed, falling back to hosted:', byokError.message);
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
|
|
@@ -227,7 +227,7 @@ function scheduleReconnect() {
|
|
|
227
227
|
reconnectTimer = setTimeout(() => {
|
|
228
228
|
reconnectTimer = null;
|
|
229
229
|
connectBridge().catch((error) => {
|
|
230
|
-
console.error('[
|
|
230
|
+
console.error('[figgen] Bridge reconnect failed:', error.message);
|
|
231
231
|
});
|
|
232
232
|
}, 1500);
|
|
233
233
|
}
|
|
@@ -245,7 +245,7 @@ function handleBridgeMessage(rawMessage) {
|
|
|
245
245
|
try {
|
|
246
246
|
message = JSON.parse(rawMessage.toString());
|
|
247
247
|
} catch (error) {
|
|
248
|
-
console.error('[
|
|
248
|
+
console.error('[figgen] Failed to parse bridge message:', error);
|
|
249
249
|
return;
|
|
250
250
|
}
|
|
251
251
|
|
|
@@ -282,7 +282,7 @@ function handleBridgeMessage(rawMessage) {
|
|
|
282
282
|
? `page "${message.data.currentPage?.name || 'unknown'}"`
|
|
283
283
|
: '';
|
|
284
284
|
if (summary) {
|
|
285
|
-
console.error(`[
|
|
285
|
+
console.error(`[figgen] ${message.event}: ${summary}`);
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
}
|
|
@@ -818,7 +818,7 @@ server.registerTool('write_selection_to_xcode', {
|
|
|
818
818
|
}
|
|
819
819
|
} catch (err) {
|
|
820
820
|
// Non-fatal — continue with plugin generation
|
|
821
|
-
console.error('[
|
|
821
|
+
console.error('[figgen] Semantic generation failed (non-fatal):', err?.message ?? err);
|
|
822
822
|
}
|
|
823
823
|
|
|
824
824
|
const generated = await callBridge('generate_swiftui', {
|
|
@@ -951,7 +951,7 @@ server.registerTool('figma_to_swiftui', {
|
|
|
951
951
|
llmResult = byokResult;
|
|
952
952
|
}
|
|
953
953
|
} catch (err) {
|
|
954
|
-
console.error('[
|
|
954
|
+
console.error('[figgen] BYOK generation failed:', err?.message ?? err);
|
|
955
955
|
}
|
|
956
956
|
}
|
|
957
957
|
|
|
@@ -978,7 +978,7 @@ server.registerTool('figma_to_swiftui', {
|
|
|
978
978
|
}
|
|
979
979
|
}
|
|
980
980
|
} catch (err) {
|
|
981
|
-
console.error('[
|
|
981
|
+
console.error('[figgen] Hosted generation failed (non-fatal):', err?.message ?? err);
|
|
982
982
|
}
|
|
983
983
|
}
|
|
984
984
|
|
|
@@ -1291,7 +1291,7 @@ async function shutdownBridgeAndExit(message, exitCode = 1) {
|
|
|
1291
1291
|
try {
|
|
1292
1292
|
await bridgeRuntimeHandle.close();
|
|
1293
1293
|
} catch (error) {
|
|
1294
|
-
console.error('[
|
|
1294
|
+
console.error('[figgen] Failed to close bridge cleanly:', error.message);
|
|
1295
1295
|
}
|
|
1296
1296
|
}
|
|
1297
1297
|
process.exit(exitCode);
|
|
@@ -1306,7 +1306,7 @@ function startAuthValidationLoop() {
|
|
|
1306
1306
|
try {
|
|
1307
1307
|
const validation = await validateRuntimeSession(runtimeAuthState);
|
|
1308
1308
|
if (!validation.valid) {
|
|
1309
|
-
await shutdownBridgeAndExit(`[
|
|
1309
|
+
await shutdownBridgeAndExit(`[figgen] Authorization lost: ${validation.error}`);
|
|
1310
1310
|
return;
|
|
1311
1311
|
}
|
|
1312
1312
|
|
|
@@ -1317,10 +1317,10 @@ function startAuthValidationLoop() {
|
|
|
1317
1317
|
};
|
|
1318
1318
|
|
|
1319
1319
|
if (validation.refreshed) {
|
|
1320
|
-
console.error('[
|
|
1320
|
+
console.error('[figgen] Refreshed CreateLex MCP authorization');
|
|
1321
1321
|
}
|
|
1322
1322
|
} catch (error) {
|
|
1323
|
-
await shutdownBridgeAndExit(`[
|
|
1323
|
+
await shutdownBridgeAndExit(`[figgen] Authorization revalidation failed: ${error.message}`);
|
|
1324
1324
|
}
|
|
1325
1325
|
}, AUTH_REVALIDATION_INTERVAL_MS);
|
|
1326
1326
|
|
|
@@ -1331,8 +1331,8 @@ async function main() {
|
|
|
1331
1331
|
runtimeAuthState = await authorizeRuntimeStartup();
|
|
1332
1332
|
console.error(
|
|
1333
1333
|
runtimeAuthState.bypass
|
|
1334
|
-
? '[
|
|
1335
|
-
: `[
|
|
1334
|
+
? '[figgen] Authorization bypass enabled'
|
|
1335
|
+
: `[figgen] Authorized CreateLex user ${runtimeAuthState.email || runtimeAuthState.userId || 'unknown-user'}`
|
|
1336
1336
|
);
|
|
1337
1337
|
|
|
1338
1338
|
const bridgeHttpUrl = new URL(BRIDGE_HTTP_URL);
|
|
@@ -1350,16 +1350,16 @@ async function main() {
|
|
|
1350
1350
|
try {
|
|
1351
1351
|
await connectBridge();
|
|
1352
1352
|
} catch (error) {
|
|
1353
|
-
console.error('[
|
|
1353
|
+
console.error('[figgen] Bridge connect failed on startup:', error.message);
|
|
1354
1354
|
}
|
|
1355
1355
|
|
|
1356
1356
|
const transport = new StdioServerTransport();
|
|
1357
1357
|
await server.connect(transport);
|
|
1358
1358
|
startAuthValidationLoop();
|
|
1359
|
-
console.error('[
|
|
1359
|
+
console.error('[figgen] MCP server running on stdio');
|
|
1360
1360
|
}
|
|
1361
1361
|
|
|
1362
1362
|
main().catch((error) => {
|
|
1363
|
-
console.error('[
|
|
1363
|
+
console.error('[figgen] Server error:', error);
|
|
1364
1364
|
process.exit(1);
|
|
1365
1365
|
});
|
package/companion/server.js
CHANGED
|
@@ -33,7 +33,7 @@ async function main() {
|
|
|
33
33
|
// Signal the bridge to return authRequired from /ping so the plugin UI
|
|
34
34
|
// can show a "Login required" panel instead of just "runtime off".
|
|
35
35
|
if (bridgeRuntime && typeof bridgeRuntime.setAuthRequired === 'function') {
|
|
36
|
-
bridgeRuntime.setAuthRequired(validation.error || 'Token expired. Run: npx @createlex/
|
|
36
|
+
bridgeRuntime.setAuthRequired(validation.error || 'Token expired. Run: npx @createlex/figgen login');
|
|
37
37
|
} else {
|
|
38
38
|
process.exit(1);
|
|
39
39
|
}
|
package/companion/setup.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* figgen setup
|
|
3
3
|
*
|
|
4
4
|
* Auto-detect installed IDEs / CLI tools and add the figma-swiftui MCP
|
|
5
5
|
* server entry to each config file.
|
|
@@ -37,12 +37,12 @@ function resolvePaths() {
|
|
|
37
37
|
const resolved = fs.realpathSync(binScript);
|
|
38
38
|
const dir = path.dirname(resolved);
|
|
39
39
|
// Check for the .js bin entry point
|
|
40
|
-
const jsCandidate = path.join(dir, '
|
|
40
|
+
const jsCandidate = path.join(dir, 'figgen.js');
|
|
41
41
|
if (fs.existsSync(jsCandidate)) {
|
|
42
42
|
return { nodePath, scriptPath: jsCandidate };
|
|
43
43
|
}
|
|
44
44
|
// Check for the wrapper (symlink without .js)
|
|
45
|
-
const candidate = path.join(dir, '
|
|
45
|
+
const candidate = path.join(dir, 'figgen');
|
|
46
46
|
if (fs.existsSync(candidate)) {
|
|
47
47
|
// Resolve symlinks to get the actual .js file
|
|
48
48
|
const real = fs.realpathSync(candidate);
|
|
@@ -52,7 +52,7 @@ function resolvePaths() {
|
|
|
52
52
|
|
|
53
53
|
// Fallback: search PATH for the binary, then resolve to its .js source
|
|
54
54
|
const whichCmd = require('node:child_process')
|
|
55
|
-
.execSync('which
|
|
55
|
+
.execSync('which figgen 2>/dev/null || true')
|
|
56
56
|
.toString()
|
|
57
57
|
.trim();
|
|
58
58
|
if (whichCmd) {
|
|
@@ -85,7 +85,7 @@ function getTargets({ nodePath, scriptPath }) {
|
|
|
85
85
|
// in IDEs that don't source shell profiles (nvm/fnm/volta).
|
|
86
86
|
const stdioEntry = scriptPath
|
|
87
87
|
? { command: nodePath, args: [scriptPath, 'start'] }
|
|
88
|
-
: { command: resolveNpxPath(), args: ['-y', '@createlex/
|
|
88
|
+
: { command: resolveNpxPath(), args: ['-y', '@createlex/figgen', 'start'] };
|
|
89
89
|
|
|
90
90
|
const stdioEntryWithType = { type: 'stdio', ...stdioEntry };
|
|
91
91
|
|
|
@@ -127,7 +127,7 @@ function getTargets({ nodePath, scriptPath }) {
|
|
|
127
127
|
key: 'mcp',
|
|
128
128
|
entry: scriptPath
|
|
129
129
|
? { type: 'local', command: [nodePath, scriptPath, 'start'], enabled: true }
|
|
130
|
-
: { type: 'local', command: [resolveNpxPath(), '-y', '@createlex/
|
|
130
|
+
: { type: 'local', command: [resolveNpxPath(), '-y', '@createlex/figgen', 'start'], enabled: true },
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
133
|
name: 'Codex CLI',
|
|
@@ -218,7 +218,7 @@ function runSetup(flags = {}) {
|
|
|
218
218
|
const paths = resolvePaths();
|
|
219
219
|
|
|
220
220
|
console.log();
|
|
221
|
-
console.log(' 🔧
|
|
221
|
+
console.log(' 🔧 figgen setup');
|
|
222
222
|
console.log(' ─────────────────────────────────────');
|
|
223
223
|
console.log(` Node: ${paths.nodePath}`);
|
|
224
224
|
if (paths.scriptPath) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@createlex/figgen",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.4",
|
|
4
4
|
"description": "CreateLex MCP runtime for Figma-to-SwiftUI generation and Xcode export",
|
|
5
5
|
"bin": {
|
|
6
6
|
"figgen": "bin/figgen.js"
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
},
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc",
|
|
26
|
-
"login": "node bin/
|
|
27
|
-
"start": "node bin/
|
|
26
|
+
"login": "node bin/figgen.js login",
|
|
27
|
+
"start": "node bin/figgen.js start",
|
|
28
28
|
"watch": "tsc --watch",
|
|
29
29
|
"test:bridge-smoke": "node ../tests/mcp/figma_plugin_swiftui_bridge_smoke.mjs",
|
|
30
30
|
"test:high-fidelity-smoke": "node ../tests/mcp/figma_plugin_swiftui_high_fidelity_smoke.mjs",
|
|
31
|
-
"start:companion:mcp": "node bin/
|
|
31
|
+
"start:companion:mcp": "node bin/figgen.js start"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
34
|
"node": ">=18.0.0"
|