@pedroab0/skills 1.0.1 → 1.2.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/README.md +19 -3
- package/bin/cli.js +175 -35
- package/figma-extractor/README.md +14 -6
- package/figma-extractor/SKILL.md +18 -16
- package/figma-extractor/figma-plugin/README.md +10 -5
- package/figma-extractor/figma-plugin/code.js +461 -87
- package/figma-extractor/figma-plugin/ui.html +76 -23
- package/figma-extractor/scripts/fetch_figma.js +465 -61
- package/figma-node-builder/README.md +1 -1
- package/figma-node-builder/SKILL.md +7 -8
- package/figma-node-builder/scripts/inspect_spec.js +15 -2
- package/figma-to-code/README.md +10 -4
- package/figma-to-code/SKILL.md +18 -16
- package/figma-to-code/scripts/fetch_figma.js +448 -53
- package/package.json +12 -3
- package/figma-extractor/demo/desktop_home.json +0 -2434
- package/figma-extractor/demo/dt_money_desktop_home.png +0 -0
- package/figma-extractor/demo/dt_money_preview.png +0 -0
- package/figma-extractor/demo/dt_money_selection.json +0 -472
- package/figma-extractor/demo/index.html +0 -188
- package/figma-extractor/demo/style.css +0 -360
- package/figma-extractor/figma-plugin/assets/cover-1920.png +0 -0
- package/figma-extractor/figma-plugin/assets/cover.jpg +0 -0
- package/figma-extractor/figma-plugin/assets/cover.png +0 -0
- package/figma-extractor/figma-plugin/assets/icon-128.png +0 -0
- package/figma-extractor/figma-plugin/assets/icon.png +0 -0
- package/figma-extractor/scripts/test_fetch_figma.js +0 -601
- package/figma-node-builder/scripts/test_inspect_spec.js +0 -148
- package/figma-to-code/scripts/test_fetch_figma.js +0 -581
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
|
-
A curated collection of modular, production-ready skills developed for AI coding agents (such as Claude Code, OpenCode, Codex, Google Antigravity, and
|
|
10
|
+
A curated collection of modular, production-ready skills developed for AI coding agents (such as Claude Code, OpenCode, Codex, Google Antigravity, Cursor, and Kiro).
|
|
11
11
|
|
|
12
12
|
Each skill in this repository is designed to give AI assistants specialized, autonomous capabilities while maintaining:
|
|
13
13
|
- **Token Efficiency**: Pruning unnecessary data and noise to keep agent context windows lean and fast.
|
|
@@ -57,7 +57,7 @@ flowchart TD
|
|
|
57
57
|
|
|
58
58
|
To extract specifications directly inside Figma Desktop without API tokens or rate limits, use the companion [**Figma Node JSON Extractor**](./figma-extractor/figma-plugin/README.md) plugin:
|
|
59
59
|
* **Figma Community (Recommended)**: Install [**Figma Node JSON Extractor** on Figma Community](https://www.figma.com/community/plugin/1691244420692136789/figma-node-json-extractor) directly, or search for it under Figma Plugins.
|
|
60
|
-
* **Manual Manifest Import (Local Backup)**: In Figma Desktop, go to **Plugins** $\rightarrow$ **Development** $\rightarrow$ **Import plugin from manifest...** and select `manifest.json` from your installed
|
|
60
|
+
* **Manual Manifest Import (Local Backup)**: In Figma Desktop, go to **Plugins** $\rightarrow$ **Development** $\rightarrow$ **Import plugin from manifest...** and select `manifest.json` from your installed skill directory (`<skill-dir>/figma-plugin/manifest.json`).
|
|
61
61
|
|
|
62
62
|
---
|
|
63
63
|
|
|
@@ -75,7 +75,7 @@ npx github:pedroab0/skills
|
|
|
75
75
|
|
|
76
76
|
The installer features a clean, interactive terminal UI:
|
|
77
77
|
* **Navigate**: Use keyboard arrows (`↑` / `↓`) or vim keys (`k` / `j`) to move between options.
|
|
78
|
-
* **Select**: Press `[Enter]` or press number keys (`1` - `
|
|
78
|
+
* **Select**: Press `[Enter]` or press number keys (`1` - `6`) to select directly.
|
|
79
79
|
* **Exit**: Press `[ESC]` or `q` at any time to cancel cleanly.
|
|
80
80
|
|
|
81
81
|
### Selection Options & Presets
|
|
@@ -91,6 +91,9 @@ Or specify flags for fast, non-interactive setup:
|
|
|
91
91
|
# Install all skills for Claude Code globally
|
|
92
92
|
npx @pedroab0/skills --agent claude --skill all
|
|
93
93
|
|
|
94
|
+
# Install SDD skills for Kiro (IDE & CLI)
|
|
95
|
+
npx @pedroab0/skills --agent kiro --skill sdd
|
|
96
|
+
|
|
94
97
|
# Install SDD skills for Google Antigravity
|
|
95
98
|
npx @pedroab0/skills --agent antigravity --skill figma-extractor,figma-node-builder
|
|
96
99
|
|
|
@@ -156,6 +159,19 @@ ln -s /path/to/skills/figma-node-builder ~/.gemini/config/skills/figma-node-buil
|
|
|
156
159
|
|
|
157
160
|
---
|
|
158
161
|
|
|
162
|
+
### 5. Kiro
|
|
163
|
+
|
|
164
|
+
Kiro discovers standard `SKILL.md` packages locally in `.kiro/skills/` or globally in `~/.kiro/skills/` (or `$KIRO_HOME/skills/`):
|
|
165
|
+
```bash
|
|
166
|
+
mkdir -p ~/.kiro/skills
|
|
167
|
+
ln -s /path/to/skills/figma-to-code ~/.kiro/skills/figma-to-code
|
|
168
|
+
ln -s /path/to/skills/figma-extractor ~/.kiro/skills/figma-extractor
|
|
169
|
+
ln -s /path/to/skills/figma-node-builder ~/.kiro/skills/figma-node-builder
|
|
170
|
+
```
|
|
171
|
+
- **Activation**: Kiro loads matching skills automatically based on your prompt or explicitly via `/figma-extractor`, `/figma-node-builder`, and `/figma-to-code` in Kiro IDE chat or Kiro CLI.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
159
175
|
## Repository Structure
|
|
160
176
|
|
|
161
177
|
```text
|
package/bin/cli.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Skills Installer CLI
|
|
5
5
|
* https://github.com/pedroab0/skills
|
|
6
6
|
*
|
|
7
|
-
* Install curated AI agent skills for Claude Code, Cursor, Antigravity, and
|
|
7
|
+
* Install curated AI agent skills for Claude Code, Cursor, Antigravity, OpenCode, and Kiro.
|
|
8
8
|
* Usage: npx @pedroab0/skills [options]
|
|
9
9
|
*/
|
|
10
10
|
|
|
@@ -41,7 +41,7 @@ const ui = {
|
|
|
41
41
|
|
|
42
42
|
function printBanner() {
|
|
43
43
|
ui.header(`Skills Installer ${style.dim('(by @pedroab0)')}`);
|
|
44
|
-
ui.item(style.dim('Curated AI Agent Skills for Claude Code, Antigravity, Cursor & OpenCode'));
|
|
44
|
+
ui.item(style.dim('Curated AI Agent Skills for Claude Code, Antigravity, Cursor, Kiro & OpenCode'));
|
|
45
45
|
ui.item(style.dim('https://github.com/pedroab0/skills'));
|
|
46
46
|
ui.bar();
|
|
47
47
|
}
|
|
@@ -53,7 +53,7 @@ ${style.bold('Usage:')}
|
|
|
53
53
|
(or: npx github:pedroab0/skills [options])
|
|
54
54
|
|
|
55
55
|
${style.bold('Options:')}
|
|
56
|
-
--agent <name> Target agent: claude, antigravity, cursor, opencode, all
|
|
56
|
+
--agent <name> Target agent: claude, antigravity, cursor, kiro, opencode, all
|
|
57
57
|
--skill <name> Skill to install (or comma-separated, or "all", "sdd")
|
|
58
58
|
--global Install globally (available across all projects on your machine)
|
|
59
59
|
--local Install in current project workspace only
|
|
@@ -65,6 +65,7 @@ ${style.bold('Options:')}
|
|
|
65
65
|
${style.bold('Examples:')}
|
|
66
66
|
npx @pedroab0/skills
|
|
67
67
|
npx @pedroab0/skills --agent claude --skill all
|
|
68
|
+
npx @pedroab0/skills --agent kiro --skill sdd
|
|
68
69
|
npx @pedroab0/skills --agent antigravity --skill figma-extractor,figma-node-builder
|
|
69
70
|
npx @pedroab0/skills --dest ~/.claude/skills --skill figma-to-code
|
|
70
71
|
`);
|
|
@@ -111,6 +112,11 @@ function getAvailableSkills() {
|
|
|
111
112
|
|
|
112
113
|
function getAgentConfigs(cwd) {
|
|
113
114
|
const home = os.homedir();
|
|
115
|
+
const kiroHome = process.env.KIRO_HOME
|
|
116
|
+
? process.env.KIRO_HOME.replace(/^~(?=$|[\\/])/, home)
|
|
117
|
+
: path.join(home, '.kiro');
|
|
118
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || path.join(home, '.config');
|
|
119
|
+
|
|
114
120
|
return {
|
|
115
121
|
claude: {
|
|
116
122
|
name: 'Claude Code',
|
|
@@ -132,11 +138,17 @@ function getAgentConfigs(cwd) {
|
|
|
132
138
|
isDetected: fs.existsSync(path.join(cwd, '.cursor')) || fs.existsSync(path.join(home, '.cursor')),
|
|
133
139
|
isCursorRule: true,
|
|
134
140
|
},
|
|
141
|
+
kiro: {
|
|
142
|
+
name: 'Kiro',
|
|
143
|
+
globalPath: path.join(kiroHome, 'skills'),
|
|
144
|
+
localPath: path.join(cwd, '.kiro', 'skills'),
|
|
145
|
+
isDetected: fs.existsSync(kiroHome) || fs.existsSync(path.join(cwd, '.kiro')),
|
|
146
|
+
},
|
|
135
147
|
opencode: {
|
|
136
148
|
name: 'OpenCode',
|
|
137
|
-
globalPath: path.join(
|
|
149
|
+
globalPath: path.join(xdgConfig, 'opencode', 'skills'),
|
|
138
150
|
localPath: path.join(cwd, '.opencode', 'skills'),
|
|
139
|
-
isDetected: fs.existsSync(path.join(
|
|
151
|
+
isDetected: fs.existsSync(path.join(xdgConfig, 'opencode')),
|
|
140
152
|
},
|
|
141
153
|
};
|
|
142
154
|
}
|
|
@@ -370,23 +382,118 @@ async function selectPrompt({ title, items, defaultIndex = 0, onConfirm }) {
|
|
|
370
382
|
// Token Verification
|
|
371
383
|
// -----------------------------------------------------------------------------
|
|
372
384
|
|
|
373
|
-
function
|
|
374
|
-
if (
|
|
375
|
-
|
|
385
|
+
function decodeEnvBuffer(buf) {
|
|
386
|
+
if (!Buffer.isBuffer(buf)) {
|
|
387
|
+
const str = String(buf || '');
|
|
388
|
+
return str.charCodeAt(0) === 0xfeff ? str.slice(1) : str;
|
|
376
389
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
390
|
+
if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) {
|
|
391
|
+
return buf.toString('utf16le', 2);
|
|
392
|
+
}
|
|
393
|
+
const str = buf.toString('utf8');
|
|
394
|
+
return str.charCodeAt(0) === 0xfeff ? str.slice(1) : str;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function getPowerShellProfilePaths(home) {
|
|
398
|
+
const paths = [];
|
|
399
|
+
if (process.env.PROFILE && process.env.PROFILE.toLowerCase().endsWith('.ps1')) {
|
|
400
|
+
paths.push(process.env.PROFILE);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const docRoots = [];
|
|
404
|
+
if (home) {
|
|
405
|
+
docRoots.push(
|
|
406
|
+
path.join(home, 'Documents'),
|
|
407
|
+
path.join(home, 'Documentos'),
|
|
408
|
+
path.join(home, 'OneDrive', 'Documents'),
|
|
409
|
+
path.join(home, 'OneDrive', 'Documentos'),
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const oneDriveEnvs = [
|
|
414
|
+
process.env.OneDrive,
|
|
415
|
+
process.env.OneDriveConsumer,
|
|
416
|
+
process.env.OneDriveCommercial,
|
|
417
|
+
].filter(Boolean);
|
|
418
|
+
|
|
419
|
+
for (const od of oneDriveEnvs) {
|
|
420
|
+
docRoots.push(path.join(od, 'Documents'), path.join(od, 'Documentos'));
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const shellDirs = ['PowerShell', 'WindowsPowerShell'];
|
|
424
|
+
const profileFiles = [
|
|
425
|
+
'Microsoft.PowerShell_profile.ps1',
|
|
426
|
+
'Microsoft.VSCode_profile.ps1',
|
|
427
|
+
'profile.ps1',
|
|
384
428
|
];
|
|
429
|
+
|
|
430
|
+
for (const docRoot of docRoots) {
|
|
431
|
+
if (!fs.existsSync(docRoot)) continue;
|
|
432
|
+
for (const shellDir of shellDirs) {
|
|
433
|
+
const fullShellDir = path.join(docRoot, shellDir);
|
|
434
|
+
if (!fs.existsSync(fullShellDir)) continue;
|
|
435
|
+
for (const profileFile of profileFiles) {
|
|
436
|
+
paths.push(path.join(fullShellDir, profileFile));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (home) {
|
|
442
|
+
const xdgPsDir = path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'powershell');
|
|
443
|
+
if (fs.existsSync(xdgPsDir)) {
|
|
444
|
+
for (const profileFile of profileFiles) {
|
|
445
|
+
paths.push(path.join(xdgPsDir, profileFile));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return Array.from(new Set(paths));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function hasFigmaTokenInText(text) {
|
|
454
|
+
if (!text) return false;
|
|
455
|
+
const lines = text.split(/\r?\n/);
|
|
456
|
+
for (const rawLine of lines) {
|
|
457
|
+
const trimmed = rawLine.replace(/^\uFEFF/, '').trim();
|
|
458
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
459
|
+
if (
|
|
460
|
+
/^(?:export\s+|set\s+"?|\$env:|\$\{env:)?(?:FIGMA_ACCESS_TOKEN|FIGMA_TOKEN)\}?[\s="']/i.test(trimmed) ||
|
|
461
|
+
/^\[(?:System\.)?Environment\]::SetEnvironmentVariable\(\s*['"](?:FIGMA_ACCESS_TOKEN|FIGMA_TOKEN)['"]/i.test(trimmed)
|
|
462
|
+
) {
|
|
463
|
+
return true;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function checkFigmaToken(options = {}) {
|
|
470
|
+
const ignoreProcessEnv = Boolean(options.ignoreProcessEnv);
|
|
471
|
+
if (!ignoreProcessEnv && (process.env.FIGMA_ACCESS_TOKEN || process.env.FIGMA_TOKEN)) {
|
|
472
|
+
return { found: true, source: 'environment variable' };
|
|
473
|
+
}
|
|
474
|
+
const cwd = options.cwd || process.cwd();
|
|
475
|
+
const home = options.home !== undefined ? options.home : (process.env.HOME || process.env.USERPROFILE || os.homedir());
|
|
476
|
+
const candidates = Array.from(
|
|
477
|
+
new Set(
|
|
478
|
+
[
|
|
479
|
+
path.join(cwd, '.env'),
|
|
480
|
+
path.join(cwd, '.env.local'),
|
|
481
|
+
home ? path.join(home, '.env') : null,
|
|
482
|
+
home ? path.join(home, '.zshrc') : null,
|
|
483
|
+
home ? path.join(home, '.zprofile') : null,
|
|
484
|
+
home ? path.join(home, '.zshenv') : null,
|
|
485
|
+
home ? path.join(home, '.bashrc') : null,
|
|
486
|
+
home ? path.join(home, '.bash_profile') : null,
|
|
487
|
+
home ? path.join(home, '.profile') : null,
|
|
488
|
+
...getPowerShellProfilePaths(home),
|
|
489
|
+
].filter(Boolean),
|
|
490
|
+
),
|
|
491
|
+
);
|
|
385
492
|
for (const c of candidates) {
|
|
386
493
|
if (fs.existsSync(c)) {
|
|
387
494
|
try {
|
|
388
|
-
const text = fs.readFileSync(c
|
|
389
|
-
if (
|
|
495
|
+
const text = decodeEnvBuffer(fs.readFileSync(c));
|
|
496
|
+
if (hasFigmaTokenInText(text)) {
|
|
390
497
|
return { found: true, source: path.basename(c) };
|
|
391
498
|
}
|
|
392
499
|
} catch (_) {}
|
|
@@ -399,12 +506,32 @@ function checkFigmaToken() {
|
|
|
399
506
|
// Skill Installation Logic
|
|
400
507
|
// -----------------------------------------------------------------------------
|
|
401
508
|
|
|
509
|
+
const EXCLUDED_SKILL_DIRS = new Set(['demo', 'demos', '.specs', 'node_modules', '.git']);
|
|
510
|
+
|
|
511
|
+
function shouldCopySkillEntry(rootSkillPath) {
|
|
512
|
+
return (srcPath) => {
|
|
513
|
+
const rel = path.relative(rootSkillPath, srcPath);
|
|
514
|
+
if (!rel) return true;
|
|
515
|
+
const parts = rel.split(/[\\/]/);
|
|
516
|
+
const base = parts[parts.length - 1];
|
|
517
|
+
if (base === '.DS_Store' || base === 'Thumbs.db' || /^test_.*\.js$/i.test(base)) {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
for (let i = 0; i < parts.length; i++) {
|
|
521
|
+
if (EXCLUDED_SKILL_DIRS.has(parts[i])) return false;
|
|
522
|
+
if (parts[i] === 'figma-plugin' && parts[i + 1] === 'assets') return false;
|
|
523
|
+
}
|
|
524
|
+
return true;
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
|
|
402
528
|
function installSkill(skill, destDir, useSymlink = false, isCursor = false) {
|
|
403
529
|
if (!fs.existsSync(destDir)) {
|
|
404
530
|
fs.mkdirSync(destDir, { recursive: true });
|
|
405
531
|
}
|
|
406
532
|
|
|
407
533
|
const targetPath = path.join(destDir, skill.name);
|
|
534
|
+
const copyFilter = shouldCopySkillEntry(skill.path);
|
|
408
535
|
|
|
409
536
|
if (isCursor) {
|
|
410
537
|
const cursorSkillsDir = path.join(path.dirname(destDir), 'skills', skill.name);
|
|
@@ -413,16 +540,17 @@ function installSkill(skill, destDir, useSymlink = false, isCursor = false) {
|
|
|
413
540
|
if (fs.existsSync(cursorSkillsDir)) {
|
|
414
541
|
fs.rmSync(cursorSkillsDir, { recursive: true, force: true });
|
|
415
542
|
}
|
|
416
|
-
fs.cpSync(skill.path, cursorSkillsDir, { recursive: true });
|
|
543
|
+
fs.cpSync(skill.path, cursorSkillsDir, { recursive: true, filter: copyFilter });
|
|
417
544
|
|
|
545
|
+
const rawSkillMd = fs.readFileSync(path.join(skill.path, 'SKILL.md'), 'utf8');
|
|
546
|
+
const skillBody = rawSkillMd.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n*/, '');
|
|
418
547
|
const ruleFile = path.join(destDir, `${skill.name}.mdc`);
|
|
419
548
|
const ruleContent = `---
|
|
420
549
|
description: ${skill.description}
|
|
421
550
|
globs: *
|
|
422
551
|
---
|
|
423
552
|
|
|
424
|
-
${
|
|
425
|
-
`;
|
|
553
|
+
${skillBody}`;
|
|
426
554
|
fs.writeFileSync(ruleFile, ruleContent, 'utf8');
|
|
427
555
|
return { targetPath: ruleFile, isCursorRule: true };
|
|
428
556
|
}
|
|
@@ -433,14 +561,15 @@ ${fs.readFileSync(path.join(skill.path, 'SKILL.md'), 'utf8')}
|
|
|
433
561
|
|
|
434
562
|
if (useSymlink) {
|
|
435
563
|
try {
|
|
436
|
-
|
|
564
|
+
const symlinkType = process.platform === 'win32' ? 'junction' : 'dir';
|
|
565
|
+
fs.symlinkSync(skill.path, targetPath, symlinkType);
|
|
437
566
|
return { targetPath, isSymlink: true };
|
|
438
567
|
} catch (e) {
|
|
439
|
-
fs.cpSync(skill.path, targetPath, { recursive: true });
|
|
568
|
+
fs.cpSync(skill.path, targetPath, { recursive: true, filter: copyFilter });
|
|
440
569
|
return { targetPath, isSymlink: false };
|
|
441
570
|
}
|
|
442
571
|
} else {
|
|
443
|
-
fs.cpSync(skill.path, targetPath, { recursive: true });
|
|
572
|
+
fs.cpSync(skill.path, targetPath, { recursive: true, filter: copyFilter });
|
|
444
573
|
const scriptsDir = path.join(targetPath, 'scripts');
|
|
445
574
|
if (fs.existsSync(scriptsDir)) {
|
|
446
575
|
try {
|
|
@@ -589,30 +718,34 @@ async function main() {
|
|
|
589
718
|
let targetDestinations = [];
|
|
590
719
|
|
|
591
720
|
if (flagDest) {
|
|
721
|
+
const expandedDest = flagDest.replace(/^~(?=$|[\\/])/, os.homedir());
|
|
592
722
|
targetDestinations.push({
|
|
593
723
|
name: 'Custom Directory',
|
|
594
|
-
path: path.resolve(cwd,
|
|
724
|
+
path: path.resolve(cwd, expandedDest),
|
|
595
725
|
isCursor: false,
|
|
596
726
|
});
|
|
597
727
|
} else if (flagAgent) {
|
|
598
|
-
const
|
|
599
|
-
|
|
728
|
+
const normalizedAgent = flagAgent === 'kiro-ide' || flagAgent === 'kiro-cli' ? 'kiro' : flagAgent;
|
|
729
|
+
const resolveScopeIsGlobal = (agentKey) => (flagScope ? flagScope === 'global' : agentKey !== 'cursor');
|
|
730
|
+
if (normalizedAgent === 'all') {
|
|
600
731
|
for (const [key, cfg] of Object.entries(agentConfigs)) {
|
|
732
|
+
const useGlobal = resolveScopeIsGlobal(key);
|
|
601
733
|
targetDestinations.push({
|
|
602
734
|
name: cfg.name,
|
|
603
|
-
path:
|
|
735
|
+
path: useGlobal ? cfg.globalPath : cfg.localPath,
|
|
604
736
|
isCursor: !!cfg.isCursorRule,
|
|
605
737
|
});
|
|
606
738
|
}
|
|
607
|
-
} else if (agentConfigs[
|
|
608
|
-
const cfg = agentConfigs[
|
|
739
|
+
} else if (agentConfigs[normalizedAgent]) {
|
|
740
|
+
const cfg = agentConfigs[normalizedAgent];
|
|
741
|
+
const useGlobal = resolveScopeIsGlobal(normalizedAgent);
|
|
609
742
|
targetDestinations.push({
|
|
610
743
|
name: cfg.name,
|
|
611
|
-
path:
|
|
744
|
+
path: useGlobal ? cfg.globalPath : cfg.localPath,
|
|
612
745
|
isCursor: !!cfg.isCursorRule,
|
|
613
746
|
});
|
|
614
747
|
} else {
|
|
615
|
-
console.error(style.yellow(`⚠ Unknown agent "${flagAgent}". Options: claude, antigravity, cursor, opencode, all`));
|
|
748
|
+
console.error(style.yellow(`⚠ Unknown agent "${flagAgent}". Options: claude, antigravity, cursor, kiro, opencode, all`));
|
|
616
749
|
process.exit(1);
|
|
617
750
|
}
|
|
618
751
|
} else {
|
|
@@ -620,6 +753,7 @@ async function main() {
|
|
|
620
753
|
{ id: 'claude', ...agentConfigs.claude },
|
|
621
754
|
{ id: 'antigravity', ...agentConfigs.antigravity },
|
|
622
755
|
{ id: 'cursor', ...agentConfigs.cursor },
|
|
756
|
+
{ id: 'kiro', ...agentConfigs.kiro },
|
|
623
757
|
{ id: 'opencode', ...agentConfigs.opencode },
|
|
624
758
|
{ id: 'all', name: 'All detected agents' },
|
|
625
759
|
];
|
|
@@ -639,8 +773,8 @@ async function main() {
|
|
|
639
773
|
},
|
|
640
774
|
});
|
|
641
775
|
|
|
642
|
-
// Scope selection (Global vs Local)
|
|
643
|
-
let isGlobal =
|
|
776
|
+
// Scope selection (Global vs Local — Cursor rules live in workspace .cursor/rules by default)
|
|
777
|
+
let isGlobal = selectedAgent.id !== 'cursor';
|
|
644
778
|
if (selectedAgent.id !== 'cursor') {
|
|
645
779
|
const scopeOptions = [
|
|
646
780
|
{
|
|
@@ -668,9 +802,10 @@ async function main() {
|
|
|
668
802
|
if (selectedAgent.id === 'all') {
|
|
669
803
|
for (const [key, cfg] of Object.entries(agentConfigs)) {
|
|
670
804
|
if (cfg.isDetected) {
|
|
805
|
+
const useGlobal = key === 'cursor' ? false : isGlobal;
|
|
671
806
|
targetDestinations.push({
|
|
672
807
|
name: cfg.name,
|
|
673
|
-
path:
|
|
808
|
+
path: useGlobal ? cfg.globalPath : cfg.localPath,
|
|
674
809
|
isCursor: !!cfg.isCursorRule,
|
|
675
810
|
});
|
|
676
811
|
}
|
|
@@ -713,7 +848,12 @@ async function main() {
|
|
|
713
848
|
} else {
|
|
714
849
|
ui.item(`${style.yellow('⚠')} ${style.bold('FIGMA_ACCESS_TOKEN')} not detected.`);
|
|
715
850
|
ui.item(`${style.dim('To scrape designs from Figma, set your token:')}`);
|
|
716
|
-
|
|
851
|
+
if (process.platform === 'win32') {
|
|
852
|
+
ui.item(`${style.cyan('[Environment]::SetEnvironmentVariable("FIGMA_ACCESS_TOKEN", "figd_your_token_here", "User")')}`);
|
|
853
|
+
ui.item(`${style.dim('or in PowerShell ($PROFILE): $env:FIGMA_ACCESS_TOKEN="figd_your_token_here"')}`);
|
|
854
|
+
} else {
|
|
855
|
+
ui.item(`${style.cyan('export FIGMA_ACCESS_TOKEN="figd_your_token_here"')}`);
|
|
856
|
+
}
|
|
717
857
|
ui.item(`${style.dim('or add it to your project\'s .env file.')}`);
|
|
718
858
|
}
|
|
719
859
|
ui.bar();
|
|
@@ -82,10 +82,16 @@ Choose one of the following terminal commands:
|
|
|
82
82
|
```bash
|
|
83
83
|
export FIGMA_ACCESS_TOKEN="figd_your_token_here"
|
|
84
84
|
```
|
|
85
|
-
- **Persist globally
|
|
85
|
+
- **Persist globally on macOS / Linux (`~/.zshrc`)**:
|
|
86
86
|
```bash
|
|
87
87
|
echo 'export FIGMA_ACCESS_TOKEN="figd_your_token_here"' >> ~/.zshrc && source ~/.zshrc
|
|
88
88
|
```
|
|
89
|
+
- **Persist globally on Windows (PowerShell)**:
|
|
90
|
+
```powershell
|
|
91
|
+
[Environment]::SetEnvironmentVariable("FIGMA_ACCESS_TOKEN", "figd_your_token_here", "User")
|
|
92
|
+
# Or inside your PowerShell profile ($PROFILE):
|
|
93
|
+
# $env:FIGMA_ACCESS_TOKEN="figd_your_token_here"
|
|
94
|
+
```
|
|
89
95
|
|
|
90
96
|
*(Optional: If behind a corporate proxy with self-signed SSL certificates, run `echo 'FIGMA_IGNORE_SSL=true' >> .env`).*
|
|
91
97
|
|
|
@@ -110,8 +116,8 @@ node figma-extractor/scripts/fetch_figma.js "<FigmaURL>" --spec card -i
|
|
|
110
116
|
# Atomic extraction with shallow instances (collapses component internals into props)
|
|
111
117
|
node figma-extractor/scripts/fetch_figma.js "<FigmaURL>" --spec card --shallow-instances
|
|
112
118
|
|
|
113
|
-
# Extract only design tokens dictionary
|
|
114
|
-
node figma-extractor/scripts/fetch_figma.js "<FigmaURL>" --tokens
|
|
119
|
+
# Extract only design tokens dictionary into ./.specs/tokens.json
|
|
120
|
+
node figma-extractor/scripts/fetch_figma.js "<FigmaURL>" --tokens --spec tokens
|
|
115
121
|
```
|
|
116
122
|
|
|
117
123
|
---
|
|
@@ -146,10 +152,12 @@ node figma-extractor/scripts/fetch_figma.js "<FigmaURL>" --tokens > ./.specs/tok
|
|
|
146
152
|
For developers who prefer exporting specifications directly from Figma Desktop without configuring API access tokens or consuming REST API quotas:
|
|
147
153
|
|
|
148
154
|
* **Published Figma Plugin (Recommended)**: Install [**Figma Node JSON Extractor** on Figma Community](https://www.figma.com/community/plugin/1691244420692136789/figma-node-json-extractor) directly, or search for it under Figma Plugins.
|
|
149
|
-
* **Manual Local Installation (Local / Offline Backup)**: In Figma Desktop, go to **Plugins** $\rightarrow$ **Development** $\rightarrow$ **Import plugin from manifest...** and select `manifest.json` from your installed skills directory:
|
|
155
|
+
* **Manual Local Installation (Local / Offline Backup)**: In Figma Desktop, go to **Plugins** $\rightarrow$ **Development** $\rightarrow$ **Import plugin from manifest...** and select `manifest.json` from your installed skills directory (`~` corresponds to `%USERPROFILE%` on Windows):
|
|
150
156
|
* **Claude Code**: `~/.claude/skills/figma-extractor/figma-plugin/manifest.json`
|
|
151
|
-
* **
|
|
157
|
+
* **Cursor**: `.cursor/skills/figma-extractor/figma-plugin/manifest.json` (or `~/.cursor/skills/...`)
|
|
152
158
|
* **OpenCode**: `~/.config/opencode/skills/figma-extractor/figma-plugin/manifest.json`
|
|
159
|
+
* **Google Antigravity**: `~/.gemini/config/skills/figma-extractor/figma-plugin/manifest.json`
|
|
160
|
+
* **Kiro**: `~/.kiro/skills/figma-extractor/figma-plugin/manifest.json`
|
|
153
161
|
* **Cloned Git Repo**: `<repo-root>/figma-extractor/figma-plugin/manifest.json`
|
|
154
162
|
* **Export Options**: Select any frame or component, then click:
|
|
155
163
|
* **📋 Copy Node JSON**: Copies the clean AST specification to your clipboard.
|
|
@@ -165,7 +173,7 @@ The skill includes the following resources:
|
|
|
165
173
|
|
|
166
174
|
- **Agent Instruction Guide (`SKILL.md`)**: The core skill definition read by AI agents containing workflow triggers and operational boundaries (spec ingestion only).
|
|
167
175
|
- **Core Extraction Script (`scripts/fetch_figma.js`)**: The standalone, zero-dependency Node.js script that interfaces with the Figma REST API, resolves tokens, prunes the AST, and downloads preview images.
|
|
168
|
-
- **Unit Test Suite (`scripts/test_fetch_figma.js`)**:
|
|
176
|
+
- **Unit Test Suite (`scripts/test_fetch_figma.js`)**: Automated unit tests verifying environment/profile parsing, URL parsing, token formatting, component prop cleaning, rate-limit gates, and node pruning without requiring network access.
|
|
169
177
|
- **Companion Figma Plugin (`figma-plugin/`)**: Zero-API desktop exporter for extracting AST and previews directly inside Figma Desktop.
|
|
170
178
|
- **Reference Documentation (`README.md`)**: This guide explaining architecture, comparison, and operational flags.
|
|
171
179
|
|
package/figma-extractor/SKILL.md
CHANGED
|
@@ -25,7 +25,7 @@ This skill serves as the **Specification Ingestion Layer** in a **Spec-Driven De
|
|
|
25
25
|
> **NEVER use browser tools (Playwright, Puppeteer, Chrome DevTools, `browser_navigate`, `read_browser_page`) on Figma URLs.**
|
|
26
26
|
>
|
|
27
27
|
> 1. **Figma Canvas is WebGL/Wasm**: Figma renders design canvas elements inside an HTML5 `<canvas>` via WebAssembly. There are NO inspectable HTML DOM elements for frames, layers, texts, or styles. Browser automation cannot extract Figma nodes or properties.
|
|
28
|
-
> 2. **Never search
|
|
28
|
+
> 2. **Never search for local browser binaries**: Do not search OS application directories (e.g., `/Applications`, `Program Files`, `/usr/bin`) or run browser automation scripts.
|
|
29
29
|
> 3. **API Errors Are Final**: If `fetch_figma.js` returns an API error (e.g., 401 Unauthorized, 403 Forbidden, or 429 Rate Limited), **report the error directly to the user**. Guide them to check their `FIGMA_ACCESS_TOKEN`, permissions, or wait out rate limits. Do NOT attempt browser scraping fallbacks.
|
|
30
30
|
|
|
31
31
|
---
|
|
@@ -35,7 +35,7 @@ This skill serves as the **Specification Ingestion Layer** in a **Spec-Driven De
|
|
|
35
35
|
To protect your Figma file quota and prevent lockout penalties, `fetch_figma.js` enforces an automatic safety gate:
|
|
36
36
|
* **Max 5 requests per minute** per file key (sliding window limit).
|
|
37
37
|
* **2.5s inter-request pacing** between consecutive requests.
|
|
38
|
-
* **Persistent Circuit Breaker**: If Figma returns HTTP 429, the script records the lockout time locally in
|
|
38
|
+
* **Persistent Circuit Breaker**: If Figma returns HTTP 429, the script records the lockout time locally in `.figma_ratelimit.json` inside the user's home directory. All future calls to that file key are **immediately blocked with zero network traffic**, preventing Figma from extending the lockout penalty.
|
|
39
39
|
|
|
40
40
|
### Agent Behavioral Rules (Zero-Hammer Protocol)
|
|
41
41
|
1. **Single-Shot Execution**: Run `fetch_figma.js` **once** per target frame or component.
|
|
@@ -49,7 +49,9 @@ To protect your Figma file quota and prevent lockout penalties, `fetch_figma.js`
|
|
|
49
49
|
A Figma Access Token (Personal Access Token or OAuth Token) is required.
|
|
50
50
|
|
|
51
51
|
Configure it using either:
|
|
52
|
-
1. **Environment Variable**:
|
|
52
|
+
1. **Environment Variable**:
|
|
53
|
+
- **macOS / Linux**: `export FIGMA_ACCESS_TOKEN="figd_your_token_here"` (or `FIGMA_TOKEN`)
|
|
54
|
+
- **Windows (PowerShell)**: `[Environment]::SetEnvironmentVariable("FIGMA_ACCESS_TOKEN", "figd_your_token_here", "User")` or `$env:FIGMA_ACCESS_TOKEN="figd_your_token_here"` in `$PROFILE`
|
|
53
55
|
2. **Workspace `.env` File**:
|
|
54
56
|
```env
|
|
55
57
|
FIGMA_ACCESS_TOKEN=figd_your_token_here
|
|
@@ -77,20 +79,20 @@ FIGMA_IGNORE_SSL=true
|
|
|
77
79
|
|
|
78
80
|
## Agent Execution Instructions
|
|
79
81
|
|
|
80
|
-
To extract a Figma frame or component as a specification artifact, run `fetch_figma.js` from
|
|
82
|
+
To extract a Figma frame or component as a specification artifact, run `fetch_figma.js` from this skill's `scripts/` directory:
|
|
81
83
|
|
|
82
84
|
```bash
|
|
83
85
|
# Recommended: Atomic extraction (writes ./.specs/<name>.json - 1 Tier 1 call)
|
|
84
|
-
node <skill-dir>/scripts/fetch_figma.js "<FigmaURL>" --spec <name>
|
|
86
|
+
node "<skill-dir>/scripts/fetch_figma.js" "<FigmaURL>" --spec <name>
|
|
85
87
|
|
|
86
88
|
# Optional: With companion preview image (writes .json and .png - 2 Tier 1 calls)
|
|
87
|
-
node <skill-dir>/scripts/fetch_figma.js "<FigmaURL>" --spec <name> -i
|
|
89
|
+
node "<skill-dir>/scripts/fetch_figma.js" "<FigmaURL>" --spec <name> -i
|
|
88
90
|
```
|
|
89
91
|
|
|
90
|
-
**
|
|
91
|
-
- **
|
|
92
|
-
- **
|
|
93
|
-
- **
|
|
92
|
+
**Resolving `<skill-dir>` (Agent-Agnostic)**:
|
|
93
|
+
Substitute `<skill-dir>` with the directory where this skill is installed — **never guess another agent's folder** (always keep double quotes around the script path for Windows/macOS/Linux compatibility):
|
|
94
|
+
- **Standard Skills (`SKILL.md`)**: `<skill-dir>` is the exact parent directory of this `SKILL.md` file.
|
|
95
|
+
- **Cursor Rule (`.cursor/rules/figma-extractor.mdc`)**: `<skill-dir>` is the sibling `.cursor/skills/figma-extractor` directory (replace `rules/figma-extractor.mdc` with `skills/figma-extractor`).
|
|
94
96
|
|
|
95
97
|
---
|
|
96
98
|
|
|
@@ -102,20 +104,20 @@ Extract node metadata directly into `./.specs/`:
|
|
|
102
104
|
|
|
103
105
|
```bash
|
|
104
106
|
# Atomic AST extraction (1 Tier 1 call - recommended)
|
|
105
|
-
node
|
|
107
|
+
node "<skill-dir>/scripts/fetch_figma.js" "https://www.figma.com/design/:fileKey/:name?node-id=4023-474" --spec card
|
|
106
108
|
|
|
107
109
|
# Optional: With rendered preview image (2 Tier 1 calls)
|
|
108
|
-
node
|
|
110
|
+
node "<skill-dir>/scripts/fetch_figma.js" "https://www.figma.com/design/:fileKey/:name?node-id=4023-474" --spec card -i
|
|
109
111
|
|
|
110
112
|
# Shallow instances mode (collapses internal component layers into clean props)
|
|
111
|
-
node
|
|
113
|
+
node "<skill-dir>/scripts/fetch_figma.js" "https://www.figma.com/design/:fileKey/:name?node-id=4023-474" --spec card --shallow-instances
|
|
112
114
|
```
|
|
113
115
|
|
|
114
116
|
### 2. Design Tokens Dictionary Mode
|
|
115
117
|
|
|
116
118
|
```bash
|
|
117
|
-
# Export only the file's design token dictionary (colors, spacing, radii)
|
|
118
|
-
node
|
|
119
|
+
# Export only the file's design token dictionary (colors, spacing, radii) into ./.specs/tokens.json
|
|
120
|
+
node "<skill-dir>/scripts/fetch_figma.js" "https://www.figma.com/design/:fileKey/:name" --tokens --spec tokens
|
|
119
121
|
```
|
|
120
122
|
|
|
121
123
|
### 3. CLI Flags Reference
|
|
@@ -192,7 +194,7 @@ When an `INSTANCE` node is present, internal property hashes are converted into
|
|
|
192
194
|
|
|
193
195
|
- **Token Expired / Missing (`FIGMA_TOKEN_EXPIRED_OR_INVALID` / `isTokenExpiredOrInvalid`)**:
|
|
194
196
|
- Alert the user that their Figma access token is missing, invalid, or expired.
|
|
195
|
-
- Instruct the user to create a token in **Figma Settings → Personal Access Tokens** and
|
|
197
|
+
- Instruct the user to create a token in **Figma Settings → Personal Access Tokens** and configure `FIGMA_ACCESS_TOKEN` in their environment or workspace `.env` file.
|
|
196
198
|
- **Node Not Found**:
|
|
197
199
|
- Alert the user that the node ID could not be found in the specified file.
|
|
198
200
|
- **Rate Limited (`FIGMA_RATE_LIMIT_EXCEEDED` / `FIGMA_CIRCUIT_BREAKER_ACTIVE`)**:
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<a href="../../LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT" /></a>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
|
-
Inspect, copy, and export compact, design-system-aware Node JSON and high-resolution companion preview images directly from **Figma Desktop** for AI coding assistants (Claude Code,
|
|
13
|
+
Inspect, copy, and export compact, design-system-aware Node JSON and high-resolution companion preview images directly from **Figma Desktop** for AI coding assistants (Claude Code, Kiro, Cursor, OpenCode, Antigravity) and frontend engineering.
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
@@ -37,14 +37,19 @@ To install the plugin directly from your local skills folder or repository:
|
|
|
37
37
|
1. Open the **Figma Desktop app**.
|
|
38
38
|
2. In the top-left menu, navigate to:
|
|
39
39
|
$$\text{Plugins} \longrightarrow \text{Development} \longrightarrow \text{Import plugin from manifest...}$$
|
|
40
|
-
3. Select `manifest.json` from your machine based on your setup:
|
|
40
|
+
3. Select `manifest.json` from your machine based on your setup (`~` corresponds to `%USERPROFILE%` on Windows):
|
|
41
41
|
* **Installed via `npx @pedroab0/skills` (Global)**:
|
|
42
42
|
* **Claude Code**: `~/.claude/skills/figma-extractor/figma-plugin/manifest.json`
|
|
43
|
-
* **
|
|
43
|
+
* **Cursor**: `~/.cursor/skills/figma-extractor/figma-plugin/manifest.json`
|
|
44
44
|
* **OpenCode**: `~/.config/opencode/skills/figma-extractor/figma-plugin/manifest.json`
|
|
45
|
+
* **Google Antigravity**: `~/.gemini/config/skills/figma-extractor/figma-plugin/manifest.json`
|
|
46
|
+
* **Kiro**: `~/.kiro/skills/figma-extractor/figma-plugin/manifest.json`
|
|
45
47
|
* **Installed in local workspace (`--local`)**:
|
|
46
48
|
* `.claude/skills/figma-extractor/figma-plugin/manifest.json`
|
|
49
|
+
* `.cursor/skills/figma-extractor/figma-plugin/manifest.json`
|
|
50
|
+
* `.opencode/skills/figma-extractor/figma-plugin/manifest.json`
|
|
47
51
|
* `.gemini/skills/figma-extractor/figma-plugin/manifest.json`
|
|
52
|
+
* `.kiro/skills/figma-extractor/figma-plugin/manifest.json`
|
|
48
53
|
* **Cloned Git repository**:
|
|
49
54
|
* `<repo-root>/figma-extractor/figma-plugin/manifest.json`
|
|
50
55
|
4. The plugin is now permanently loaded under **Plugins** $\rightarrow$ **Development**.
|
|
@@ -54,7 +59,7 @@ To install the plugin directly from your local skills folder or repository:
|
|
|
54
59
|
## How to Extract Node JSON
|
|
55
60
|
|
|
56
61
|
1. **Select any frame or component** on your canvas (e.g., Transaction Card, Navbar, Modal).
|
|
57
|
-
2. Right-click the element $\rightarrow$ **Plugins** $\rightarrow$ **Figma Node JSON Extractor** (or press `⌥ ⌘ P` to re-run).
|
|
62
|
+
2. Right-click the element $\rightarrow$ **Plugins** $\rightarrow$ **Figma Node JSON Extractor** (or press `⌥ ⌘ P` on macOS / `Ctrl + Alt + P` on Windows to re-run).
|
|
58
63
|
3. In the plugin popup, choose your export format:
|
|
59
64
|
* **📋 Copy Node JSON**: Copies the clean AST specification directly to your clipboard (ready to paste into chat).
|
|
60
65
|
* **💾 .json**: Downloads `<name>.json` containing layout, dimensions, tokens, and props.
|
|
@@ -72,7 +77,7 @@ To install the plugin directly from your local skills folder or repository:
|
|
|
72
77
|
|
|
73
78
|
## How to Tell Your Agent to Build the UI
|
|
74
79
|
|
|
75
|
-
Once the specification is in `./.specs/`, simply instruct your agent in
|
|
80
|
+
Once the specification is in `./.specs/`, simply instruct your agent in Claude Code, Kiro, Cursor, OpenCode, or Antigravity:
|
|
76
81
|
|
|
77
82
|
```text
|
|
78
83
|
Build the component from .specs/card.json
|