@pedroab0/skills 1.0.1 → 1.1.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 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 Cursor).
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 skills folder (e.g. `~/.claude/skills/figma-extractor/figma-plugin/manifest.json` or `~/.gemini/config/skills/figma-extractor/figma-plugin/manifest.json`).
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` - `5`) to select directly.
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 OpenCode.
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(home, '.config', 'opencode', 'skills'),
149
+ globalPath: path.join(xdgConfig, 'opencode', 'skills'),
138
150
  localPath: path.join(cwd, '.opencode', 'skills'),
139
- isDetected: fs.existsSync(path.join(home, '.config', 'opencode')),
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 checkFigmaToken() {
374
- if (process.env.FIGMA_ACCESS_TOKEN || process.env.FIGMA_TOKEN) {
375
- return { found: true, source: 'environment variable' };
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
- const home = os.homedir();
378
- const candidates = [
379
- path.join(process.cwd(), '.env'),
380
- path.join(process.cwd(), '.env.local'),
381
- path.join(home, '.zshrc'),
382
- path.join(home, '.bashrc'),
383
- path.join(home, '.profile'),
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, 'utf8');
389
- if (/FIGMA_ACCESS_TOKEN|FIGMA_TOKEN/.test(text)) {
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 (_) {}
@@ -415,14 +522,15 @@ function installSkill(skill, destDir, useSymlink = false, isCursor = false) {
415
522
  }
416
523
  fs.cpSync(skill.path, cursorSkillsDir, { recursive: true });
417
524
 
525
+ const rawSkillMd = fs.readFileSync(path.join(skill.path, 'SKILL.md'), 'utf8');
526
+ const skillBody = rawSkillMd.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n*/, '');
418
527
  const ruleFile = path.join(destDir, `${skill.name}.mdc`);
419
528
  const ruleContent = `---
420
529
  description: ${skill.description}
421
530
  globs: *
422
531
  ---
423
532
 
424
- ${fs.readFileSync(path.join(skill.path, 'SKILL.md'), 'utf8')}
425
- `;
533
+ ${skillBody}`;
426
534
  fs.writeFileSync(ruleFile, ruleContent, 'utf8');
427
535
  return { targetPath: ruleFile, isCursorRule: true };
428
536
  }
@@ -433,7 +541,8 @@ ${fs.readFileSync(path.join(skill.path, 'SKILL.md'), 'utf8')}
433
541
 
434
542
  if (useSymlink) {
435
543
  try {
436
- fs.symlinkSync(skill.path, targetPath, 'dir');
544
+ const symlinkType = process.platform === 'win32' ? 'junction' : 'dir';
545
+ fs.symlinkSync(skill.path, targetPath, symlinkType);
437
546
  return { targetPath, isSymlink: true };
438
547
  } catch (e) {
439
548
  fs.cpSync(skill.path, targetPath, { recursive: true });
@@ -589,30 +698,34 @@ async function main() {
589
698
  let targetDestinations = [];
590
699
 
591
700
  if (flagDest) {
701
+ const expandedDest = flagDest.replace(/^~(?=$|[\\/])/, os.homedir());
592
702
  targetDestinations.push({
593
703
  name: 'Custom Directory',
594
- path: path.resolve(cwd, flagDest),
704
+ path: path.resolve(cwd, expandedDest),
595
705
  isCursor: false,
596
706
  });
597
707
  } else if (flagAgent) {
598
- const isGlobal = flagScope !== 'local';
599
- if (flagAgent === 'all') {
708
+ const normalizedAgent = flagAgent === 'kiro-ide' || flagAgent === 'kiro-cli' ? 'kiro' : flagAgent;
709
+ const resolveScopeIsGlobal = (agentKey) => (flagScope ? flagScope === 'global' : agentKey !== 'cursor');
710
+ if (normalizedAgent === 'all') {
600
711
  for (const [key, cfg] of Object.entries(agentConfigs)) {
712
+ const useGlobal = resolveScopeIsGlobal(key);
601
713
  targetDestinations.push({
602
714
  name: cfg.name,
603
- path: isGlobal ? cfg.globalPath : cfg.localPath,
715
+ path: useGlobal ? cfg.globalPath : cfg.localPath,
604
716
  isCursor: !!cfg.isCursorRule,
605
717
  });
606
718
  }
607
- } else if (agentConfigs[flagAgent]) {
608
- const cfg = agentConfigs[flagAgent];
719
+ } else if (agentConfigs[normalizedAgent]) {
720
+ const cfg = agentConfigs[normalizedAgent];
721
+ const useGlobal = resolveScopeIsGlobal(normalizedAgent);
609
722
  targetDestinations.push({
610
723
  name: cfg.name,
611
- path: isGlobal ? cfg.globalPath : cfg.localPath,
724
+ path: useGlobal ? cfg.globalPath : cfg.localPath,
612
725
  isCursor: !!cfg.isCursorRule,
613
726
  });
614
727
  } else {
615
- console.error(style.yellow(`⚠ Unknown agent "${flagAgent}". Options: claude, antigravity, cursor, opencode, all`));
728
+ console.error(style.yellow(`⚠ Unknown agent "${flagAgent}". Options: claude, antigravity, cursor, kiro, opencode, all`));
616
729
  process.exit(1);
617
730
  }
618
731
  } else {
@@ -620,6 +733,7 @@ async function main() {
620
733
  { id: 'claude', ...agentConfigs.claude },
621
734
  { id: 'antigravity', ...agentConfigs.antigravity },
622
735
  { id: 'cursor', ...agentConfigs.cursor },
736
+ { id: 'kiro', ...agentConfigs.kiro },
623
737
  { id: 'opencode', ...agentConfigs.opencode },
624
738
  { id: 'all', name: 'All detected agents' },
625
739
  ];
@@ -639,8 +753,8 @@ async function main() {
639
753
  },
640
754
  });
641
755
 
642
- // Scope selection (Global vs Local)
643
- let isGlobal = true;
756
+ // Scope selection (Global vs Local — Cursor rules live in workspace .cursor/rules by default)
757
+ let isGlobal = selectedAgent.id !== 'cursor';
644
758
  if (selectedAgent.id !== 'cursor') {
645
759
  const scopeOptions = [
646
760
  {
@@ -668,9 +782,10 @@ async function main() {
668
782
  if (selectedAgent.id === 'all') {
669
783
  for (const [key, cfg] of Object.entries(agentConfigs)) {
670
784
  if (cfg.isDetected) {
785
+ const useGlobal = key === 'cursor' ? false : isGlobal;
671
786
  targetDestinations.push({
672
787
  name: cfg.name,
673
- path: isGlobal ? cfg.globalPath : cfg.localPath,
788
+ path: useGlobal ? cfg.globalPath : cfg.localPath,
674
789
  isCursor: !!cfg.isCursorRule,
675
790
  });
676
791
  }
@@ -713,7 +828,12 @@ async function main() {
713
828
  } else {
714
829
  ui.item(`${style.yellow('⚠')} ${style.bold('FIGMA_ACCESS_TOKEN')} not detected.`);
715
830
  ui.item(`${style.dim('To scrape designs from Figma, set your token:')}`);
716
- ui.item(`${style.cyan('export FIGMA_ACCESS_TOKEN="figd_your_token_here"')}`);
831
+ if (process.platform === 'win32') {
832
+ ui.item(`${style.cyan('[Environment]::SetEnvironmentVariable("FIGMA_ACCESS_TOKEN", "figd_your_token_here", "User")')}`);
833
+ ui.item(`${style.dim('or in PowerShell ($PROFILE): $env:FIGMA_ACCESS_TOKEN="figd_your_token_here"')}`);
834
+ } else {
835
+ ui.item(`${style.cyan('export FIGMA_ACCESS_TOKEN="figd_your_token_here"')}`);
836
+ }
717
837
  ui.item(`${style.dim('or add it to your project\'s .env file.')}`);
718
838
  }
719
839
  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 across all terminal sessions (`~/.zshrc`)**:
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 (colors, spacing, radii)
114
- node figma-extractor/scripts/fetch_figma.js "<FigmaURL>" --tokens > ./.specs/tokens.json
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
- * **Google Antigravity**: `~/.gemini/config/skills/figma-extractor/figma-plugin/manifest.json`
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`)**: A suite of 40 automated unit tests verifying URL parsing, token formatting, component prop cleaning, rate-limit gates, and node pruning without requiring network access.
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
 
@@ -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 `/Applications` for browsers**: Do not search for installed browser binaries or run browser automation scripts.
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 `~/.figma_ratelimit.json`. All future calls to that file key are **immediately blocked with zero network traffic**, preventing Figma from extending the lockout penalty.
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**: `export FIGMA_ACCESS_TOKEN="figd_your_token_here"` or `export FIGMA_TOKEN="..."`
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 the skill's scripts directory:
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
- **Script Path Resolution**:
91
- - **Global Antigravity install**: `node ~/.gemini/config/skills/figma-extractor/scripts/fetch_figma.js "<FigmaURL>" --spec <name>`
92
- - **Global Claude Code install**: `node ~/.claude/skills/figma-extractor/scripts/fetch_figma.js "<FigmaURL>" --spec <name>`
93
- - **Project local install**: `node figma-extractor/scripts/fetch_figma.js "<FigmaURL>" --spec <name>`
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 figma-extractor/scripts/fetch_figma.js "https://www.figma.com/design/:fileKey/:name?node-id=4023-474" --spec card
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 figma-extractor/scripts/fetch_figma.js "https://www.figma.com/design/:fileKey/:name?node-id=4023-474" --spec card -i
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 figma-extractor/scripts/fetch_figma.js "https://www.figma.com/design/:fileKey/:name?node-id=4023-474" --spec card --shallow-instances
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 figma-extractor/scripts/fetch_figma.js "https://www.figma.com/design/:fileKey/:name" --tokens > ./.specs/tokens.json
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 export `FIGMA_ACCESS_TOKEN=your_token` or update `.env`.
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, Antigravity, Cursor) and frontend engineering.
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
- * **Google Antigravity**: `~/.gemini/config/skills/figma-extractor/figma-plugin/manifest.json`
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 Antigravity, Claude Code, or Cursor:
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