@sarjallab09/figma-intelligence 1.0.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/LICENSE +26 -0
- package/README.md +327 -0
- package/bin/cli.js +859 -0
- package/design-bridge/.env.example +5 -0
- package/design-bridge/bridge.js +196 -0
- package/design-bridge/lib/assets.js +367 -0
- package/design-bridge/lib/prompt.js +85 -0
- package/design-bridge/lib/server.js +66 -0
- package/design-bridge/lib/stitch.js +37 -0
- package/design-bridge/lib/tokens.js +82 -0
- package/design-bridge/package-lock.json +579 -0
- package/design-bridge/package.json +19 -0
- package/figma-bridge-plugin/README.md +97 -0
- package/figma-bridge-plugin/anthropic-chat-runner.js +192 -0
- package/figma-bridge-plugin/bridge-relay.js +2363 -0
- package/figma-bridge-plugin/chat-runner.js +459 -0
- package/figma-bridge-plugin/code.js +1528 -0
- package/figma-bridge-plugin/codex-runner.js +505 -0
- package/figma-bridge-plugin/component-schemas.js +110 -0
- package/figma-bridge-plugin/content-context.js +869 -0
- package/figma-bridge-plugin/create-button.js +216 -0
- package/figma-bridge-plugin/gemini-cli-runner.js +291 -0
- package/figma-bridge-plugin/gemini-runner.js +187 -0
- package/figma-bridge-plugin/html-to-figma.js +927 -0
- package/figma-bridge-plugin/knowledge-hub/.gitkeep +0 -0
- package/figma-bridge-plugin/knowledge-hub/uspec-references/anatomy-spec.md +159 -0
- package/figma-bridge-plugin/knowledge-hub/uspec-references/api-spec.md +162 -0
- package/figma-bridge-plugin/knowledge-hub/uspec-references/color-spec.md +148 -0
- package/figma-bridge-plugin/knowledge-hub/uspec-references/full-spec-template.md +314 -0
- package/figma-bridge-plugin/knowledge-hub/uspec-references/property-spec.md +175 -0
- package/figma-bridge-plugin/knowledge-hub/uspec-references/screen-reader-spec.md +180 -0
- package/figma-bridge-plugin/knowledge-hub/uspec-references/structure-spec.md +165 -0
- package/figma-bridge-plugin/manifest.json +21 -0
- package/figma-bridge-plugin/package-lock.json +1936 -0
- package/figma-bridge-plugin/package.json +20 -0
- package/figma-bridge-plugin/perplexity-runner.js +188 -0
- package/figma-bridge-plugin/references/SKILL.md +178 -0
- package/figma-bridge-plugin/references/anatomy-spec.md +159 -0
- package/figma-bridge-plugin/references/api-spec.md +162 -0
- package/figma-bridge-plugin/references/color-spec.md +148 -0
- package/figma-bridge-plugin/references/full-spec-template.md +314 -0
- package/figma-bridge-plugin/references/property-spec.md +175 -0
- package/figma-bridge-plugin/references/screen-reader-spec.md +180 -0
- package/figma-bridge-plugin/references/structure-spec.md +165 -0
- package/figma-bridge-plugin/shared-prompt-config.js +604 -0
- package/figma-bridge-plugin/spec-helpers/build-table.js +269 -0
- package/figma-bridge-plugin/spec-helpers/classify-elements.js +189 -0
- package/figma-bridge-plugin/spec-helpers/index.js +35 -0
- package/figma-bridge-plugin/spec-helpers/parse-figma-link.js +49 -0
- package/figma-bridge-plugin/spec-helpers/position-markers.js +158 -0
- package/figma-bridge-plugin/stitch-auth.js +322 -0
- package/figma-bridge-plugin/stitch-runner.js +1427 -0
- package/figma-bridge-plugin/token-resolver.js +107 -0
- package/figma-bridge-plugin/ui.html +4467 -0
- package/figma-intelligence-layer/.env.example +39 -0
- package/figma-intelligence-layer/docs/local-image-generation.md +60 -0
- package/figma-intelligence-layer/examples/comfyui-workflow-template.example.json +101 -0
- package/figma-intelligence-layer/jest.config.js +14 -0
- package/figma-intelligence-layer/mcp-config.json +19 -0
- package/figma-intelligence-layer/package-lock.json +5892 -0
- package/figma-intelligence-layer/package.json +48 -0
- package/figma-intelligence-layer/scripts/setup-comfyui-local.sh +67 -0
- package/figma-intelligence-layer/scripts/start-comfyui.sh +33 -0
- package/figma-intelligence-layer/src/index.ts +2233 -0
- package/figma-intelligence-layer/src/shared/auto-layout-validator.ts +404 -0
- package/figma-intelligence-layer/src/shared/cache.ts +187 -0
- package/figma-intelligence-layer/src/shared/color-operations.ts +533 -0
- package/figma-intelligence-layer/src/shared/color-utils.ts +138 -0
- package/figma-intelligence-layer/src/shared/component-script-builder.ts +413 -0
- package/figma-intelligence-layer/src/shared/component-templates.ts +2767 -0
- package/figma-intelligence-layer/src/shared/concept-taxonomy.ts +694 -0
- package/figma-intelligence-layer/src/shared/decision-log.ts +128 -0
- package/figma-intelligence-layer/src/shared/design-system-context.ts +568 -0
- package/figma-intelligence-layer/src/shared/design-system-intelligence.ts +131 -0
- package/figma-intelligence-layer/src/shared/design-system-matcher.ts +184 -0
- package/figma-intelligence-layer/src/shared/design-system-normalizers.ts +196 -0
- package/figma-intelligence-layer/src/shared/design-system-tokens.ts +295 -0
- package/figma-intelligence-layer/src/shared/dtcg-validator.ts +530 -0
- package/figma-intelligence-layer/src/shared/enrichment-pipeline.ts +671 -0
- package/figma-intelligence-layer/src/shared/figma-bridge.ts +1408 -0
- package/figma-intelligence-layer/src/shared/font-config.ts +126 -0
- package/figma-intelligence-layer/src/shared/icon-catalog.ts +360 -0
- package/figma-intelligence-layer/src/shared/icon-fetch.ts +80 -0
- package/figma-intelligence-layer/src/shared/prototype-script-builder.ts +162 -0
- package/figma-intelligence-layer/src/shared/response-compression.ts +440 -0
- package/figma-intelligence-layer/src/shared/semantic-token-catalog.ts +324 -0
- package/figma-intelligence-layer/src/shared/token-binder.ts +505 -0
- package/figma-intelligence-layer/src/shared/token-math.ts +427 -0
- package/figma-intelligence-layer/src/shared/token-naming.ts +468 -0
- package/figma-intelligence-layer/src/shared/token-utils.ts +420 -0
- package/figma-intelligence-layer/src/shared/types.ts +346 -0
- package/figma-intelligence-layer/src/shared/typography-presets.ts +94 -0
- package/figma-intelligence-layer/src/shared/unsplash.ts +165 -0
- package/figma-intelligence-layer/src/shared/vision-client.ts +607 -0
- package/figma-intelligence-layer/src/shared/vision-provider-anthropic.ts +334 -0
- package/figma-intelligence-layer/src/shared/vision-provider-openai.ts +446 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-handler.ts +782 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotate-renderer.ts +496 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/a11y-annotation-kit.ts +230 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/colorblind-sim.ts +66 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/index.ts +810 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-analyzer.ts +1191 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-figma-page.ts +1346 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/keyboard-sr-order-handler.ts +148 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-figma-page.ts +499 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/vpat-report.ts +910 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-checker.ts +989 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/a11y-audit/wcag-criteria.ts +1160 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/design-from-ref/index.ts +424 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/component-recognizer.ts +38 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/ds-matcher.ts +111 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/font-matcher.ts +114 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/icon-resolver.ts +103 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/index.ts +1060 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/layout-segmenter.ts +18 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/token-inferencer.ts +39 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/screen-cloner/vision-pipeline.ts +58 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/sketch-to-design/index.ts +298 -0
- package/figma-intelligence-layer/src/tools/phase1-vision/visual-audit/index.ts +197 -0
- package/figma-intelligence-layer/src/tools/phase2-accuracy/component-audit/index.ts +494 -0
- package/figma-intelligence-layer/src/tools/phase2-accuracy/intent-translator/index.ts +356 -0
- package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/container-patterns.ts +123 -0
- package/figma-intelligence-layer/src/tools/phase2-accuracy/layout-intelligence/index.ts +663 -0
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/built-in-rules.yaml +56 -0
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/index.ts +614 -0
- package/figma-intelligence-layer/src/tools/phase2-accuracy/lint-rules/rule-engine.ts +113 -0
- package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/color-theory.ts +178 -0
- package/figma-intelligence-layer/src/tools/phase2-accuracy/theme-generator/index.ts +470 -0
- package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/index.ts +429 -0
- package/figma-intelligence-layer/src/tools/phase2-accuracy/variant-expander/token-override-maps.ts +226 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/ai-image-insert/index.ts +535 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/index.ts +660 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/component-archaeologist/pattern-fingerprints.ts +209 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/composition-builder/index.ts +540 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/figma-animated-build.ts +391 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/index.ts +2019 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/page-architect/screen-templates.ts +131 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/prototype-map/index.ts +381 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/prototype-wire/index.ts +565 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/swarm-build/index.ts +764 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/system-drift/index.ts +535 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/unsplash-search/index.ts +84 -0
- package/figma-intelligence-layer/src/tools/phase3-generation/url-to-frame/index.ts +401 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/css-animations.ts +68 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/framer-motion.ts +78 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/code-generators/swift-animations.ts +93 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/animation-specifier/index.ts +596 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/ci-check/index.ts +462 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/export-tokens/index.ts +1470 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/generate-component-code/index.ts +829 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/handoff-spec/index.ts +702 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/icon-library-sync/index.ts +483 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/index.ts +501 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/sync-from-code/storybook-parser.ts +106 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/watch-docs/index.ts +676 -0
- package/figma-intelligence-layer/src/tools/phase4-sync/webhook-listener/index.ts +560 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/apg-doc/index.ts +1043 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/component-detection.ts +620 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/anatomy.ts +331 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/color-tokens.ts +77 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/properties.ts +54 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/snapshot.ts +287 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/spacing.ts +71 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/states.ts +43 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/extractors/typography.ts +71 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/index.ts +221 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/_default.ts +166 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/accordion.ts +232 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/alert.ts +234 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar-group.ts +270 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/avatar.ts +249 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/badge.ts +231 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/banner.ts +293 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/breadcrumb.ts +240 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/button.ts +243 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/calendar.ts +307 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/card.ts +143 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/checkbox.ts +227 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/chip.ts +233 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/combobox.ts +282 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/datepicker.ts +276 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/divider.ts +223 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/drawer.ts +255 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/dropdown-menu.ts +289 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/empty-state.ts +261 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/file-uploader.ts +290 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/form.ts +265 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/grid.ts +238 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/icon.ts +255 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/index.ts +128 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-edit.ts +286 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/inline-message.ts +255 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/input.ts +330 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/link.ts +247 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/list.ts +250 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/menu.ts +247 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/modal.ts +144 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navbar.ts +264 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/navigation.ts +251 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/number-input.ts +261 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/pagination.ts +248 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/popover.ts +270 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/progress.ts +251 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/radio.ts +142 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/range-slider.ts +282 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/rating.ts +250 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/search.ts +258 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/segmented-control.ts +265 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/select.ts +319 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/skeleton.ts +256 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/slider.ts +232 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/spinner.ts +239 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/status-dot.ts +252 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/stepper.ts +270 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/table.ts +244 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tabs.ts +143 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tag.ts +243 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/textarea.ts +259 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/time-picker.ts +293 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toast.ts +144 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toggle.ts +289 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/toolbar.ts +267 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/tooltip.ts +232 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/treeview.ts +257 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/knowledge/typography.ts +319 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/legacy-compat.ts +121 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/anatomy-diagram.ts +430 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/figma-page.ts +312 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/json.ts +129 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/markdown.ts +78 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/renderers/visual-doc.ts +2333 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/accessibility.ts +100 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/anatomy.ts +32 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/color-tokens.ts +59 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/content-guidance.ts +18 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/design-tokens.ts +53 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/interaction-rules.ts +19 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/overview.ts +91 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/properties-api.ts +71 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/qa-criteria.ts +19 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/related-components.ts +110 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/responsive.ts +19 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/size-specs.ts +67 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/spacing-structure.ts +58 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/state-specs.ts +79 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/states.ts +50 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/type-hierarchy.ts +33 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/typography.ts +55 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/usage-guidelines.ts +73 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/sections/variants.ts +81 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec/types.ts +409 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/index.ts +198 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/renderer.ts +701 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/component-spec-sheet/types.ts +88 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/decision-log/index.ts +135 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/design-decision-log/index.ts +491 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-primitives/index.ts +416 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-scaffolder/index.ts +722 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/ds-variables/index.ts +449 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/health-report/index.ts +393 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/index.ts +406 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/figma-page.ts +292 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/json.ts +24 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/markdown.ts +172 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/taxonomy-docs/renderers/naming-guide.ts +409 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/token-analytics/index.ts +594 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/token-docs/index.ts +710 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/token-migrate/index.ts +458 -0
- package/figma-intelligence-layer/src/tools/phase5-governance/token-naming/index.ts +134 -0
- package/figma-intelligence-layer/tests/apg-doc.test.ts +101 -0
- package/figma-intelligence-layer/tests/design-system-context.test.ts +152 -0
- package/figma-intelligence-layer/tests/design-system-matcher.test.ts +144 -0
- package/figma-intelligence-layer/tests/figma-bridge.test.ts +83 -0
- package/figma-intelligence-layer/tests/generate-image-and-insert.test.ts +56 -0
- package/figma-intelligence-layer/tests/screen-cloner-regression.test.ts +69 -0
- package/figma-intelligence-layer/tests/smoke.test.ts +174 -0
- package/figma-intelligence-layer/tests/spec-generator.test.ts +127 -0
- package/figma-intelligence-layer/tests/token-migrate.test.ts +21 -0
- package/figma-intelligence-layer/tests/token-naming.test.ts +30 -0
- package/figma-intelligence-layer/tsconfig.json +19 -0
- package/package.json +35 -0
- package/scripts/clean-existing-chunks.js +179 -0
- package/scripts/connect-ai-tool.js +490 -0
- package/scripts/convert-hub-pdfs.js +425 -0
- package/scripts/figma-mcp-status.js +349 -0
- package/scripts/register-codex-mcp.js +96 -0
|
@@ -0,0 +1,1528 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Figma Intelligence Bridge Plugin — Plugin Sandbox (code.js)
|
|
3
|
+
*
|
|
4
|
+
* This runs inside Figma's plugin sandbox. It cannot do network I/O directly.
|
|
5
|
+
* It communicates with the UI iframe (ui.html) via figma.ui.postMessage / onmessage.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* MCP Server → WebSocket → ui.html → postMessage → this code → Figma Plugin API
|
|
9
|
+
* result ← WebSocket ← ui.html ← postMessage ← this code
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
var UI_WIDTH = 320;
|
|
13
|
+
var UI_HEIGHT = 480;
|
|
14
|
+
var UI_MIN_HEIGHT = 480;
|
|
15
|
+
var UI_MAX_HEIGHT = 960;
|
|
16
|
+
|
|
17
|
+
figma.showUI(__html__, { visible: true, width: UI_WIDTH, height: UI_HEIGHT });
|
|
18
|
+
|
|
19
|
+
function normalizeExecuteCode(code) {
|
|
20
|
+
var trimmed = typeof code === "string" ? code.trim() : "";
|
|
21
|
+
if (!trimmed) return "";
|
|
22
|
+
|
|
23
|
+
// Many callers already pass an async IIFE. Preserve compatibility by
|
|
24
|
+
// returning its resolved value instead of letting the outer wrapper finish
|
|
25
|
+
// early with `undefined`.
|
|
26
|
+
if (/^\(async\s*\(\)\s*=>\s*\{[\s\S]*\}\)\(\);?$/.test(trimmed)) {
|
|
27
|
+
return "return await " + trimmed;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return code;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function getSelectionSummary() {
|
|
34
|
+
return figma.currentPage.selection.map(function(node) {
|
|
35
|
+
return { id: node.id, name: node.name, type: node.type };
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function getCurrentPageSummary() {
|
|
40
|
+
return {
|
|
41
|
+
id: figma.currentPage.id,
|
|
42
|
+
name: figma.currentPage.name,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function summarizeDocumentChange(change) {
|
|
47
|
+
var summary = { type: change.type || "UNKNOWN" };
|
|
48
|
+
|
|
49
|
+
if (change.id) summary.id = change.id;
|
|
50
|
+
if (change.node && change.node.id) {
|
|
51
|
+
summary.nodeId = change.node.id;
|
|
52
|
+
summary.nodeType = change.node.type;
|
|
53
|
+
} else if (change.nodeId) {
|
|
54
|
+
summary.nodeId = change.nodeId;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return summary;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function postBridgeEvent(eventType, payload) {
|
|
61
|
+
figma.ui.postMessage({
|
|
62
|
+
type: "bridge-event",
|
|
63
|
+
eventType: eventType,
|
|
64
|
+
payload: payload,
|
|
65
|
+
timestamp: Date.now(),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function emitReadyEvent() {
|
|
70
|
+
postBridgeEvent("bridge.ready", {
|
|
71
|
+
fileName: figma.root.name,
|
|
72
|
+
currentPage: getCurrentPageSummary(),
|
|
73
|
+
pageCount: figma.root.children.length,
|
|
74
|
+
selection: getSelectionSummary(),
|
|
75
|
+
capabilities: getBridgeCapabilities(),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getBridgeCapabilities() {
|
|
80
|
+
return {
|
|
81
|
+
editorType: figma.editorType,
|
|
82
|
+
mode: figma.mode || null,
|
|
83
|
+
variablesApi: !!figma.variables,
|
|
84
|
+
localVariablesApi: !!(figma.variables && figma.variables.getLocalVariableCollectionsAsync),
|
|
85
|
+
localPaintStylesApi: !!figma.getLocalPaintStylesAsync,
|
|
86
|
+
localTextStylesApi: !!figma.getLocalTextStylesAsync,
|
|
87
|
+
localEffectStylesApi: !!figma.getLocalEffectStylesAsync,
|
|
88
|
+
fileName: figma.root.name,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function emitSelectionChange() {
|
|
93
|
+
postBridgeEvent("selectionchange", {
|
|
94
|
+
selection: getSelectionSummary(),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function emitCurrentPageChange() {
|
|
99
|
+
postBridgeEvent("currentpagechange", {
|
|
100
|
+
currentPage: getCurrentPageSummary(),
|
|
101
|
+
selection: getSelectionSummary(),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
var documentChangeFlushTimer = null;
|
|
106
|
+
var pendingDocumentChanges = [];
|
|
107
|
+
|
|
108
|
+
function flushDocumentChanges() {
|
|
109
|
+
if (!pendingDocumentChanges.length) return;
|
|
110
|
+
var batch = pendingDocumentChanges.splice(0, 50).map(summarizeDocumentChange);
|
|
111
|
+
postBridgeEvent("documentchange", {
|
|
112
|
+
documentChanges: batch,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function scheduleDocumentChangeFlush() {
|
|
117
|
+
if (documentChangeFlushTimer) return;
|
|
118
|
+
// Coalesce noisy document changes so the UI bridge socket stays stable.
|
|
119
|
+
documentChangeFlushTimer = setTimeout(function() {
|
|
120
|
+
documentChangeFlushTimer = null;
|
|
121
|
+
flushDocumentChanges();
|
|
122
|
+
}, 120);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function emitDocumentChange(event) {
|
|
126
|
+
var changes = Array.isArray(event && event.documentChanges) ? event.documentChanges : [];
|
|
127
|
+
if (!changes.length) return;
|
|
128
|
+
pendingDocumentChanges = pendingDocumentChanges.concat(changes);
|
|
129
|
+
if (pendingDocumentChanges.length > 200) {
|
|
130
|
+
pendingDocumentChanges = pendingDocumentChanges.slice(-200);
|
|
131
|
+
}
|
|
132
|
+
scheduleDocumentChangeFlush();
|
|
133
|
+
|
|
134
|
+
// Detect variable/token changes for taxonomy docs auto-sync
|
|
135
|
+
var hasVariableChange = changes.some(function(c) {
|
|
136
|
+
var t = (c.type || "").toUpperCase();
|
|
137
|
+
return t === "VARIABLE_CHANGE" || t === "VARIABLE_COLLECTION_CHANGE"
|
|
138
|
+
|| t === "CREATE" || t === "PROPERTY_CHANGE" || t === "DELETE";
|
|
139
|
+
});
|
|
140
|
+
if (hasVariableChange) {
|
|
141
|
+
postBridgeEvent("variable-change", { timestamp: Date.now() });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
figma.on("selectionchange", emitSelectionChange);
|
|
146
|
+
figma.on("currentpagechange", emitCurrentPageChange);
|
|
147
|
+
|
|
148
|
+
async function initializeBridgeEvents() {
|
|
149
|
+
try {
|
|
150
|
+
await figma.loadAllPagesAsync();
|
|
151
|
+
figma.on("documentchange", emitDocumentChange);
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.warn("Failed to enable documentchange events:", error);
|
|
154
|
+
} finally {
|
|
155
|
+
emitReadyEvent();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
initializeBridgeEvents();
|
|
160
|
+
|
|
161
|
+
// ─── Operation Cursor System ──────────────────────────────────────────────────
|
|
162
|
+
// Single honest cursor that tracks real operations. Moves to the actual target
|
|
163
|
+
// node, shows real operation names, and stays invisible when idle.
|
|
164
|
+
|
|
165
|
+
// Pre-load font for cursor label
|
|
166
|
+
figma.loadFontAsync({ family: "Inter", style: "Bold" }).catch(function() {
|
|
167
|
+
figma.loadFontAsync({ family: "Inter", style: "Semi Bold" }).catch(function() {
|
|
168
|
+
figma.loadFontAsync({ family: "Inter", style: "Regular" }).catch(function() {});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// ── Cursor Configuration ──────────────────────────────────────────────────────
|
|
173
|
+
var CURSOR_NAME = "MCP Power";
|
|
174
|
+
var CURSOR_COLOR = { r: 0.43, g: 0.37, b: 0.85 }; // #6E5FD8
|
|
175
|
+
var CURSOR_TEXT_COLOR = { r: 1, g: 1, b: 1 };
|
|
176
|
+
var CURSOR_ANIM_DURATION = 250; // ms — perceptible but snappy
|
|
177
|
+
var SESSION_IDLE_TIMEOUT = 8000;
|
|
178
|
+
|
|
179
|
+
// ── Methods that skip cursor entirely (they manage their own frames) ──
|
|
180
|
+
var THEATRE_SKIP_METHODS = { execute: 1 };
|
|
181
|
+
|
|
182
|
+
// ── Shared State ──────────────────────────────────────────────────────────────
|
|
183
|
+
var opCursor = null; // { pointer, label, text, x, y, fontStyle }
|
|
184
|
+
var operationCount = 0;
|
|
185
|
+
var cursorCleanupTimer = null;
|
|
186
|
+
var cursorActive = false;
|
|
187
|
+
|
|
188
|
+
// ── Operation Classification ──────────────────────────────────────────────────
|
|
189
|
+
var READ_OPERATIONS = {
|
|
190
|
+
getNode:1, getSelection:1, getStatus:1, getPages:1,
|
|
191
|
+
getStyles:1, getVariables:1, getComponentSets:1,
|
|
192
|
+
searchComponents:1, screenshot:1, getCapabilities:1, ping:1,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
// ── Human-readable operation labels (shown on cursor) ─────────────────────────
|
|
196
|
+
var METHOD_LABELS = {
|
|
197
|
+
createChild: "Creating", createPage: "Creating page",
|
|
198
|
+
moveNode: "Positioning", resizeNode: "Resizing",
|
|
199
|
+
cloneNode: "Duplicating", deleteNode: "Removing",
|
|
200
|
+
setFills: "Styling", setStrokes: "Styling",
|
|
201
|
+
createVariable: "Creating token", updateVariable: "Updating token",
|
|
202
|
+
createVariableCollection: "New collection",
|
|
203
|
+
batchCreateVariables: "Creating tokens", batchUpdateVariables: "Updating tokens",
|
|
204
|
+
deleteVariable: "Removing token",
|
|
205
|
+
setText: "Writing text", renameNode: "Naming",
|
|
206
|
+
setDescription: "Documenting",
|
|
207
|
+
instantiateComponent: "Placing component",
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// ── Utility ───────────────────────────────────────────────────────────────────
|
|
211
|
+
function theatreDelay(ms) {
|
|
212
|
+
return new Promise(function(resolve) { setTimeout(resolve, ms); });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// ── Absolute Position Helper ───────────────────────────────────────────────────
|
|
216
|
+
function getAbsolutePosition(node) {
|
|
217
|
+
try {
|
|
218
|
+
if (node.absoluteTransform) {
|
|
219
|
+
return {
|
|
220
|
+
x: node.absoluteTransform[0][2],
|
|
221
|
+
y: node.absoluteTransform[1][2],
|
|
222
|
+
width: node.width || 0,
|
|
223
|
+
height: node.height || 0,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {}
|
|
227
|
+
return { x: node.x || 0, y: node.y || 0, width: node.width || 0, height: node.height || 0 };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ── Get Real Target Position ──────────────────────────────────────────────────
|
|
231
|
+
async function getTargetPosition(method, params) {
|
|
232
|
+
try {
|
|
233
|
+
var targetNode = null;
|
|
234
|
+
if (params.nodeId) targetNode = await figma.getNodeByIdAsync(params.nodeId);
|
|
235
|
+
if (!targetNode && params.parentId) targetNode = await figma.getNodeByIdAsync(params.parentId);
|
|
236
|
+
|
|
237
|
+
if (targetNode && targetNode.absoluteTransform) {
|
|
238
|
+
var abs = getAbsolutePosition(targetNode);
|
|
239
|
+
// Position cursor just outside the top-left of the target node
|
|
240
|
+
return { x: abs.x - 20, y: abs.y - 6 };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Fall back to explicit coordinates if provided
|
|
244
|
+
if (typeof params.x === "number" && typeof params.y === "number") {
|
|
245
|
+
return { x: params.x - 20, y: params.y - 6 };
|
|
246
|
+
}
|
|
247
|
+
} catch (e) {}
|
|
248
|
+
|
|
249
|
+
// Last resort: viewport center
|
|
250
|
+
try {
|
|
251
|
+
var vc = figma.viewport.center;
|
|
252
|
+
return { x: vc.x, y: vc.y };
|
|
253
|
+
} catch (e) {
|
|
254
|
+
return { x: 400, y: 300 };
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ── Build Operation Label ─────────────────────────────────────────────────────
|
|
259
|
+
function buildOperationLabel(method, params) {
|
|
260
|
+
var prefix = METHOD_LABELS[method] || method;
|
|
261
|
+
// Append the element name when available for context
|
|
262
|
+
var name = params && params.name;
|
|
263
|
+
if (!name && params && params.characters) {
|
|
264
|
+
name = params.characters.length > 20 ? params.characters.substring(0, 20) + "…" : params.characters;
|
|
265
|
+
}
|
|
266
|
+
return name ? prefix + ": " + name : prefix;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ── Cursor Creation ─────────────────────────────────────────────────────────
|
|
270
|
+
async function createCursorNode(x, y) {
|
|
271
|
+
var POINTER_W = 14;
|
|
272
|
+
var POINTER_H = 21;
|
|
273
|
+
|
|
274
|
+
var pointer;
|
|
275
|
+
try {
|
|
276
|
+
pointer = figma.createVector();
|
|
277
|
+
pointer.vectorPaths = [{
|
|
278
|
+
windingRule: "NONZERO",
|
|
279
|
+
data: "M 0 0 L 0 18 L 5 13 L 9 21 L 12 19.5 L 8 11 L 14 11 Z"
|
|
280
|
+
}];
|
|
281
|
+
pointer.fills = [{ type: "SOLID", color: { r: 1, g: 1, b: 1 } }];
|
|
282
|
+
pointer.strokes = [{ type: "SOLID", color: CURSOR_COLOR }];
|
|
283
|
+
pointer.strokeWeight = 1.5;
|
|
284
|
+
pointer.resize(POINTER_W, POINTER_H);
|
|
285
|
+
} catch (e) {
|
|
286
|
+
pointer = figma.createRectangle();
|
|
287
|
+
pointer.resize(10, 14);
|
|
288
|
+
pointer.cornerRadius = 1;
|
|
289
|
+
pointer.fills = [{ type: "SOLID", color: { r: 1, g: 1, b: 1 } }];
|
|
290
|
+
pointer.strokes = [{ type: "SOLID", color: CURSOR_COLOR }];
|
|
291
|
+
pointer.strokeWeight = 1;
|
|
292
|
+
}
|
|
293
|
+
pointer.name = "__agent_ptr_op";
|
|
294
|
+
pointer.locked = true;
|
|
295
|
+
pointer.x = x;
|
|
296
|
+
pointer.y = y;
|
|
297
|
+
figma.currentPage.appendChild(pointer);
|
|
298
|
+
|
|
299
|
+
var label = figma.createFrame();
|
|
300
|
+
label.name = "__agent_label_op";
|
|
301
|
+
label.locked = true;
|
|
302
|
+
label.fills = [{ type: "SOLID", color: CURSOR_COLOR }];
|
|
303
|
+
label.cornerRadius = 4;
|
|
304
|
+
label.layoutMode = "HORIZONTAL";
|
|
305
|
+
label.primaryAxisSizingMode = "AUTO";
|
|
306
|
+
label.counterAxisSizingMode = "AUTO";
|
|
307
|
+
label.paddingLeft = 6;
|
|
308
|
+
label.paddingRight = 6;
|
|
309
|
+
label.paddingTop = 2;
|
|
310
|
+
label.paddingBottom = 2;
|
|
311
|
+
label.effects = [{
|
|
312
|
+
type: "DROP_SHADOW",
|
|
313
|
+
color: { r: 0, g: 0, b: 0, a: 0.3 },
|
|
314
|
+
offset: { x: 0, y: 2 },
|
|
315
|
+
radius: 4,
|
|
316
|
+
visible: true,
|
|
317
|
+
blendMode: "NORMAL",
|
|
318
|
+
spread: 0,
|
|
319
|
+
}];
|
|
320
|
+
label.x = x + POINTER_W + 2;
|
|
321
|
+
label.y = y + POINTER_H - 4;
|
|
322
|
+
figma.currentPage.appendChild(label);
|
|
323
|
+
|
|
324
|
+
var fontStyle = "Regular";
|
|
325
|
+
var textNode = null;
|
|
326
|
+
try {
|
|
327
|
+
try {
|
|
328
|
+
await figma.loadFontAsync({ family: "Inter", style: "Bold" });
|
|
329
|
+
fontStyle = "Bold";
|
|
330
|
+
} catch (e) {
|
|
331
|
+
try {
|
|
332
|
+
await figma.loadFontAsync({ family: "Inter", style: "Semi Bold" });
|
|
333
|
+
fontStyle = "Semi Bold";
|
|
334
|
+
} catch (e2) {
|
|
335
|
+
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
textNode = figma.createText();
|
|
339
|
+
textNode.fontName = { family: "Inter", style: fontStyle };
|
|
340
|
+
textNode.characters = CURSOR_NAME;
|
|
341
|
+
textNode.fontSize = 10;
|
|
342
|
+
textNode.fills = [{ type: "SOLID", color: CURSOR_TEXT_COLOR }];
|
|
343
|
+
textNode.locked = true;
|
|
344
|
+
label.appendChild(textNode);
|
|
345
|
+
} catch (fontErr) {
|
|
346
|
+
label.resize(50, 18);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
opCursor = {
|
|
350
|
+
pointer: pointer, label: label, text: textNode,
|
|
351
|
+
x: x, y: y, fontStyle: fontStyle,
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ── Cursor Label Update ────────────────────────────────────────────────────────
|
|
356
|
+
function updateCursorLabel(text) {
|
|
357
|
+
if (!opCursor || !opCursor.text) return;
|
|
358
|
+
try { opCursor.text.characters = text; } catch (e) {}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// ── Cursor Movement (instant) ──────────────────────────────────────────────────
|
|
362
|
+
function moveCursorTo(x, y) {
|
|
363
|
+
if (!opCursor) return;
|
|
364
|
+
try {
|
|
365
|
+
opCursor.pointer.x = x;
|
|
366
|
+
opCursor.pointer.y = y;
|
|
367
|
+
opCursor.label.x = x + 16;
|
|
368
|
+
opCursor.label.y = y + 17;
|
|
369
|
+
opCursor.x = x;
|
|
370
|
+
opCursor.y = y;
|
|
371
|
+
} catch (e) {}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ── Animated Cursor Movement ───────────────────────────────────────────────────
|
|
375
|
+
async function animateCursorTo(targetX, targetY, durationMs) {
|
|
376
|
+
if (!opCursor) return;
|
|
377
|
+
|
|
378
|
+
var dur = (typeof durationMs === "number") ? durationMs : CURSOR_ANIM_DURATION;
|
|
379
|
+
var startX = opCursor.x;
|
|
380
|
+
var startY = opCursor.y;
|
|
381
|
+
var dx = targetX - startX;
|
|
382
|
+
var dy = targetY - startY;
|
|
383
|
+
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
384
|
+
|
|
385
|
+
if (dist < 15) {
|
|
386
|
+
moveCursorTo(targetX, targetY);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
var steps = Math.max(6, Math.min(18, Math.floor(dist / 18)));
|
|
391
|
+
var stepDelay = Math.floor(dur / steps);
|
|
392
|
+
|
|
393
|
+
for (var i = 1; i <= steps; i++) {
|
|
394
|
+
var t = i / steps;
|
|
395
|
+
var ease = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
396
|
+
moveCursorTo(startX + dx * ease, startY + dy * ease);
|
|
397
|
+
if (i < steps) await theatreDelay(stepDelay);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ── Fade Out Cursor ───────────────────────────────────────────────────────────
|
|
402
|
+
async function fadeOutCursor(durationMs) {
|
|
403
|
+
if (!opCursor) return;
|
|
404
|
+
var stepDelay = Math.floor((durationMs || 400) / 5);
|
|
405
|
+
for (var step = 4; step >= 0; step--) {
|
|
406
|
+
var opacity = step / 4;
|
|
407
|
+
try { opCursor.pointer.opacity = opacity; } catch (e) {}
|
|
408
|
+
try { opCursor.label.opacity = opacity; } catch (e) {}
|
|
409
|
+
if (step > 0) await theatreDelay(stepDelay);
|
|
410
|
+
}
|
|
411
|
+
removeCursorNodes();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// ── Ensure Cursor Exists ──────────────────────────────────────────────────────
|
|
415
|
+
async function ensureCursor(x, y) {
|
|
416
|
+
if (opCursor) return;
|
|
417
|
+
cleanupOrphanedCursors();
|
|
418
|
+
await createCursorNode(x, y);
|
|
419
|
+
cursorActive = true;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ── Cleanup ────────────────────────────────────────────────────────────────────
|
|
423
|
+
function removeCursorNodes() {
|
|
424
|
+
if (!opCursor) return;
|
|
425
|
+
try { opCursor.pointer.remove(); } catch (e) {}
|
|
426
|
+
try { opCursor.label.remove(); } catch (e) {}
|
|
427
|
+
opCursor = null;
|
|
428
|
+
cursorActive = false;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function cleanupOrphanedCursors() {
|
|
432
|
+
try {
|
|
433
|
+
var orphans = figma.currentPage.findAll(function(n) {
|
|
434
|
+
return n.name && (
|
|
435
|
+
n.name.indexOf("__agent_ptr_") === 0 ||
|
|
436
|
+
n.name.indexOf("__agent_label_") === 0 ||
|
|
437
|
+
n.name.indexOf("__agent_cursor_") === 0 ||
|
|
438
|
+
n.name.indexOf("__cursor_") === 0
|
|
439
|
+
);
|
|
440
|
+
});
|
|
441
|
+
for (var i = 0; i < orphans.length; i++) {
|
|
442
|
+
try { orphans[i].remove(); } catch (e) {}
|
|
443
|
+
}
|
|
444
|
+
} catch (e) {}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ── Bring Cursor to Front ─────────────────────────────────────────────────────
|
|
448
|
+
function bringCursorToFront() {
|
|
449
|
+
if (!opCursor) return;
|
|
450
|
+
try {
|
|
451
|
+
if (opCursor.pointer && opCursor.pointer.parent === figma.currentPage) {
|
|
452
|
+
figma.currentPage.appendChild(opCursor.pointer);
|
|
453
|
+
}
|
|
454
|
+
if (opCursor.label && opCursor.label.parent === figma.currentPage) {
|
|
455
|
+
figma.currentPage.appendChild(opCursor.label);
|
|
456
|
+
}
|
|
457
|
+
} catch (e) {}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// ── Idle Cleanup Timer ────────────────────────────────────────────────────────
|
|
461
|
+
function resetCursorCleanupTimer() {
|
|
462
|
+
if (cursorCleanupTimer) clearTimeout(cursorCleanupTimer);
|
|
463
|
+
cursorCleanupTimer = setTimeout(function() {
|
|
464
|
+
if (opCursor) {
|
|
465
|
+
(async function() { await fadeOutCursor(400); })();
|
|
466
|
+
figma.ui.postMessage({ type: "agent-session-end", timestamp: Date.now() });
|
|
467
|
+
}
|
|
468
|
+
operationCount = 0;
|
|
469
|
+
}, SESSION_IDLE_TIMEOUT);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// ── Main Operation Cursor ─────────────────────────────────────────────────────
|
|
473
|
+
async function activateAgentForOperation(method, params) {
|
|
474
|
+
if (READ_OPERATIONS[method]) return;
|
|
475
|
+
|
|
476
|
+
operationCount++;
|
|
477
|
+
var pos = await getTargetPosition(method, params || {});
|
|
478
|
+
await ensureCursor(pos.x, pos.y);
|
|
479
|
+
|
|
480
|
+
// Build label from real operation + element name
|
|
481
|
+
var label = buildOperationLabel(method, params || {});
|
|
482
|
+
updateCursorLabel(label);
|
|
483
|
+
|
|
484
|
+
// Animate to actual target position
|
|
485
|
+
await animateCursorTo(pos.x, pos.y);
|
|
486
|
+
bringCursorToFront();
|
|
487
|
+
|
|
488
|
+
// Report real activity to UI
|
|
489
|
+
figma.ui.postMessage({
|
|
490
|
+
type: "agent-activity",
|
|
491
|
+
agent: CURSOR_NAME,
|
|
492
|
+
method: method,
|
|
493
|
+
status: label,
|
|
494
|
+
phase: "WORKING",
|
|
495
|
+
timestamp: Date.now(),
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
resetCursorCleanupTimer();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ── Post-Operation Effect ──────────────────────────────────────────────────────
|
|
502
|
+
async function postOperationEffect(method, params, result) {
|
|
503
|
+
if (READ_OPERATIONS[method]) return;
|
|
504
|
+
if (!opCursor) return;
|
|
505
|
+
|
|
506
|
+
// Move cursor to the newly created node's actual position
|
|
507
|
+
if (result && result.id) {
|
|
508
|
+
try {
|
|
509
|
+
var newNode = await figma.getNodeByIdAsync(result.id);
|
|
510
|
+
if (newNode && newNode.absoluteTransform) {
|
|
511
|
+
var abs = getAbsolutePosition(newNode);
|
|
512
|
+
await animateCursorTo(abs.x - 20, abs.y - 6, 150);
|
|
513
|
+
}
|
|
514
|
+
} catch (e) {}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
resetCursorCleanupTimer();
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ─── End Operation Cursor System ──────────────────────────────────────────────
|
|
521
|
+
|
|
522
|
+
// ─── Multi-Agent Cursor System (Swarm Mode) ─────────────────────────────────
|
|
523
|
+
// Multiple named agent cursors with unique colors for parallel visual theatre.
|
|
524
|
+
// Each agent gets its own pointer + label that can move independently.
|
|
525
|
+
|
|
526
|
+
var AGENT_COLORS = {
|
|
527
|
+
Layouter: { r: 0.26, g: 0.52, b: 0.96 }, // #4285F4 blue
|
|
528
|
+
Styler: { r: 0.60, g: 0.33, b: 0.86 }, // #9955DB purple
|
|
529
|
+
Copywriter: { r: 0.13, g: 0.72, b: 0.45 }, // #22B873 green
|
|
530
|
+
Matcher: { r: 0.96, g: 0.50, b: 0.14 }, // #F58024 orange
|
|
531
|
+
Reviewer: { r: 0.90, g: 0.22, b: 0.35 }, // #E63959 red
|
|
532
|
+
Builder: { r: 0.18, g: 0.73, b: 0.82 }, // #2EBAD1 teal
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
var agentCursors = {}; // { agentId: { pointer, label, text, x, y } }
|
|
536
|
+
|
|
537
|
+
async function createAgentCursorNode(agentId, x, y) {
|
|
538
|
+
var color = AGENT_COLORS[agentId] || AGENT_COLORS.Builder;
|
|
539
|
+
|
|
540
|
+
var pointer;
|
|
541
|
+
try {
|
|
542
|
+
pointer = figma.createVector();
|
|
543
|
+
pointer.vectorPaths = [{
|
|
544
|
+
windingRule: "NONZERO",
|
|
545
|
+
data: "M 0 0 L 0 18 L 5 13 L 9 21 L 12 19.5 L 8 11 L 14 11 Z"
|
|
546
|
+
}];
|
|
547
|
+
pointer.fills = [{ type: "SOLID", color: { r: 1, g: 1, b: 1 } }];
|
|
548
|
+
pointer.strokes = [{ type: "SOLID", color: color }];
|
|
549
|
+
pointer.strokeWeight = 1.5;
|
|
550
|
+
pointer.resize(14, 21);
|
|
551
|
+
} catch (e) {
|
|
552
|
+
pointer = figma.createRectangle();
|
|
553
|
+
pointer.resize(10, 14);
|
|
554
|
+
pointer.cornerRadius = 1;
|
|
555
|
+
pointer.fills = [{ type: "SOLID", color: { r: 1, g: 1, b: 1 } }];
|
|
556
|
+
pointer.strokes = [{ type: "SOLID", color: color }];
|
|
557
|
+
pointer.strokeWeight = 1;
|
|
558
|
+
}
|
|
559
|
+
pointer.name = "__agent_ptr_" + agentId;
|
|
560
|
+
pointer.locked = true;
|
|
561
|
+
pointer.x = x;
|
|
562
|
+
pointer.y = y;
|
|
563
|
+
figma.currentPage.appendChild(pointer);
|
|
564
|
+
|
|
565
|
+
var label = figma.createFrame();
|
|
566
|
+
label.name = "__agent_label_" + agentId;
|
|
567
|
+
label.locked = true;
|
|
568
|
+
label.fills = [{ type: "SOLID", color: color }];
|
|
569
|
+
label.cornerRadius = 4;
|
|
570
|
+
label.layoutMode = "HORIZONTAL";
|
|
571
|
+
label.primaryAxisSizingMode = "AUTO";
|
|
572
|
+
label.counterAxisSizingMode = "AUTO";
|
|
573
|
+
label.paddingLeft = 6;
|
|
574
|
+
label.paddingRight = 6;
|
|
575
|
+
label.paddingTop = 2;
|
|
576
|
+
label.paddingBottom = 2;
|
|
577
|
+
label.effects = [{
|
|
578
|
+
type: "DROP_SHADOW",
|
|
579
|
+
color: { r: 0, g: 0, b: 0, a: 0.3 },
|
|
580
|
+
offset: { x: 0, y: 2 },
|
|
581
|
+
radius: 4,
|
|
582
|
+
visible: true,
|
|
583
|
+
blendMode: "NORMAL",
|
|
584
|
+
spread: 0,
|
|
585
|
+
}];
|
|
586
|
+
label.x = x + 16;
|
|
587
|
+
label.y = y + 17;
|
|
588
|
+
figma.currentPage.appendChild(label);
|
|
589
|
+
|
|
590
|
+
var textNode = null;
|
|
591
|
+
try {
|
|
592
|
+
await figma.loadFontAsync({ family: "Inter", style: "Bold" });
|
|
593
|
+
textNode = figma.createText();
|
|
594
|
+
textNode.fontName = { family: "Inter", style: "Bold" };
|
|
595
|
+
textNode.characters = agentId;
|
|
596
|
+
textNode.fontSize = 10;
|
|
597
|
+
textNode.fills = [{ type: "SOLID", color: { r: 1, g: 1, b: 1 } }];
|
|
598
|
+
textNode.locked = true;
|
|
599
|
+
label.appendChild(textNode);
|
|
600
|
+
} catch (e) {
|
|
601
|
+
try {
|
|
602
|
+
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
|
|
603
|
+
textNode = figma.createText();
|
|
604
|
+
textNode.fontName = { family: "Inter", style: "Regular" };
|
|
605
|
+
textNode.characters = agentId;
|
|
606
|
+
textNode.fontSize = 10;
|
|
607
|
+
textNode.fills = [{ type: "SOLID", color: { r: 1, g: 1, b: 1 } }];
|
|
608
|
+
textNode.locked = true;
|
|
609
|
+
label.appendChild(textNode);
|
|
610
|
+
} catch (e2) {
|
|
611
|
+
label.resize(60, 18);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
agentCursors[agentId] = { pointer: pointer, label: label, text: textNode, x: x, y: y };
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function moveAgentCursorTo(agentId, x, y) {
|
|
619
|
+
var cursor = agentCursors[agentId];
|
|
620
|
+
if (!cursor) return;
|
|
621
|
+
try {
|
|
622
|
+
cursor.pointer.x = x;
|
|
623
|
+
cursor.pointer.y = y;
|
|
624
|
+
cursor.label.x = x + 16;
|
|
625
|
+
cursor.label.y = y + 17;
|
|
626
|
+
cursor.x = x;
|
|
627
|
+
cursor.y = y;
|
|
628
|
+
} catch (e) {}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
async function animateAgentCursorTo(agentId, targetX, targetY, durationMs) {
|
|
632
|
+
var cursor = agentCursors[agentId];
|
|
633
|
+
if (!cursor) return;
|
|
634
|
+
|
|
635
|
+
var dur = (typeof durationMs === "number") ? durationMs : 250;
|
|
636
|
+
var startX = cursor.x;
|
|
637
|
+
var startY = cursor.y;
|
|
638
|
+
var dx = targetX - startX;
|
|
639
|
+
var dy = targetY - startY;
|
|
640
|
+
var dist = Math.sqrt(dx * dx + dy * dy);
|
|
641
|
+
|
|
642
|
+
if (dist < 15) {
|
|
643
|
+
moveAgentCursorTo(agentId, targetX, targetY);
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
var steps = Math.max(6, Math.min(14, Math.floor(dist / 20)));
|
|
648
|
+
var stepDelay = Math.floor(dur / steps);
|
|
649
|
+
|
|
650
|
+
for (var i = 1; i <= steps; i++) {
|
|
651
|
+
var t = i / steps;
|
|
652
|
+
var ease = t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
653
|
+
moveAgentCursorTo(agentId, startX + dx * ease, startY + dy * ease);
|
|
654
|
+
if (i < steps) await theatreDelay(stepDelay);
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
function updateAgentCursorLabel(agentId, text) {
|
|
659
|
+
var cursor = agentCursors[agentId];
|
|
660
|
+
if (!cursor || !cursor.text) return;
|
|
661
|
+
try { cursor.text.characters = text; } catch (e) {}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function removeAgentCursorNode(agentId) {
|
|
665
|
+
var cursor = agentCursors[agentId];
|
|
666
|
+
if (!cursor) return;
|
|
667
|
+
try { cursor.pointer.remove(); } catch (e) {}
|
|
668
|
+
try { cursor.label.remove(); } catch (e) {}
|
|
669
|
+
delete agentCursors[agentId];
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
function removeAllAgentCursorNodes() {
|
|
673
|
+
var ids = Object.keys(agentCursors);
|
|
674
|
+
for (var i = 0; i < ids.length; i++) {
|
|
675
|
+
removeAgentCursorNode(ids[i]);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async function createAgentChatNote(agentId, message, x, y) {
|
|
680
|
+
var color = AGENT_COLORS[agentId] || AGENT_COLORS.Builder;
|
|
681
|
+
|
|
682
|
+
var note = figma.createFrame();
|
|
683
|
+
note.name = "__agent_chat_" + agentId;
|
|
684
|
+
note.locked = true;
|
|
685
|
+
note.layoutMode = "VERTICAL";
|
|
686
|
+
note.primaryAxisSizingMode = "AUTO";
|
|
687
|
+
note.counterAxisSizingMode = "AUTO";
|
|
688
|
+
note.paddingLeft = 10;
|
|
689
|
+
note.paddingRight = 10;
|
|
690
|
+
note.paddingTop = 6;
|
|
691
|
+
note.paddingBottom = 6;
|
|
692
|
+
note.itemSpacing = 2;
|
|
693
|
+
note.cornerRadius = 8;
|
|
694
|
+
note.fills = [{ type: "SOLID", color: { r: 1, g: 1, b: 1 } }];
|
|
695
|
+
note.strokes = [{ type: "SOLID", color: color }];
|
|
696
|
+
note.strokeWeight = 1.5;
|
|
697
|
+
note.effects = [{
|
|
698
|
+
type: "DROP_SHADOW",
|
|
699
|
+
color: { r: 0, g: 0, b: 0, a: 0.12 },
|
|
700
|
+
offset: { x: 0, y: 2 },
|
|
701
|
+
radius: 6,
|
|
702
|
+
visible: true,
|
|
703
|
+
blendMode: "NORMAL",
|
|
704
|
+
spread: 0,
|
|
705
|
+
}];
|
|
706
|
+
note.x = x;
|
|
707
|
+
note.y = y;
|
|
708
|
+
|
|
709
|
+
try {
|
|
710
|
+
await figma.loadFontAsync({ family: "Inter", style: "Bold" });
|
|
711
|
+
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
|
|
712
|
+
|
|
713
|
+
var nameText = figma.createText();
|
|
714
|
+
nameText.fontName = { family: "Inter", style: "Bold" };
|
|
715
|
+
nameText.characters = agentId;
|
|
716
|
+
nameText.fontSize = 10;
|
|
717
|
+
nameText.fills = [{ type: "SOLID", color: color }];
|
|
718
|
+
nameText.locked = true;
|
|
719
|
+
note.appendChild(nameText);
|
|
720
|
+
|
|
721
|
+
var msgText = figma.createText();
|
|
722
|
+
msgText.fontName = { family: "Inter", style: "Regular" };
|
|
723
|
+
msgText.characters = message;
|
|
724
|
+
msgText.fontSize = 10;
|
|
725
|
+
msgText.fills = [{ type: "SOLID", color: { r: 0.3, g: 0.3, b: 0.3 } }];
|
|
726
|
+
msgText.locked = true;
|
|
727
|
+
note.appendChild(msgText);
|
|
728
|
+
} catch (e) {
|
|
729
|
+
note.resize(140, 36);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
figma.currentPage.appendChild(note);
|
|
733
|
+
return note.id;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function cleanupAgentChatNotes() {
|
|
737
|
+
try {
|
|
738
|
+
var notes = figma.currentPage.findAll(function(n) {
|
|
739
|
+
return n.name && n.name.indexOf("__agent_chat_") === 0;
|
|
740
|
+
});
|
|
741
|
+
for (var i = 0; i < notes.length; i++) {
|
|
742
|
+
try { notes[i].remove(); } catch (e) {}
|
|
743
|
+
}
|
|
744
|
+
} catch (e) {}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// ─── End Multi-Agent Cursor System ───────────────────────────────────────────
|
|
748
|
+
|
|
749
|
+
async function normalizeVariableValue(resolvedType, value) {
|
|
750
|
+
if (value === undefined || value === null) return value;
|
|
751
|
+
|
|
752
|
+
if (
|
|
753
|
+
typeof value === "object" &&
|
|
754
|
+
value &&
|
|
755
|
+
value.type === "VARIABLE_ALIAS" &&
|
|
756
|
+
typeof value.variableId === "string"
|
|
757
|
+
) {
|
|
758
|
+
if (!figma.variables || !figma.variables.getVariableByIdAsync || !figma.variables.createVariableAlias) {
|
|
759
|
+
throw new Error("Variable aliasing is unavailable in this plugin runtime");
|
|
760
|
+
}
|
|
761
|
+
const aliasTarget = await figma.variables.getVariableByIdAsync(value.variableId);
|
|
762
|
+
if (!aliasTarget) {
|
|
763
|
+
throw new Error("Alias target variable not found: " + value.variableId);
|
|
764
|
+
}
|
|
765
|
+
return figma.variables.createVariableAlias(aliasTarget);
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (resolvedType === "COLOR") {
|
|
769
|
+
if (typeof value === "string") {
|
|
770
|
+
var hex = value.trim().replace(/^#/, "");
|
|
771
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
772
|
+
return {
|
|
773
|
+
r: parseInt(hex.slice(0, 2), 16) / 255,
|
|
774
|
+
g: parseInt(hex.slice(2, 4), 16) / 255,
|
|
775
|
+
b: parseInt(hex.slice(4, 6), 16) / 255,
|
|
776
|
+
a: hex.length === 8 ? parseInt(hex.slice(6, 8), 16) / 255 : 1,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (
|
|
781
|
+
typeof value === "object" &&
|
|
782
|
+
typeof value.r === "number" &&
|
|
783
|
+
typeof value.g === "number" &&
|
|
784
|
+
typeof value.b === "number"
|
|
785
|
+
) {
|
|
786
|
+
return {
|
|
787
|
+
r: value.r,
|
|
788
|
+
g: value.g,
|
|
789
|
+
b: value.b,
|
|
790
|
+
a: typeof value.a === "number" ? value.a : 1,
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
throw new Error("Invalid COLOR value: expected #RRGGBB, #RRGGBBAA, or {r,g,b,a}");
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
if (resolvedType === "FLOAT") {
|
|
797
|
+
if (typeof value === "number") return value;
|
|
798
|
+
if (typeof value === "string" && value.trim() !== "") {
|
|
799
|
+
var parsed = Number(value);
|
|
800
|
+
if (!Number.isNaN(parsed)) return parsed;
|
|
801
|
+
}
|
|
802
|
+
throw new Error("Invalid FLOAT value: expected number");
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (resolvedType === "BOOLEAN") {
|
|
806
|
+
if (typeof value === "boolean") return value;
|
|
807
|
+
if (value === "true") return true;
|
|
808
|
+
if (value === "false") return false;
|
|
809
|
+
throw new Error("Invalid BOOLEAN value: expected true/false");
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (resolvedType === "STRING") {
|
|
813
|
+
return String(value);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return value;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// ─── Handle messages from the UI (which receives them from the WebSocket) ────
|
|
820
|
+
|
|
821
|
+
figma.ui.onmessage = async (msg) => {
|
|
822
|
+
if (msg.type === "resize-ui") {
|
|
823
|
+
var width = typeof msg.width === "number" ? Math.max(280, Math.round(msg.width)) : UI_WIDTH;
|
|
824
|
+
var height = typeof msg.height === "number"
|
|
825
|
+
? Math.max(UI_MIN_HEIGHT, Math.min(UI_MAX_HEIGHT, Math.round(msg.height)))
|
|
826
|
+
: UI_HEIGHT;
|
|
827
|
+
UI_WIDTH = width;
|
|
828
|
+
UI_HEIGHT = height;
|
|
829
|
+
figma.ui.resize(UI_WIDTH, UI_HEIGHT);
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
// Handle agent cursor commands from UI
|
|
834
|
+
if (msg.type === "agent-command") {
|
|
835
|
+
if (msg.command === "cleanup") {
|
|
836
|
+
removeAllAgentCursorNodes();
|
|
837
|
+
cleanupOrphanedCursors();
|
|
838
|
+
}
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// msg: { type: "bridge-request", id: string, method: string, params: object }
|
|
843
|
+
if (msg.type !== "bridge-request") return;
|
|
844
|
+
|
|
845
|
+
const { id, method, params } = msg;
|
|
846
|
+
|
|
847
|
+
// Activate agent cursor for this operation (errors must not block the real work)
|
|
848
|
+
try { if (!THEATRE_SKIP_METHODS[method]) await activateAgentForOperation(method, params); } catch (e) {}
|
|
849
|
+
|
|
850
|
+
try {
|
|
851
|
+
let result;
|
|
852
|
+
|
|
853
|
+
switch (method) {
|
|
854
|
+
|
|
855
|
+
// ── Core: execute arbitrary plugin code ────────────────────────────
|
|
856
|
+
case "execute": {
|
|
857
|
+
// params.code is a string of Figma Plugin API code
|
|
858
|
+
// We wrap it in an async function so `return` works
|
|
859
|
+
const normalizedCode = normalizeExecuteCode(params.code);
|
|
860
|
+
const fn = new Function("figma", `return (async () => { ${normalizedCode} })();`);
|
|
861
|
+
result = await fn(figma);
|
|
862
|
+
break;
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// ── Convenience: get node by ID ────────────────────────────────────
|
|
866
|
+
case "getNode": {
|
|
867
|
+
const node = await figma.getNodeByIdAsync(params.nodeId);
|
|
868
|
+
if (!node) throw new Error("Node not found: " + params.nodeId);
|
|
869
|
+
result = serializeNode(node);
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// ── Convenience: take screenshot ───────────────────────────────────
|
|
874
|
+
case "screenshot": {
|
|
875
|
+
const node = await figma.getNodeByIdAsync(params.nodeId);
|
|
876
|
+
if (!node) throw new Error("Node not found: " + params.nodeId);
|
|
877
|
+
if (!('exportAsync' in node)) throw new Error('Node does not support export');
|
|
878
|
+
const bytes = await node.exportAsync({
|
|
879
|
+
format: "PNG",
|
|
880
|
+
constraint: { type: "SCALE", value: params.scale || 2 },
|
|
881
|
+
});
|
|
882
|
+
// Send bytes as Uint8Array; UI layer will convert to base64
|
|
883
|
+
figma.ui.postMessage({
|
|
884
|
+
type: "bridge-response",
|
|
885
|
+
id,
|
|
886
|
+
resultBytes: Array.from(bytes),
|
|
887
|
+
});
|
|
888
|
+
return; // early return — we sent the response directly
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// ── Convenience: import image from a data URI and return image hash ──
|
|
892
|
+
case "importImage": {
|
|
893
|
+
if (typeof params.imageDataUri !== "string" || !params.imageDataUri.startsWith("data:image")) {
|
|
894
|
+
throw new Error("importImage requires imageDataUri");
|
|
895
|
+
}
|
|
896
|
+
const response = await fetch(params.imageDataUri);
|
|
897
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
898
|
+
const image = figma.createImage(bytes);
|
|
899
|
+
result = {
|
|
900
|
+
imageHash: image.hash,
|
|
901
|
+
byteLength: bytes.length,
|
|
902
|
+
};
|
|
903
|
+
break;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// ── Convenience: list pages ────────────────────────────────────────
|
|
907
|
+
case "getPages": {
|
|
908
|
+
result = figma.root.children.map((p) => ({
|
|
909
|
+
id: p.id,
|
|
910
|
+
name: p.name,
|
|
911
|
+
}));
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
// ── Convenience: list component sets ───────────────────────────────
|
|
916
|
+
case "getComponentSets": {
|
|
917
|
+
var sets = figma.currentPage.findAll(function(n) { return n.type === "COMPONENT_SET"; });
|
|
918
|
+
result = sets.map((s) => ({
|
|
919
|
+
id: s.id,
|
|
920
|
+
name: s.name,
|
|
921
|
+
description: s.description || "",
|
|
922
|
+
children: s.children.map((c) => ({
|
|
923
|
+
id: c.id,
|
|
924
|
+
name: c.name,
|
|
925
|
+
type: c.type,
|
|
926
|
+
})),
|
|
927
|
+
}));
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// ── Convenience: get tokens / variables ────────────────────────────
|
|
932
|
+
case "getTokens": {
|
|
933
|
+
const collections = await figma.variables.getLocalVariableCollectionsAsync();
|
|
934
|
+
const filtered = params.collectionId
|
|
935
|
+
? collections.filter((c) => c.id === params.collectionId)
|
|
936
|
+
: collections;
|
|
937
|
+
const tokens = [];
|
|
938
|
+
for (const col of filtered) {
|
|
939
|
+
for (const varId of col.variableIds) {
|
|
940
|
+
const v = await figma.variables.getVariableByIdAsync(varId);
|
|
941
|
+
if (!v) continue;
|
|
942
|
+
const modeValues = {};
|
|
943
|
+
for (const [modeId, val] of Object.entries(v.valuesByMode)) {
|
|
944
|
+
const mode = col.modes.find((m) => m.modeId === modeId);
|
|
945
|
+
modeValues[mode ? mode.name : modeId] = val;
|
|
946
|
+
}
|
|
947
|
+
var firstVal = Object.values(modeValues)[0];
|
|
948
|
+
tokens.push({
|
|
949
|
+
id: v.id,
|
|
950
|
+
name: v.name,
|
|
951
|
+
type: v.resolvedType,
|
|
952
|
+
value: firstVal !== undefined ? firstVal : null,
|
|
953
|
+
collectionId: col.id,
|
|
954
|
+
modeValues,
|
|
955
|
+
description: v.description || "",
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
result = tokens;
|
|
960
|
+
break;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
// ── Ping / health check ────────────────────────────────────────────
|
|
964
|
+
case "ping": {
|
|
965
|
+
result = {
|
|
966
|
+
status: "ok",
|
|
967
|
+
fileName: figma.root.name,
|
|
968
|
+
pageCount: figma.root.children.length,
|
|
969
|
+
timestamp: Date.now(),
|
|
970
|
+
};
|
|
971
|
+
break;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// ── Navigation & Status ─────────────────────────────────────────────
|
|
975
|
+
case "getStatus": {
|
|
976
|
+
var connStatus = "connected";
|
|
977
|
+
var currentPage = figma.currentPage;
|
|
978
|
+
result = {
|
|
979
|
+
status: connStatus,
|
|
980
|
+
fileName: figma.root.name,
|
|
981
|
+
currentPage: { id: currentPage.id, name: currentPage.name },
|
|
982
|
+
pageCount: figma.root.children.length,
|
|
983
|
+
timestamp: Date.now(),
|
|
984
|
+
};
|
|
985
|
+
break;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
case "getCapabilities": {
|
|
989
|
+
result = getBridgeCapabilities();
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
case "navigate": {
|
|
994
|
+
if (params.nodeId) {
|
|
995
|
+
var navNode = await figma.getNodeByIdAsync(params.nodeId);
|
|
996
|
+
if (navNode) figma.viewport.scrollAndZoomIntoView([navNode]);
|
|
997
|
+
result = { navigated: true, nodeId: params.nodeId };
|
|
998
|
+
} else {
|
|
999
|
+
result = { navigated: false, error: "No nodeId provided" };
|
|
1000
|
+
}
|
|
1001
|
+
break;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
case "getSelection": {
|
|
1005
|
+
var sel = figma.currentPage.selection;
|
|
1006
|
+
result = sel.map(function(n) {
|
|
1007
|
+
return { id: n.id, name: n.name, type: n.type };
|
|
1008
|
+
});
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// ── Variable CRUD ──────────────────────────────────────────────────
|
|
1013
|
+
case "createVariableCollection": {
|
|
1014
|
+
var col = figma.variables.createVariableCollection(params.name);
|
|
1015
|
+
if (params.initialModeName && col.modes.length > 0) {
|
|
1016
|
+
col.renameMode(col.modes[0].modeId, params.initialModeName);
|
|
1017
|
+
}
|
|
1018
|
+
result = {
|
|
1019
|
+
id: col.id,
|
|
1020
|
+
name: col.name,
|
|
1021
|
+
modes: col.modes.map(function(m) { return { modeId: m.modeId, name: m.name }; }),
|
|
1022
|
+
};
|
|
1023
|
+
break;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
case "createVariable": {
|
|
1027
|
+
var targetCollection = await figma.variables.getVariableCollectionByIdAsync(params.collectionId);
|
|
1028
|
+
if (!targetCollection) throw new Error("Collection not found: " + params.collectionId);
|
|
1029
|
+
var newVar = figma.variables.createVariable(
|
|
1030
|
+
params.name,
|
|
1031
|
+
targetCollection,
|
|
1032
|
+
params.resolvedType
|
|
1033
|
+
);
|
|
1034
|
+
if (params.description) newVar.description = params.description;
|
|
1035
|
+
if (params.valuesByMode) {
|
|
1036
|
+
var entries = Object.entries(params.valuesByMode);
|
|
1037
|
+
for (var vi = 0; vi < entries.length; vi++) {
|
|
1038
|
+
newVar.setValueForMode(entries[vi][0], await normalizeVariableValue(params.resolvedType, entries[vi][1]));
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
result = { id: newVar.id, name: newVar.name, resolvedType: newVar.resolvedType };
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
case "updateVariable": {
|
|
1046
|
+
var updVar = await figma.variables.getVariableByIdAsync(params.variableId);
|
|
1047
|
+
if (!updVar) throw new Error("Variable not found: " + params.variableId);
|
|
1048
|
+
updVar.setValueForMode(params.modeId, await normalizeVariableValue(updVar.resolvedType, params.value));
|
|
1049
|
+
result = { id: updVar.id, name: updVar.name, updated: true };
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
case "deleteVariable": {
|
|
1054
|
+
var delVar = await figma.variables.getVariableByIdAsync(params.variableId);
|
|
1055
|
+
if (!delVar) throw new Error("Variable not found: " + params.variableId);
|
|
1056
|
+
delVar.remove();
|
|
1057
|
+
result = { deleted: true, variableId: params.variableId };
|
|
1058
|
+
break;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
case "renameVariable": {
|
|
1062
|
+
var renVar = await figma.variables.getVariableByIdAsync(params.variableId);
|
|
1063
|
+
if (!renVar) throw new Error("Variable not found: " + params.variableId);
|
|
1064
|
+
renVar.name = params.newName;
|
|
1065
|
+
result = { id: renVar.id, name: renVar.name, renamed: true };
|
|
1066
|
+
break;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
case "deleteVariableCollection": {
|
|
1070
|
+
var delCol = await figma.variables.getVariableCollectionByIdAsync(params.collectionId);
|
|
1071
|
+
if (!delCol) throw new Error("Collection not found: " + params.collectionId);
|
|
1072
|
+
delCol.remove();
|
|
1073
|
+
result = { deleted: true, collectionId: params.collectionId };
|
|
1074
|
+
break;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
case "addMode": {
|
|
1078
|
+
var modeCol = await figma.variables.getVariableCollectionByIdAsync(params.collectionId);
|
|
1079
|
+
if (!modeCol) throw new Error("Collection not found: " + params.collectionId);
|
|
1080
|
+
var newMode = modeCol.addMode(params.modeName);
|
|
1081
|
+
result = { collectionId: params.collectionId, modeId: newMode, modeName: params.modeName };
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
case "renameMode": {
|
|
1086
|
+
var rmCol = await figma.variables.getVariableCollectionByIdAsync(params.collectionId);
|
|
1087
|
+
if (!rmCol) throw new Error("Collection not found: " + params.collectionId);
|
|
1088
|
+
rmCol.renameMode(params.modeId, params.newName);
|
|
1089
|
+
result = { collectionId: params.collectionId, modeId: params.modeId, newName: params.newName };
|
|
1090
|
+
break;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
case "batchCreateVariables": {
|
|
1094
|
+
var batchResults = [];
|
|
1095
|
+
for (var bi = 0; bi < params.variables.length; bi++) {
|
|
1096
|
+
var spec = params.variables[bi];
|
|
1097
|
+
var batchCollection = await figma.variables.getVariableCollectionByIdAsync(spec.collectionId);
|
|
1098
|
+
if (!batchCollection) throw new Error("Collection not found: " + spec.collectionId);
|
|
1099
|
+
var bVar = figma.variables.createVariable(spec.name, batchCollection, spec.resolvedType);
|
|
1100
|
+
if (spec.description) bVar.description = spec.description;
|
|
1101
|
+
if (spec.valuesByMode) {
|
|
1102
|
+
var bEntries = Object.entries(spec.valuesByMode);
|
|
1103
|
+
for (var bj = 0; bj < bEntries.length; bj++) {
|
|
1104
|
+
bVar.setValueForMode(bEntries[bj][0], await normalizeVariableValue(spec.resolvedType, bEntries[bj][1]));
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
batchResults.push({ id: bVar.id, name: bVar.name, resolvedType: bVar.resolvedType });
|
|
1108
|
+
}
|
|
1109
|
+
result = { created: batchResults.length, variables: batchResults };
|
|
1110
|
+
break;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
case "batchUpdateVariables": {
|
|
1114
|
+
var batchUpdated = 0;
|
|
1115
|
+
for (var ui = 0; ui < params.updates.length; ui++) {
|
|
1116
|
+
var upd = params.updates[ui];
|
|
1117
|
+
var buVar = await figma.variables.getVariableByIdAsync(upd.variableId);
|
|
1118
|
+
if (buVar) {
|
|
1119
|
+
buVar.setValueForMode(upd.modeId, await normalizeVariableValue(buVar.resolvedType, upd.value));
|
|
1120
|
+
batchUpdated++;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
result = { updated: batchUpdated, total: params.updates.length };
|
|
1124
|
+
break;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// ── Node Operations ────────────────────────────────────────────────
|
|
1128
|
+
case "cloneNode": {
|
|
1129
|
+
var srcNode = await figma.getNodeByIdAsync(params.nodeId);
|
|
1130
|
+
if (!srcNode) throw new Error("Node not found: " + params.nodeId);
|
|
1131
|
+
var cloned = srcNode.clone();
|
|
1132
|
+
if (params.x !== undefined) cloned.x = params.x;
|
|
1133
|
+
if (params.y !== undefined) cloned.y = params.y;
|
|
1134
|
+
result = { id: cloned.id, name: cloned.name, type: cloned.type };
|
|
1135
|
+
break;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
case "deleteNode": {
|
|
1139
|
+
var delNode = await figma.getNodeByIdAsync(params.nodeId);
|
|
1140
|
+
if (!delNode) throw new Error("Node not found: " + params.nodeId);
|
|
1141
|
+
delNode.remove();
|
|
1142
|
+
result = { deleted: true, nodeId: params.nodeId };
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
case "moveNode": {
|
|
1147
|
+
var mvNode = await figma.getNodeByIdAsync(params.nodeId);
|
|
1148
|
+
if (!mvNode) throw new Error("Node not found: " + params.nodeId);
|
|
1149
|
+
if (params.x !== undefined) mvNode.x = params.x;
|
|
1150
|
+
if (params.y !== undefined) mvNode.y = params.y;
|
|
1151
|
+
if (params.parentId) {
|
|
1152
|
+
var newParent = await figma.getNodeByIdAsync(params.parentId);
|
|
1153
|
+
if (newParent && "appendChild" in newParent) {
|
|
1154
|
+
newParent.appendChild(mvNode);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
result = { id: mvNode.id, x: mvNode.x, y: mvNode.y };
|
|
1158
|
+
break;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
case "resizeNode": {
|
|
1162
|
+
var rsNode = await figma.getNodeByIdAsync(params.nodeId);
|
|
1163
|
+
if (!rsNode) throw new Error("Node not found: " + params.nodeId);
|
|
1164
|
+
if ("resize" in rsNode) {
|
|
1165
|
+
rsNode.resize(params.width, params.height);
|
|
1166
|
+
}
|
|
1167
|
+
result = { id: rsNode.id, width: params.width, height: params.height };
|
|
1168
|
+
break;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
case "renameNode": {
|
|
1172
|
+
var rnNode = await figma.getNodeByIdAsync(params.nodeId);
|
|
1173
|
+
if (!rnNode) throw new Error("Node not found: " + params.nodeId);
|
|
1174
|
+
rnNode.name = params.newName;
|
|
1175
|
+
result = { id: rnNode.id, name: rnNode.name };
|
|
1176
|
+
break;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
case "setFills": {
|
|
1180
|
+
var fillNode = await figma.getNodeByIdAsync(params.nodeId);
|
|
1181
|
+
if (!fillNode) throw new Error("Node not found: " + params.nodeId);
|
|
1182
|
+
if ("fills" in fillNode) {
|
|
1183
|
+
fillNode.fills = params.fills;
|
|
1184
|
+
}
|
|
1185
|
+
result = { id: fillNode.id, fills: params.fills };
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
case "setStrokes": {
|
|
1190
|
+
var strokeNode = await figma.getNodeByIdAsync(params.nodeId);
|
|
1191
|
+
if (!strokeNode) throw new Error("Node not found: " + params.nodeId);
|
|
1192
|
+
if ("strokes" in strokeNode) {
|
|
1193
|
+
strokeNode.strokes = params.strokes;
|
|
1194
|
+
if (params.strokeWeight !== undefined) strokeNode.strokeWeight = params.strokeWeight;
|
|
1195
|
+
}
|
|
1196
|
+
result = { id: strokeNode.id, strokes: params.strokes };
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
case "setText": {
|
|
1201
|
+
var textNode = await figma.getNodeByIdAsync(params.nodeId);
|
|
1202
|
+
if (!textNode) throw new Error("Node not found: " + params.nodeId);
|
|
1203
|
+
if (textNode.type !== "TEXT") throw new Error("Node is not a text node");
|
|
1204
|
+
await figma.loadFontAsync(textNode.fontName);
|
|
1205
|
+
textNode.characters = params.characters;
|
|
1206
|
+
if (params.fontSize) textNode.fontSize = params.fontSize;
|
|
1207
|
+
result = { id: textNode.id, characters: textNode.characters };
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
// ── Component Operations ───────────────────────────────────────────
|
|
1212
|
+
case "searchComponents": {
|
|
1213
|
+
var query = (params.query || "").toLowerCase();
|
|
1214
|
+
var limit = params.limit || 20;
|
|
1215
|
+
var allComponents = figma.currentPage.findAll(function(n) {
|
|
1216
|
+
return n.type === "COMPONENT" || n.type === "COMPONENT_SET";
|
|
1217
|
+
});
|
|
1218
|
+
var matched = [];
|
|
1219
|
+
for (var ci = 0; ci < allComponents.length; ci++) {
|
|
1220
|
+
if (allComponents[ci].name.toLowerCase().indexOf(query) !== -1) {
|
|
1221
|
+
matched.push({
|
|
1222
|
+
id: allComponents[ci].id,
|
|
1223
|
+
name: allComponents[ci].name,
|
|
1224
|
+
type: allComponents[ci].type,
|
|
1225
|
+
description: allComponents[ci].description || "",
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
if (matched.length >= limit) break;
|
|
1229
|
+
}
|
|
1230
|
+
result = matched;
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
case "instantiateComponent": {
|
|
1235
|
+
var comp = null;
|
|
1236
|
+
if (params.nodeId) {
|
|
1237
|
+
comp = await figma.getNodeByIdAsync(params.nodeId);
|
|
1238
|
+
}
|
|
1239
|
+
if (!comp) throw new Error("Component not found");
|
|
1240
|
+
if (comp.type === "COMPONENT_SET") {
|
|
1241
|
+
// Find matching variant
|
|
1242
|
+
var targetVariant = null;
|
|
1243
|
+
if (params.variant) {
|
|
1244
|
+
var variantStr = Object.entries(params.variant).map(function(e) {
|
|
1245
|
+
return e[0] + "=" + e[1];
|
|
1246
|
+
}).join(", ");
|
|
1247
|
+
for (var vi2 = 0; vi2 < comp.children.length; vi2++) {
|
|
1248
|
+
if (comp.children[vi2].name === variantStr) {
|
|
1249
|
+
targetVariant = comp.children[vi2];
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
if (!targetVariant) targetVariant = comp.children[0];
|
|
1255
|
+
comp = targetVariant;
|
|
1256
|
+
}
|
|
1257
|
+
if (comp.type !== "COMPONENT") throw new Error("Node is not a component: " + comp.type);
|
|
1258
|
+
var instance = comp.createInstance();
|
|
1259
|
+
if (params.x !== undefined) instance.x = params.x;
|
|
1260
|
+
if (params.y !== undefined) instance.y = params.y;
|
|
1261
|
+
if (params.parentId) {
|
|
1262
|
+
var instParent = await figma.getNodeByIdAsync(params.parentId);
|
|
1263
|
+
if (instParent && "appendChild" in instParent) {
|
|
1264
|
+
instParent.appendChild(instance);
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
result = { id: instance.id, name: instance.name, type: instance.type, componentId: comp.id };
|
|
1268
|
+
break;
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
case "setDescription": {
|
|
1272
|
+
var descNode = await figma.getNodeByIdAsync(params.nodeId);
|
|
1273
|
+
if (!descNode) throw new Error("Node not found: " + params.nodeId);
|
|
1274
|
+
if ("description" in descNode) {
|
|
1275
|
+
descNode.description = params.description;
|
|
1276
|
+
}
|
|
1277
|
+
result = { id: descNode.id, description: params.description };
|
|
1278
|
+
break;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
case "getVariables": {
|
|
1282
|
+
if (!figma.variables || !figma.variables.getLocalVariableCollectionsAsync) {
|
|
1283
|
+
throw new Error("Variables API unavailable in this plugin runtime");
|
|
1284
|
+
}
|
|
1285
|
+
var verbosity = params.verbosity || "summary";
|
|
1286
|
+
var gvCollections = await figma.variables.getLocalVariableCollectionsAsync();
|
|
1287
|
+
if (params.collectionId) {
|
|
1288
|
+
gvCollections = gvCollections.filter(function(c) { return c.id === params.collectionId; });
|
|
1289
|
+
}
|
|
1290
|
+
var gvResult = [];
|
|
1291
|
+
for (var gci = 0; gci < gvCollections.length; gci++) {
|
|
1292
|
+
var gCol = gvCollections[gci];
|
|
1293
|
+
var gVars = [];
|
|
1294
|
+
for (var gvi = 0; gvi < gCol.variableIds.length; gvi++) {
|
|
1295
|
+
var gv = await figma.variables.getVariableByIdAsync(gCol.variableIds[gvi]);
|
|
1296
|
+
if (!gv) continue;
|
|
1297
|
+
var gvEntry = { id: gv.id, name: gv.name, type: gv.resolvedType };
|
|
1298
|
+
if (verbosity !== "inventory") {
|
|
1299
|
+
var gvModes = {};
|
|
1300
|
+
var gvModeEntries = Object.entries(gv.valuesByMode);
|
|
1301
|
+
for (var gmi = 0; gmi < gvModeEntries.length; gmi++) {
|
|
1302
|
+
var mode = gCol.modes.find(function(m) { return m.modeId === gvModeEntries[gmi][0]; });
|
|
1303
|
+
gvModes[mode ? mode.name : gvModeEntries[gmi][0]] = gvModeEntries[gmi][1];
|
|
1304
|
+
}
|
|
1305
|
+
gvEntry.valuesByMode = gvModes;
|
|
1306
|
+
if (verbosity === "full") {
|
|
1307
|
+
gvEntry.description = gv.description || "";
|
|
1308
|
+
gvEntry.collectionId = gCol.id;
|
|
1309
|
+
gvEntry.collectionName = gCol.name;
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
gVars.push(gvEntry);
|
|
1313
|
+
}
|
|
1314
|
+
gvResult.push({
|
|
1315
|
+
id: gCol.id,
|
|
1316
|
+
name: gCol.name,
|
|
1317
|
+
modes: gCol.modes.map(function(m) { return { modeId: m.modeId, name: m.name }; }),
|
|
1318
|
+
variables: gVars,
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
result = gvResult;
|
|
1322
|
+
break;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
case "getStyles": {
|
|
1326
|
+
if (!figma.getLocalPaintStylesAsync || !figma.getLocalTextStylesAsync || !figma.getLocalEffectStylesAsync) {
|
|
1327
|
+
throw new Error("Local styles APIs unavailable in this plugin runtime");
|
|
1328
|
+
}
|
|
1329
|
+
var paintStyles = await figma.getLocalPaintStylesAsync();
|
|
1330
|
+
var textStyles = await figma.getLocalTextStylesAsync();
|
|
1331
|
+
var effectStyles = await figma.getLocalEffectStylesAsync();
|
|
1332
|
+
result = {
|
|
1333
|
+
paint: paintStyles.map(function(s) {
|
|
1334
|
+
return { id: s.id, name: s.name, paints: s.paints, description: s.description || "" };
|
|
1335
|
+
}),
|
|
1336
|
+
text: textStyles.map(function(s) {
|
|
1337
|
+
return {
|
|
1338
|
+
id: s.id, name: s.name,
|
|
1339
|
+
fontName: s.fontName, fontSize: s.fontSize,
|
|
1340
|
+
lineHeight: s.lineHeight, letterSpacing: s.letterSpacing,
|
|
1341
|
+
description: s.description || "",
|
|
1342
|
+
};
|
|
1343
|
+
}),
|
|
1344
|
+
effect: effectStyles.map(function(s) {
|
|
1345
|
+
return { id: s.id, name: s.name, effects: s.effects, description: s.description || "" };
|
|
1346
|
+
}),
|
|
1347
|
+
};
|
|
1348
|
+
break;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
case "createChild": {
|
|
1352
|
+
var parentNode = params.parentId
|
|
1353
|
+
? await figma.getNodeByIdAsync(params.parentId)
|
|
1354
|
+
: figma.currentPage;
|
|
1355
|
+
if (!parentNode || !("appendChild" in parentNode))
|
|
1356
|
+
throw new Error("Invalid parent node");
|
|
1357
|
+
var child;
|
|
1358
|
+
switch (params.childType) {
|
|
1359
|
+
case "FRAME":
|
|
1360
|
+
child = figma.createFrame();
|
|
1361
|
+
break;
|
|
1362
|
+
case "TEXT":
|
|
1363
|
+
child = figma.createText();
|
|
1364
|
+
await figma.loadFontAsync({ family: "Inter", style: "Regular" });
|
|
1365
|
+
if (params.characters) child.characters = params.characters;
|
|
1366
|
+
break;
|
|
1367
|
+
case "RECTANGLE":
|
|
1368
|
+
child = figma.createRectangle();
|
|
1369
|
+
break;
|
|
1370
|
+
case "ELLIPSE":
|
|
1371
|
+
child = figma.createEllipse();
|
|
1372
|
+
break;
|
|
1373
|
+
case "LINE":
|
|
1374
|
+
child = figma.createLine();
|
|
1375
|
+
break;
|
|
1376
|
+
case "COMPONENT":
|
|
1377
|
+
child = figma.createComponent();
|
|
1378
|
+
break;
|
|
1379
|
+
case "SECTION":
|
|
1380
|
+
child = figma.createSection();
|
|
1381
|
+
break;
|
|
1382
|
+
default:
|
|
1383
|
+
child = figma.createFrame();
|
|
1384
|
+
}
|
|
1385
|
+
if (params.name) child.name = params.name;
|
|
1386
|
+
if (params.width && params.height && "resize" in child) child.resize(params.width, params.height);
|
|
1387
|
+
if (params.x !== undefined) child.x = params.x;
|
|
1388
|
+
if (params.y !== undefined) child.y = params.y;
|
|
1389
|
+
parentNode.appendChild(child);
|
|
1390
|
+
result = { id: child.id, name: child.name, type: child.type };
|
|
1391
|
+
break;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
case "cleanupAgentCursors": {
|
|
1395
|
+
removeAllAgentCursorNodes();
|
|
1396
|
+
cleanupAgentChatNotes();
|
|
1397
|
+
cleanupOrphanedCursors();
|
|
1398
|
+
result = { cleaned: true };
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
// ── Swarm Agent Cursor Operations ─────────────────────────────────
|
|
1403
|
+
case "spawnAgentCursor": {
|
|
1404
|
+
await createAgentCursorNode(params.agentId, params.x || 0, params.y || 0);
|
|
1405
|
+
result = { agentId: params.agentId, spawned: true };
|
|
1406
|
+
break;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
case "moveAgentCursor": {
|
|
1410
|
+
if (params.animate) {
|
|
1411
|
+
await animateAgentCursorTo(params.agentId, params.x, params.y, params.durationMs);
|
|
1412
|
+
} else {
|
|
1413
|
+
moveAgentCursorTo(params.agentId, params.x, params.y);
|
|
1414
|
+
}
|
|
1415
|
+
result = { agentId: params.agentId, x: params.x, y: params.y };
|
|
1416
|
+
break;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
case "updateAgentLabel": {
|
|
1420
|
+
updateAgentCursorLabel(params.agentId, params.label);
|
|
1421
|
+
result = { agentId: params.agentId, label: params.label };
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
case "removeAgentCursor": {
|
|
1426
|
+
removeAgentCursorNode(params.agentId);
|
|
1427
|
+
result = { agentId: params.agentId, removed: true };
|
|
1428
|
+
break;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
case "agentChat": {
|
|
1432
|
+
var noteId = await createAgentChatNote(params.agentId, params.message, params.x || 0, params.y || 0);
|
|
1433
|
+
result = { agentId: params.agentId, noteId: noteId };
|
|
1434
|
+
break;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
case "cleanupAgentChats": {
|
|
1438
|
+
cleanupAgentChatNotes();
|
|
1439
|
+
result = { cleaned: true };
|
|
1440
|
+
break;
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
default:
|
|
1444
|
+
throw new Error(`Unknown bridge method: ${method}`);
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// Post-operation: cursor inspects the result (visual operations only)
|
|
1448
|
+
try { if (!THEATRE_SKIP_METHODS[method]) await postOperationEffect(method, params, result); } catch (e) {}
|
|
1449
|
+
|
|
1450
|
+
figma.ui.postMessage({ type: "bridge-response", id, result });
|
|
1451
|
+
} catch (err) {
|
|
1452
|
+
figma.ui.postMessage({
|
|
1453
|
+
type: "bridge-response",
|
|
1454
|
+
id,
|
|
1455
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
|
|
1460
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
1461
|
+
|
|
1462
|
+
function serializeNode(node) {
|
|
1463
|
+
const base = {
|
|
1464
|
+
id: node.id,
|
|
1465
|
+
name: node.name,
|
|
1466
|
+
type: node.type,
|
|
1467
|
+
visible: node.visible,
|
|
1468
|
+
};
|
|
1469
|
+
|
|
1470
|
+
if ("x" in node) {
|
|
1471
|
+
Object.assign(base, {
|
|
1472
|
+
x: node.x,
|
|
1473
|
+
y: node.y,
|
|
1474
|
+
width: node.width,
|
|
1475
|
+
height: node.height,
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
if ("fills" in node) base.fills = node.fills;
|
|
1479
|
+
if ("strokes" in node) base.strokes = node.strokes;
|
|
1480
|
+
if ("effects" in node) base.effects = node.effects;
|
|
1481
|
+
if ("opacity" in node) base.opacity = node.opacity;
|
|
1482
|
+
if ("cornerRadius" in node) base.cornerRadius = node.cornerRadius;
|
|
1483
|
+
if ("layoutMode" in node) {
|
|
1484
|
+
Object.assign(base, {
|
|
1485
|
+
layoutMode: node.layoutMode,
|
|
1486
|
+
primaryAxisSizingMode: node.primaryAxisSizingMode,
|
|
1487
|
+
counterAxisSizingMode: node.counterAxisSizingMode,
|
|
1488
|
+
paddingTop: node.paddingTop,
|
|
1489
|
+
paddingBottom: node.paddingBottom,
|
|
1490
|
+
paddingLeft: node.paddingLeft,
|
|
1491
|
+
paddingRight: node.paddingRight,
|
|
1492
|
+
itemSpacing: node.itemSpacing,
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
if ("characters" in node) {
|
|
1496
|
+
Object.assign(base, {
|
|
1497
|
+
characters: node.characters,
|
|
1498
|
+
fontSize: node.fontSize,
|
|
1499
|
+
fontName: node.fontName,
|
|
1500
|
+
lineHeight: node.lineHeight,
|
|
1501
|
+
letterSpacing: node.letterSpacing,
|
|
1502
|
+
textAlignHorizontal: node.textAlignHorizontal,
|
|
1503
|
+
textAlignVertical: node.textAlignVertical,
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
if ("children" in node) {
|
|
1507
|
+
base.childCount = node.children.length;
|
|
1508
|
+
base.children = node.children.map((c) => ({
|
|
1509
|
+
id: c.id,
|
|
1510
|
+
name: c.name,
|
|
1511
|
+
type: c.type,
|
|
1512
|
+
}));
|
|
1513
|
+
}
|
|
1514
|
+
if ("mainComponent" in node) {
|
|
1515
|
+
base.mainComponentId = node.mainComponent ? node.mainComponent.id : undefined;
|
|
1516
|
+
base.mainComponentName = node.mainComponent ? node.mainComponent.name : undefined;
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
return base;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
// Keep plugin running
|
|
1523
|
+
figma.on("close", () => {
|
|
1524
|
+
removeAllAgentCursorNodes();
|
|
1525
|
+
cleanupOrphanedCursors();
|
|
1526
|
+
});
|
|
1527
|
+
|
|
1528
|
+
console.log("✅ Figma Intelligence Bridge Plugin loaded");
|