canicode 0.5.2 → 0.6.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.
@@ -1063,6 +1063,86 @@ var FigmaUrlParseError = class extends Error {
1063
1063
  function buildFigmaDeepLink(fileKey, nodeId) {
1064
1064
  return `https://www.figma.com/design/${fileKey}?node-id=${encodeURIComponent(nodeId)}`;
1065
1065
  }
1066
+ var AIREADY_DIR = join(homedir(), ".canicode");
1067
+ var CONFIG_PATH = join(AIREADY_DIR, "config.json");
1068
+ var REPORTS_DIR = join(AIREADY_DIR, "reports");
1069
+ function ensureDir(dir) {
1070
+ if (!existsSync(dir)) {
1071
+ mkdirSync(dir, { recursive: true });
1072
+ }
1073
+ }
1074
+ function readConfig() {
1075
+ if (!existsSync(CONFIG_PATH)) {
1076
+ return {};
1077
+ }
1078
+ try {
1079
+ const raw = readFileSync(CONFIG_PATH, "utf-8");
1080
+ return JSON.parse(raw);
1081
+ } catch {
1082
+ return {};
1083
+ }
1084
+ }
1085
+ function getFigmaToken() {
1086
+ return process.env["FIGMA_TOKEN"] ?? readConfig().figmaToken;
1087
+ }
1088
+ function getReportsDir() {
1089
+ return REPORTS_DIR;
1090
+ }
1091
+ function ensureReportsDir() {
1092
+ ensureDir(REPORTS_DIR);
1093
+ }
1094
+ function getTelemetryEnabled() {
1095
+ return readConfig().telemetry !== false;
1096
+ }
1097
+ function getPosthogApiKey() {
1098
+ return process.env["POSTHOG_API_KEY"] ?? readConfig().posthogApiKey;
1099
+ }
1100
+ function getSentryDsn() {
1101
+ return process.env["SENTRY_DSN"] ?? readConfig().sentryDsn;
1102
+ }
1103
+
1104
+ // src/core/loader.ts
1105
+ function isFigmaUrl(input) {
1106
+ return input.includes("figma.com/");
1107
+ }
1108
+ function isJsonFile(input) {
1109
+ return input.endsWith(".json");
1110
+ }
1111
+ async function loadFile(input, token) {
1112
+ if (isJsonFile(input)) {
1113
+ const filePath = resolve(input);
1114
+ if (!existsSync(filePath)) {
1115
+ throw new Error(`File not found: ${filePath}`);
1116
+ }
1117
+ console.log(`Loading from JSON: ${filePath}`);
1118
+ return { file: await loadFigmaFileFromJson(filePath) };
1119
+ }
1120
+ if (isFigmaUrl(input)) {
1121
+ const { fileKey, nodeId } = parseFigmaUrl(input);
1122
+ return loadFromApi(fileKey, nodeId, token);
1123
+ }
1124
+ throw new Error(
1125
+ `Invalid input: ${input}. Provide a Figma URL or JSON file path.`
1126
+ );
1127
+ }
1128
+ async function loadFromApi(fileKey, nodeId, token) {
1129
+ console.log(`Fetching from Figma REST API: ${fileKey}`);
1130
+ if (nodeId) {
1131
+ console.log(`Target node: ${nodeId}`);
1132
+ }
1133
+ const figmaToken = token ?? getFigmaToken();
1134
+ if (!figmaToken) {
1135
+ throw new Error(
1136
+ "Figma token required. Run 'canicode init --token YOUR_TOKEN' or set FIGMA_TOKEN env var."
1137
+ );
1138
+ }
1139
+ const client = new FigmaClient({ token: figmaToken });
1140
+ const response = await client.getFile(fileKey);
1141
+ return {
1142
+ file: transformFigmaResponse(fileKey, response),
1143
+ nodeId
1144
+ };
1145
+ }
1066
1146
 
1067
1147
  // src/adapters/figma-mcp-adapter.ts
1068
1148
  var TAG_TYPE_MAP = {
@@ -1194,118 +1274,6 @@ function parseMcpMetadataXml(xml, fileKey, fileName) {
1194
1274
  styles: {}
1195
1275
  };
1196
1276
  }
1197
- var AIREADY_DIR = join(homedir(), ".canicode");
1198
- var CONFIG_PATH = join(AIREADY_DIR, "config.json");
1199
- var REPORTS_DIR = join(AIREADY_DIR, "reports");
1200
- function ensureDir(dir) {
1201
- if (!existsSync(dir)) {
1202
- mkdirSync(dir, { recursive: true });
1203
- }
1204
- }
1205
- function readConfig() {
1206
- if (!existsSync(CONFIG_PATH)) {
1207
- return {};
1208
- }
1209
- try {
1210
- const raw = readFileSync(CONFIG_PATH, "utf-8");
1211
- return JSON.parse(raw);
1212
- } catch {
1213
- return {};
1214
- }
1215
- }
1216
- function getFigmaToken() {
1217
- return process.env["FIGMA_TOKEN"] ?? readConfig().figmaToken;
1218
- }
1219
- function getReportsDir() {
1220
- return REPORTS_DIR;
1221
- }
1222
- function ensureReportsDir() {
1223
- ensureDir(REPORTS_DIR);
1224
- }
1225
- function getTelemetryEnabled() {
1226
- return readConfig().telemetry !== false;
1227
- }
1228
- function getPosthogApiKey() {
1229
- return process.env["POSTHOG_API_KEY"] ?? readConfig().posthogApiKey;
1230
- }
1231
- function getSentryDsn() {
1232
- return process.env["SENTRY_DSN"] ?? readConfig().sentryDsn;
1233
- }
1234
-
1235
- // src/core/loader.ts
1236
- function isFigmaUrl(input) {
1237
- return input.includes("figma.com/");
1238
- }
1239
- function isJsonFile(input) {
1240
- return input.endsWith(".json");
1241
- }
1242
- async function loadFile(input, token, mode = "auto") {
1243
- if (isJsonFile(input)) {
1244
- const filePath = resolve(input);
1245
- if (!existsSync(filePath)) {
1246
- throw new Error(`File not found: ${filePath}`);
1247
- }
1248
- console.log(`Loading from JSON: ${filePath}`);
1249
- return { file: await loadFigmaFileFromJson(filePath) };
1250
- }
1251
- if (isFigmaUrl(input)) {
1252
- const { fileKey, nodeId, fileName } = parseFigmaUrl(input);
1253
- if (mode === "mcp") {
1254
- return loadFromMcp(fileKey, nodeId, fileName);
1255
- }
1256
- if (mode === "api") {
1257
- return loadFromApi(fileKey, nodeId, token);
1258
- }
1259
- try {
1260
- console.log("Auto-detecting data source... trying MCP first.");
1261
- return await loadFromMcp(fileKey, nodeId, fileName);
1262
- } catch (mcpError) {
1263
- const mcpMsg = mcpError instanceof Error ? mcpError.message : String(mcpError);
1264
- console.log(`MCP unavailable (${mcpMsg}). Falling back to REST API.`);
1265
- return loadFromApi(fileKey, nodeId, token);
1266
- }
1267
- }
1268
- throw new Error(
1269
- `Invalid input: ${input}. Provide a Figma URL or JSON file path.`
1270
- );
1271
- }
1272
- async function loadFromMcp(fileKey, nodeId, fileName) {
1273
- console.log(`Loading via MCP: ${fileKey} (node: ${nodeId ?? "root"})`);
1274
- const file = await loadViaMcp(fileKey, nodeId ?? "0:1", fileName);
1275
- return { file, nodeId };
1276
- }
1277
- async function loadFromApi(fileKey, nodeId, token) {
1278
- console.log(`Fetching from Figma REST API: ${fileKey}`);
1279
- if (nodeId) {
1280
- console.log(`Target node: ${nodeId}`);
1281
- }
1282
- const figmaToken = token ?? getFigmaToken();
1283
- if (!figmaToken) {
1284
- throw new Error(
1285
- "Figma token required. Run 'canicode init --token YOUR_TOKEN' or set FIGMA_TOKEN env var."
1286
- );
1287
- }
1288
- const client = new FigmaClient({ token: figmaToken });
1289
- const response = await client.getFile(fileKey);
1290
- return {
1291
- file: transformFigmaResponse(fileKey, response),
1292
- nodeId
1293
- };
1294
- }
1295
- async function loadViaMcp(fileKey, nodeId, fileName) {
1296
- const { execSync } = await import('child_process');
1297
- const result = execSync(
1298
- `claude --print "Use the mcp__figma__get_metadata tool with fileKey=\\"${fileKey}\\" and nodeId=\\"${nodeId.replace(/-/g, ":")}\\" \u2014 return ONLY the raw XML output, nothing else."`,
1299
- { encoding: "utf-8", timeout: 12e4 }
1300
- );
1301
- const xmlStart = result.indexOf("<");
1302
- const xmlEnd = result.lastIndexOf(">");
1303
- if (xmlStart === -1 || xmlEnd === -1) {
1304
- throw new Error("MCP did not return valid XML metadata");
1305
- }
1306
- const xml = result.slice(xmlStart, xmlEnd + 1);
1307
- return parseMcpMetadataXml(xml, fileKey, fileName);
1308
- }
1309
1277
 
1310
1278
  // src/core/design-data-parser.ts
1311
1279
  function parseDesignData(data, fileKey, fileName) {
@@ -3254,9 +3222,16 @@ Two ways to provide design data:
3254
3222
  1. designData \u2014 Pass Figma node data directly (from Figma MCP get_metadata). Recommended when using Figma MCP.
3255
3223
  2. input \u2014 Figma URL (fetches via REST API, requires FIGMA_TOKEN).
3256
3224
 
3257
- Typical flow with Figma MCP:
3258
- Step 1: Call Figma MCP get_metadata to get the node tree
3259
- Step 2: Pass the result as designData to this tool`,
3225
+ Typical flow with Figma MCP (recommended, no token needed):
3226
+ Step 1: Call the official Figma MCP's get_metadata tool to get the node tree
3227
+ Step 2: Pass the result as designData to this tool
3228
+
3229
+ IMPORTANT \u2014 Before calling this tool, check which data source is available:
3230
+ - If the official Figma MCP (https://mcp.figma.com/mcp) is connected: use get_metadata \u2192 designData flow. No token needed.
3231
+ - If Figma MCP is NOT connected: use the input parameter with a Figma URL. This requires a FIGMA_TOKEN.
3232
+ Tell the user: "The official Figma MCP server is not connected. To use without a token, set it up:
3233
+ claude mcp add -s project -t http figma https://mcp.figma.com/mcp
3234
+ Otherwise, provide a Figma API token via FIGMA_TOKEN env var or the token parameter."`,
3260
3235
  {
3261
3236
  designData: z.string().optional().describe("Figma node data from Figma MCP get_metadata (XML or JSON). Pass this instead of input when using Figma MCP."),
3262
3237
  input: z.string().optional().describe("Figma URL. Used when designData is not provided. Requires FIGMA_TOKEN."),
@@ -3282,7 +3257,7 @@ Typical flow with Figma MCP:
3282
3257
  if (designData) {
3283
3258
  file = parseDesignData(designData, fileKey ?? "unknown", fileName);
3284
3259
  } else if (input) {
3285
- const loaded = await loadFile(input, token, "api");
3260
+ const loaded = await loadFile(input, token);
3286
3261
  file = loaded.file;
3287
3262
  nodeId = loaded.nodeId;
3288
3263
  } else {