@mmerterden/multi-agent-pipeline 11.2.0 → 11.3.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/CHANGELOG.md +37 -0
- package/README.md +2 -2
- package/docs/FIGMA_PIPELINE.md +12 -25
- package/docs/architecture.md +3 -5
- package/docs/features.md +8 -8
- package/install/_dev-only-files.mjs +0 -6
- package/install/claude.mjs +33 -15
- package/install/copilot.mjs +16 -14
- package/install/templates/copilot-instructions.md +3 -3
- package/package.json +1 -1
- package/pipeline/commands/{multi-agent.md → multi-agent/SKILL.md} +39 -39
- package/pipeline/commands/multi-agent/{analysis.md → analysis/SKILL.md} +18 -18
- package/pipeline/commands/multi-agent/{analysis-resolve.md → analysis-resolve/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{autopilot.md → autopilot/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{build-optimize.md → build-optimize/SKILL.md} +1 -1
- package/pipeline/commands/multi-agent/{channels.md → channels/SKILL.md} +12 -12
- package/pipeline/commands/multi-agent/{dev.md → dev/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{dev-autopilot.md → dev-autopilot/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{dev-local.md → dev-local/SKILL.md} +5 -5
- package/pipeline/commands/multi-agent/{dev-local-autopilot.md → dev-local-autopilot/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{finish.md → finish/SKILL.md} +6 -6
- package/pipeline/commands/multi-agent/{generate.md → generate/SKILL.md} +3 -3
- package/pipeline/commands/multi-agent/{help.md → help/SKILL.md} +4 -4
- package/pipeline/commands/multi-agent/{local.md → local/SKILL.md} +5 -5
- package/pipeline/commands/multi-agent/{local-autopilot.md → local-autopilot/SKILL.md} +5 -5
- package/pipeline/commands/multi-agent/{manual-test.md → manual-test/SKILL.md} +1 -1
- package/pipeline/commands/multi-agent/refactor/SKILL.md +257 -0
- package/pipeline/commands/multi-agent/{resume.md → resume/SKILL.md} +1 -1
- package/pipeline/commands/multi-agent/{review.md → review/SKILL.md} +5 -5
- package/pipeline/commands/multi-agent/{setup.md → setup/SKILL.md} +1 -1
- package/pipeline/commands/multi-agent/{sync.md → sync/SKILL.md} +20 -58
- package/pipeline/{commands/multi-agent → multi-agent-refs}/_account-picker.md +2 -2
- package/pipeline/{commands/multi-agent → multi-agent-refs}/_dev-context.md +2 -2
- package/pipeline/{commands/multi-agent → multi-agent-refs}/_repo-picker.md +2 -2
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/android-guide.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/jira.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/wiki.md +7 -7
- package/pipeline/multi-agent-refs/component-dispatch.md +92 -0
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/cross-cli-contract.md +10 -77
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/verify-by-test.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/generate-issue.md +2 -2
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/issue-jira-triad.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/keychain.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/log-format.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/modes.md +2 -2
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/operations.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-0-init.md +10 -10
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-1-analysis.md +6 -6
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-2-planning.md +3 -3
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-3-dev.md +10 -10
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-4-review.md +8 -8
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-5-test.md +3 -3
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-6-commit.md +4 -4
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases/phase-7-report.md +6 -6
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/phases.md +12 -12
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/progress-contract.md +4 -4
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/swiftui-guide.md +1 -1
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/tracker-contract.md +3 -3
- package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/wiki-capture.md +4 -4
- package/pipeline/preferences-template.json +17 -1
- package/pipeline/rules/figma-pipeline.md +2 -2
- package/pipeline/schemas/prefs.schema.json +0 -34
- package/pipeline/scripts/README.md +1 -3
- package/pipeline/scripts/fixtures/install-layout.tsv +6 -5
- package/pipeline/scripts/gen-mode-dispatch.mjs +3 -3
- package/pipeline/scripts/phase-banner.sh +2 -2
- package/pipeline/scripts/review-scope.mjs +94 -0
- package/pipeline/scripts/scan-agent-config.sh +2 -2
- package/pipeline/scripts/smoke-agent-log-cost.sh +1 -1
- package/pipeline/scripts/smoke-agent-model-routing.sh +4 -4
- package/pipeline/scripts/smoke-autopilot-circuit-breaker.sh +2 -2
- package/pipeline/scripts/smoke-channels-approval-gate.sh +2 -2
- package/pipeline/scripts/smoke-channels-flow.sh +5 -5
- package/pipeline/scripts/smoke-clarify.sh +1 -1
- package/pipeline/scripts/smoke-commands-skills-parity.sh +3 -3
- package/pipeline/scripts/smoke-community-gates.sh +1 -1
- package/pipeline/scripts/smoke-compliance-skills.sh +2 -2
- package/pipeline/scripts/smoke-cost-summary.sh +1 -1
- package/pipeline/scripts/smoke-cross-cli-behavior.sh +17 -43
- package/pipeline/scripts/smoke-cross-phase-cohesion.sh +1 -1
- package/pipeline/scripts/smoke-dev-critic.sh +1 -1
- package/pipeline/scripts/smoke-diff-explain.sh +1 -1
- package/pipeline/scripts/smoke-diff-risk.sh +1 -1
- package/pipeline/scripts/smoke-evidence-gate.sh +1 -1
- package/pipeline/scripts/smoke-figma-dispatch.sh +46 -57
- package/pipeline/scripts/smoke-gate-hooks.sh +1 -1
- package/pipeline/scripts/smoke-generate-issue.sh +8 -8
- package/pipeline/scripts/smoke-handoff-contract.sh +3 -3
- package/pipeline/scripts/smoke-identity-isolation.sh +2 -2
- package/pipeline/scripts/smoke-intent-guard.sh +1 -1
- package/pipeline/scripts/smoke-issue-comment-template.sh +2 -2
- package/pipeline/scripts/smoke-issue-jira-triad.sh +4 -4
- package/pipeline/scripts/smoke-language-axis.sh +6 -6
- package/pipeline/scripts/smoke-learnings-ledger.sh +3 -3
- package/pipeline/scripts/smoke-mode-dispatch-drift.sh +16 -16
- package/pipeline/scripts/smoke-model-fallback.sh +3 -3
- package/pipeline/scripts/smoke-multi-repo-integration.sh +2 -2
- package/pipeline/scripts/smoke-multi-repo-worktree.sh +1 -1
- package/pipeline/scripts/smoke-no-token-prompt.sh +5 -5
- package/pipeline/scripts/smoke-phase-0-multi-repo.sh +1 -1
- package/pipeline/scripts/smoke-phase-6-multi.sh +1 -1
- package/pipeline/scripts/smoke-phase4-gates.sh +45 -0
- package/pipeline/scripts/smoke-plan-approval-gate.sh +5 -5
- package/pipeline/scripts/smoke-plan-todos.sh +3 -3
- package/pipeline/scripts/smoke-pr-review-actions.sh +3 -3
- package/pipeline/scripts/smoke-prefs-language.sh +1 -1
- package/pipeline/scripts/smoke-progress-contract.sh +2 -2
- package/pipeline/scripts/smoke-push-retry.sh +1 -1
- package/pipeline/scripts/smoke-repo-map.sh +2 -2
- package/pipeline/scripts/smoke-shadow-git.sh +1 -1
- package/pipeline/scripts/smoke-skill-authoring.sh +4 -9
- package/pipeline/scripts/smoke-tasklist-ordering.sh +10 -10
- package/pipeline/scripts/smoke-test-gap.sh +1 -1
- package/pipeline/scripts/smoke-token-budget.sh +1 -1
- package/pipeline/scripts/smoke-token-preflight.sh +2 -2
- package/pipeline/scripts/smoke-tracker-contract.sh +13 -13
- package/pipeline/scripts/smoke-tracker-tokens-invocation.sh +5 -5
- package/pipeline/scripts/smoke-triage-memory.sh +2 -2
- package/pipeline/scripts/smoke-update-check.sh +1 -1
- package/pipeline/scripts/smoke-url-enrichment.sh +2 -2
- package/pipeline/scripts/smoke-validator-gates.sh +1 -1
- package/pipeline/scripts/smoke-verify-by-test.sh +3 -3
- package/pipeline/scripts/smoke-wiki-integration.sh +9 -33
- package/pipeline/scripts/smoke-work-summary.sh +1 -1
- package/pipeline/scripts/smoke-worktree-path-convention.sh +4 -4
- package/pipeline/scripts/test-integrity-gate.mjs +83 -0
- package/pipeline/scripts/uninstall.mjs +2 -2
- package/pipeline/skills/.skill-manifest.json +40 -160
- package/pipeline/skills/.skills-index.json +3 -372
- package/pipeline/skills/shared/README.md +1 -1
- package/pipeline/skills/shared/core/multi-agent/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-analysis/SKILL.md +2 -2
- package/pipeline/skills/shared/core/multi-agent-analysis-resolve/SKILL.md +3 -3
- package/pipeline/skills/shared/core/multi-agent-build-optimize/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-finish/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +1 -1
- package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +116 -51
- package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +8 -30
- package/pipeline/skills/shared/external/compose-components/SKILL.md +14 -14
- package/pipeline/skills/skills-index.md +2 -43
- package/pipeline/commands/multi-agent/refactor.md +0 -188
- package/pipeline/commands/multi-agent/refs/component-dispatch.md +0 -92
- package/pipeline/scripts/smoke-existing-discovery-gate.sh +0 -71
- package/pipeline/scripts/smoke-figma-android-parity.sh +0 -148
- package/pipeline/scripts/smoke-figma-credential-store.sh +0 -105
- package/pipeline/scripts/smoke-figma-cross-cli-inventory.sh +0 -177
- package/pipeline/scripts/smoke-figma-sync.sh +0 -149
- package/pipeline/scripts/smoke-sync-delegation.sh +0 -74
- package/pipeline/skills/figma-android/README.md +0 -38
- package/pipeline/skills/figma-android/figma-component-code-connect/SKILL.md +0 -62
- package/pipeline/skills/figma-android/figma-component-implement/SKILL.md +0 -158
- package/pipeline/skills/figma-android/figma-component-test/SKILL.md +0 -120
- package/pipeline/skills/figma-android/figma-component-wiki/SKILL.md +0 -35
- package/pipeline/skills/figma-android/figma-to-component/SKILL.md +0 -124
- package/pipeline/skills/figma-common/README.md +0 -64
- package/pipeline/skills/figma-common/figma-bottom-sheets/SKILL.md +0 -152
- package/pipeline/skills/figma-common/figma-cli-iterate/SKILL.md +0 -282
- package/pipeline/skills/figma-common/figma-cli-iterate-mend/SKILL.md +0 -503
- package/pipeline/skills/figma-common/figma-cli-lean-iterate/SKILL.md +0 -288
- package/pipeline/skills/figma-common/figma-cli-skip/SKILL.md +0 -362
- package/pipeline/skills/figma-common/figma-commit/COMMON_REBASE.md +0 -206
- package/pipeline/skills/figma-common/figma-commit/REVIEW.md +0 -337
- package/pipeline/skills/figma-common/figma-commit/SKILL.md +0 -216
- package/pipeline/skills/figma-common/figma-component-confluence-sync/SKILL.md +0 -223
- package/pipeline/skills/figma-common/figma-component-start/SKILL.md +0 -251
- package/pipeline/skills/figma-common/figma-component-status-update/SKILL.md +0 -78
- package/pipeline/skills/figma-common/figma-evolve-component/SKILL.md +0 -61
- package/pipeline/skills/figma-common/figma-fix/SKILL.md +0 -321
- package/pipeline/skills/figma-common/figma-form-integration/SKILL.md +0 -542
- package/pipeline/skills/figma-common/figma-issue/SKILL.md +0 -750
- package/pipeline/skills/figma-common/figma-iterate/SKILL.md +0 -208
- package/pipeline/skills/figma-common/figma-iteration-commit/SKILL.md +0 -1020
- package/pipeline/skills/figma-common/figma-mend/SKILL.md +0 -331
- package/pipeline/skills/figma-common/figma-navigation/SKILL.md +0 -156
- package/pipeline/skills/figma-common/figma-overlays/SKILL.md +0 -142
- package/pipeline/skills/figma-common/figma-price-integration/SKILL.md +0 -398
- package/pipeline/skills/figma-common/figma-remote-mcp-auth/SKILL.md +0 -104
- package/pipeline/skills/figma-common/figma-review/SKILL.md +0 -400
- package/pipeline/skills/figma-common/figma-setup/SKILL.md +0 -514
- package/pipeline/skills/figma-common/figma-setup/scripts/fetch-mcp-token.py +0 -592
- package/pipeline/skills/figma-common/figma-skip/SKILL.md +0 -129
- package/pipeline/skills/figma-common/figma-ui-patterns/SKILL.md +0 -105
- package/pipeline/skills/figma-common/figma-ui-patterns/patterns/animated-gradient-border.md +0 -116
- package/pipeline/skills/figma-common/figma-utility/SKILL.md +0 -274
- package/pipeline/skills/figma-common/figma-utility/scripts/figma-utility.py +0 -808
- package/pipeline/skills/figma-common/figma-validate/SKILL.md +0 -633
- package/pipeline/skills/figma-common/performance-iteration-commit-all/SKILL.md +0 -716
- package/pipeline/skills/figma-common/performance-review-next/SKILL.md +0 -238
- package/pipeline/skills/figma-common/performance-start/SKILL.md +0 -430
- package/pipeline/skills/figma-common/performance-swiftui/SKILL.md +0 -706
- package/pipeline/skills/figma-common/performance-tour/SKILL.md +0 -423
- package/pipeline/skills/figma-ios/REVIEW_CHECKLIST.md +0 -67
- package/pipeline/skills/figma-ios/figma-component-code-connect/SKILL.md +0 -178
- package/pipeline/skills/figma-ios/figma-component-implement/SKILL.md +0 -184
- package/pipeline/skills/figma-ios/figma-component-test/SKILL.md +0 -219
- package/pipeline/skills/figma-ios/figma-component-wiki/SKILL.md +0 -274
- package/pipeline/skills/figma-ios/figma-to-component/SKILL.md +0 -416
- package/pipeline/skills/figma-ios/figma-to-component/halt-return-protocol.md +0 -57
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-0-init.md +0 -307
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-1-gathering.md +0 -119
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-1.5-existing-discovery.md +0 -174
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2-orchestrator.md +0 -333
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2a-testing-identifiers.md +0 -368
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2b-localization.md +0 -393
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2c-accessibility.md +0 -617
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-2d-analytics.md +0 -352
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3-orchestrator.md +0 -337
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3a-location.md +0 -206
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3b-tokens.md +0 -235
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3c-nested.md +0 -214
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3d-patterns.md +0 -902
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3e-assets.md +0 -156
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3f-utilities.md +0 -175
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3g-property-coverage.md +0 -176
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-3h-variant-config.md +0 -333
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4-orchestrator.md +0 -412
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4a-configuration.md +0 -336
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4b-view.md +0 -705
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4c-documentation.md +0 -332
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4d-preview.md +0 -380
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-4e-modifiers.md +0 -262
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5-orchestrator.md +0 -482
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5a-viewinspector.md +0 -274
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5b-snapshot.md +0 -636
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-5c-unit.md +0 -142
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-6-code-connect.md +0 -547
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7-wiki.md +0 -39
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7a-confluence-generate.md +0 -659
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-7a-wiki-generate.md +0 -580
- package/pipeline/skills/figma-ios/figma-to-component/phases/phase-8-cleanup.md +0 -51
- package/pipeline/skills/figma-ios/figma-to-component/reference/accessibility.md +0 -183
- package/pipeline/skills/figma-ios/figma-to-component/reference/analytics-events.md +0 -64
- package/pipeline/skills/figma-ios/figma-to-component/reference/code-connect.md +0 -531
- package/pipeline/skills/figma-ios/figma-to-component/reference/confluence-api.md +0 -89
- package/pipeline/skills/figma-ios/figma-to-component/reference/confluence-xhtml.md +0 -155
- package/pipeline/skills/figma-ios/figma-to-component/reference/figma-to-swiftui-effects.md +0 -196
- package/pipeline/skills/figma-ios/figma-to-component/reference/halt-return-protocol.md +0 -57
- package/pipeline/skills/figma-ios/figma-to-component/reference/localization-naming.md +0 -89
- package/pipeline/skills/figma-ios/figma-to-component/reference/macros.md +0 -227
- package/pipeline/skills/figma-ios/figma-to-component/reference/missing-tokens.md +0 -157
- package/pipeline/skills/figma-ios/figma-to-component/reference/orchestrator-discipline.md +0 -90
- package/pipeline/skills/figma-ios/figma-to-component/reference/registry.md +0 -116
- package/pipeline/skills/figma-ios/figma-to-component/reference/remote-mcp-script.md +0 -153
- package/pipeline/skills/figma-ios/figma-to-component/reference/rest-api-script.md +0 -130
- package/pipeline/skills/figma-ios/figma-to-component/reference/scripts-inventory.md +0 -218
- package/pipeline/skills/figma-ios/figma-to-component/reference/snapshot-testing.md +0 -188
- package/pipeline/skills/figma-ios/figma-to-component/reference/subcomponent-graph.md +0 -93
- package/pipeline/skills/figma-ios/figma-to-component/reference/testing-identifiers-naming.md +0 -98
- package/pipeline/skills/figma-ios/figma-to-component/reference/tools.md +0 -261
- package/pipeline/skills/figma-ios/figma-to-component/reference/viewinspector.md +0 -147
- package/pipeline/skills/figma-ios/figma-to-component/reference/wiki-to-confluence-mapping.md +0 -182
- package/pipeline/skills/figma-ios/figma-to-component/scripts/apply-author-login-map.py +0 -185
- package/pipeline/skills/figma-ios/figma-to-component/scripts/backfill-status.py +0 -609
- package/pipeline/skills/figma-ios/figma-to-component/scripts/build-author-registry.py +0 -332
- package/pipeline/skills/figma-ios/figma-to-component/scripts/bulk-sync-issues.py +0 -261
- package/pipeline/skills/figma-ios/figma-to-component/scripts/code-connect-data-gather.py +0 -184
- package/pipeline/skills/figma-ios/figma-to-component/scripts/code-connect-publish.sh +0 -188
- package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-component-status-upload.py +0 -768
- package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-component-status.py +0 -191
- package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-data-gather.py +0 -420
- package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-page-ids.example.json +0 -9
- package/pipeline/skills/figma-ios/figma-to-component/scripts/confluence-publish.py +0 -336
- package/pipeline/skills/figma-ios/figma-to-component/scripts/figma-subcomponent-graph.py +0 -391
- package/pipeline/skills/figma-ios/figma-to-component/scripts/figma-update.py +0 -292
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/__init__.py +0 -1
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/issue_sync_propagate.py +0 -93
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/registry_writer.py +0 -299
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_backfill_status.py +0 -343
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_figma_update.py +0 -206
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_figma_update_http.py +0 -149
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_phase_clis.py +0 -281
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_registry_writer.py +0 -332
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_skill_figma_issue.py +0 -176
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_skill_figma_review.py +0 -98
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_update_issue.py +0 -298
- package/pipeline/skills/figma-ios/figma-to-component/scripts/lib/test_update_issue_gh.py +0 -195
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase1-gather.py +0 -1298
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase2-finalize.py +0 -228
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase3-scripts.py +0 -1089
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase4-finalize.py +0 -141
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase5-finalize.py +0 -106
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase6-finalize.py +0 -162
- package/pipeline/skills/figma-ios/figma-to-component/scripts/phase7-finalize.py +0 -105
- package/pipeline/skills/figma-ios/figma-to-component/scripts/register-icons-codeconnect.py +0 -179
- package/pipeline/skills/figma-ios/figma-to-component/scripts/remote-mcp-fetch.py +0 -260
- package/pipeline/skills/figma-ios/figma-to-component/scripts/resolve-author-logins.py +0 -260
- package/pipeline/skills/figma-ios/figma-to-component/scripts/run-uicomponents-tests.sh +0 -86
- package/pipeline/skills/figma-ios/figma-to-component/scripts/sidebar-generator.py +0 -321
- package/pipeline/skills/figma-ios/figma-to-component/scripts/update-issue-from-registry.py +0 -1470
- package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase4.sh +0 -176
- package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase6.sh +0 -147
- package/pipeline/skills/figma-ios/figma-to-component/scripts/validate-phase7a.py +0 -629
- /package/pipeline/commands/multi-agent/{delete.md → delete/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{diff-explain.md → diff-explain/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{garbage-collect.md → garbage-collect/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{issue.md → issue/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{jira.md → jira/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{kill.md → kill/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{language.md → language/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{log.md → log/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{prune-logs.md → prune-logs/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{purge.md → purge/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{scan.md → scan/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{search.md → search/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{stack.md → stack/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{status.md → status/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{test.md → test/SKILL.md} +0 -0
- /package/pipeline/commands/multi-agent/{update.md → update/SKILL.md} +0 -0
- /package/pipeline/{commands/multi-agent → multi-agent-refs}/_input-parser.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/analysis-template.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/audit-guide.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/backend-guide.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/confluence.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/issue-comment.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/pr-review-actions.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/channels/pr.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/conventions-defaults.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/autopilot-circuit-breaker.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/dev-critic.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/external-context-injection.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/model-fallback.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/plan-todos.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/prior-fix-detection.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/repo-map.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/features/shadow-git.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/frontend-guide.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/knowledge.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/multi-repo-integration-build.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/picker-contract.md +0 -0
- /package/pipeline/{commands/multi-agent/refs → multi-agent-refs}/rules.md +0 -0
|
@@ -1,592 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Figma Remote MCP Token Fetcher
|
|
4
|
-
|
|
5
|
-
Retrieves or refreshes a Figma MCP OAuth token. Three modes:
|
|
6
|
-
|
|
7
|
-
MODE 1 - Extract (default):
|
|
8
|
-
Reads existing token from Keychain or Claude Code credentials.
|
|
9
|
-
python3 fetch-mcp-token.py
|
|
10
|
-
|
|
11
|
-
MODE 2 - OAuth (--oauth):
|
|
12
|
-
Runs full OAuth flow. Opens browser for Figma consent, runs local
|
|
13
|
-
callback server, exchanges code for token. Works from ANY terminal.
|
|
14
|
-
python3 fetch-mcp-token.py --oauth --client-id X --client-secret Y --save-keychain
|
|
15
|
-
|
|
16
|
-
Credentials are extracted from Claude Code keychain if available,
|
|
17
|
-
or must be provided via --client-id and --client-secret.
|
|
18
|
-
|
|
19
|
-
COMMON FLAGS:
|
|
20
|
-
--save-keychain Save token to macOS Keychain as FIGMA_MCP_TOKEN
|
|
21
|
-
--verify Verify token against Figma MCP server
|
|
22
|
-
--quiet Only print the token to stdout
|
|
23
|
-
--port PORT Local callback server port (default: 8923)
|
|
24
|
-
--client-id OAuth client ID (required for --oauth if no Claude Code)
|
|
25
|
-
--client-secret OAuth client secret (required for --oauth if no Claude Code)
|
|
26
|
-
--show-clients Show OAuth clients from Claude Code credentials
|
|
27
|
-
"""
|
|
28
|
-
|
|
29
|
-
import argparse
|
|
30
|
-
import base64
|
|
31
|
-
import hashlib
|
|
32
|
-
import http.server
|
|
33
|
-
import json
|
|
34
|
-
import os
|
|
35
|
-
import re
|
|
36
|
-
import secrets
|
|
37
|
-
import subprocess
|
|
38
|
-
import sys
|
|
39
|
-
import threading
|
|
40
|
-
import time
|
|
41
|
-
import urllib.parse
|
|
42
|
-
import urllib.request
|
|
43
|
-
|
|
44
|
-
# ─── Configuration ──────────────────────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
KEYCHAIN_SERVICE = "FIGMA_MCP_TOKEN"
|
|
47
|
-
KEYCHAIN_REFRESH_SERVICE = "FIGMA_MCP_REFRESH_TOKEN"
|
|
48
|
-
KEYCHAIN_CLIENT_ID_SERVICE = "FIGMA_MCP_CLIENT_ID"
|
|
49
|
-
KEYCHAIN_CLIENT_SECRET_SERVICE = "FIGMA_MCP_CLIENT_SECRET"
|
|
50
|
-
CLAUDE_CREDENTIALS_SERVICE = "Claude Code-credentials"
|
|
51
|
-
MCP_SERVER_URL = "https://mcp.figma.com/mcp"
|
|
52
|
-
AUTH_ENDPOINT = "https://www.figma.com/oauth/mcp"
|
|
53
|
-
TOKEN_ENDPOINT = "https://api.figma.com/v1/oauth/token"
|
|
54
|
-
REFRESH_ENDPOINT = "https://api.figma.com/v1/oauth/refresh"
|
|
55
|
-
DEFAULT_PORT = 8923
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
# ─── Helpers ────────────────────────────────────────────────────────────────
|
|
59
|
-
|
|
60
|
-
def log(msg):
|
|
61
|
-
print(f"[fetch-mcp-token] {msg}", file=sys.stderr)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def get_claude_credentials_raw():
|
|
65
|
-
"""Read raw Claude Code credentials from Keychain."""
|
|
66
|
-
try:
|
|
67
|
-
return subprocess.check_output(
|
|
68
|
-
["security", "find-generic-password", "-s", CLAUDE_CREDENTIALS_SERVICE,
|
|
69
|
-
"-a", os.environ["USER"], "-w"],
|
|
70
|
-
stderr=subprocess.DEVNULL
|
|
71
|
-
).decode()
|
|
72
|
-
except (subprocess.CalledProcessError, KeyError):
|
|
73
|
-
return None
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
# ─── Token Sources ──────────────────────────────────────────────────────────
|
|
77
|
-
|
|
78
|
-
def get_from_dedicated_keychain():
|
|
79
|
-
"""Source 1: Dedicated FIGMA_MCP_TOKEN keychain entry."""
|
|
80
|
-
try:
|
|
81
|
-
token = subprocess.check_output(
|
|
82
|
-
["security", "find-generic-password", "-a", os.environ["USER"],
|
|
83
|
-
"-s", KEYCHAIN_SERVICE, "-w"],
|
|
84
|
-
stderr=subprocess.DEVNULL
|
|
85
|
-
).decode().strip()
|
|
86
|
-
if token and token.startswith("figu_"):
|
|
87
|
-
return token, "keychain:FIGMA_MCP_TOKEN"
|
|
88
|
-
except (subprocess.CalledProcessError, KeyError):
|
|
89
|
-
pass
|
|
90
|
-
return None, None
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def get_from_claude_credentials():
|
|
94
|
-
"""Source 2: Extract accessToken from Claude Code's credential store."""
|
|
95
|
-
raw = get_claude_credentials_raw()
|
|
96
|
-
if not raw:
|
|
97
|
-
return None, None
|
|
98
|
-
match = re.search(
|
|
99
|
-
r'"figma-remote\|[^"]+":\{[^}]*"accessToken":"([^"]+)"', raw
|
|
100
|
-
)
|
|
101
|
-
if match:
|
|
102
|
-
token = match.group(1)
|
|
103
|
-
if token.startswith("figu_"):
|
|
104
|
-
return token, "keychain:Claude Code-credentials"
|
|
105
|
-
return None, None
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def get_expiry_from_claude_credentials():
|
|
109
|
-
"""Extract expiresAt from Claude Code credentials."""
|
|
110
|
-
raw = get_claude_credentials_raw()
|
|
111
|
-
if not raw:
|
|
112
|
-
return None
|
|
113
|
-
match = re.search(
|
|
114
|
-
r'"figma-remote\|[^"]+":\{[^}]*"expiresAt":(\d+)', raw
|
|
115
|
-
)
|
|
116
|
-
if match:
|
|
117
|
-
import datetime
|
|
118
|
-
ts = int(match.group(1)) / 1000
|
|
119
|
-
return datetime.datetime.fromtimestamp(ts).isoformat()
|
|
120
|
-
return None
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def get_client_credentials_from_claude():
|
|
124
|
-
"""Extract clientId + clientSecret from Claude Code credentials."""
|
|
125
|
-
raw = get_claude_credentials_raw()
|
|
126
|
-
if not raw:
|
|
127
|
-
return []
|
|
128
|
-
clients = []
|
|
129
|
-
# Find all clientRegistration blocks with their parent server context
|
|
130
|
-
for m in re.finditer(r'"clientId":"([^"]+)"', raw):
|
|
131
|
-
cid = m.group(1)
|
|
132
|
-
# Look for clientSecret right after
|
|
133
|
-
after = raw[m.end():m.end() + 200]
|
|
134
|
-
sec_match = re.search(r'"clientSecret":"([^"]+)"', after)
|
|
135
|
-
# Look backwards for serverName
|
|
136
|
-
before = raw[max(0, m.start() - 500):m.start()]
|
|
137
|
-
srv_match = re.search(r'"serverName":"([^"]+)"', before)
|
|
138
|
-
url_match = re.search(r'"serverUrl":"([^"]+)"', before)
|
|
139
|
-
clients.append({
|
|
140
|
-
"clientId": cid,
|
|
141
|
-
"clientSecret": sec_match.group(1) if sec_match else None,
|
|
142
|
-
"serverName": srv_match.group(1) if srv_match else "unknown",
|
|
143
|
-
"serverUrl": url_match.group(1) if url_match else "unknown",
|
|
144
|
-
})
|
|
145
|
-
return clients
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
# ─── Keychain & Verify ──────────────────────────────────────────────────────
|
|
149
|
-
|
|
150
|
-
def save_to_keychain(token, service=KEYCHAIN_SERVICE):
|
|
151
|
-
"""Save token to macOS Keychain."""
|
|
152
|
-
user = os.environ.get("USER", "")
|
|
153
|
-
subprocess.run(
|
|
154
|
-
["security", "delete-generic-password", "-a", user, "-s", service],
|
|
155
|
-
capture_output=True
|
|
156
|
-
)
|
|
157
|
-
result = subprocess.run(
|
|
158
|
-
["security", "add-generic-password", "-a", user, "-s", service, "-w", token],
|
|
159
|
-
capture_output=True
|
|
160
|
-
)
|
|
161
|
-
return result.returncode == 0
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def get_refresh_token_from_keychain():
|
|
165
|
-
"""Read refresh token from macOS Keychain."""
|
|
166
|
-
try:
|
|
167
|
-
return subprocess.check_output(
|
|
168
|
-
["security", "find-generic-password", "-a", os.environ["USER"],
|
|
169
|
-
"-s", KEYCHAIN_REFRESH_SERVICE, "-w"],
|
|
170
|
-
stderr=subprocess.DEVNULL
|
|
171
|
-
).decode().strip() or None
|
|
172
|
-
except (subprocess.CalledProcessError, KeyError):
|
|
173
|
-
return None
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
def get_client_credentials_from_keychain():
|
|
177
|
-
"""Read client_id and client_secret from macOS Keychain."""
|
|
178
|
-
try:
|
|
179
|
-
cid = subprocess.check_output(
|
|
180
|
-
["security", "find-generic-password", "-a", os.environ["USER"],
|
|
181
|
-
"-s", KEYCHAIN_CLIENT_ID_SERVICE, "-w"],
|
|
182
|
-
stderr=subprocess.DEVNULL
|
|
183
|
-
).decode().strip()
|
|
184
|
-
csec = subprocess.check_output(
|
|
185
|
-
["security", "find-generic-password", "-a", os.environ["USER"],
|
|
186
|
-
"-s", KEYCHAIN_CLIENT_SECRET_SERVICE, "-w"],
|
|
187
|
-
stderr=subprocess.DEVNULL
|
|
188
|
-
).decode().strip()
|
|
189
|
-
if cid and csec:
|
|
190
|
-
return cid, csec
|
|
191
|
-
except (subprocess.CalledProcessError, KeyError):
|
|
192
|
-
pass
|
|
193
|
-
return None, None
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def refresh_access_token(refresh_token, client_id, client_secret):
|
|
197
|
-
"""Use refresh_token to obtain a new access_token from Figma."""
|
|
198
|
-
auth_str = base64.b64encode(f"{client_id}:{client_secret}".encode()).decode()
|
|
199
|
-
body = urllib.parse.urlencode({"refresh_token": refresh_token}).encode()
|
|
200
|
-
req = urllib.request.Request(
|
|
201
|
-
REFRESH_ENDPOINT,
|
|
202
|
-
data=body,
|
|
203
|
-
headers={
|
|
204
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
205
|
-
"Authorization": f"Basic {auth_str}",
|
|
206
|
-
},
|
|
207
|
-
method="POST",
|
|
208
|
-
)
|
|
209
|
-
try:
|
|
210
|
-
with urllib.request.urlopen(req) as resp:
|
|
211
|
-
result = json.loads(resp.read().decode())
|
|
212
|
-
except urllib.error.HTTPError as e:
|
|
213
|
-
body_text = e.read().decode()
|
|
214
|
-
log(f"Refresh failed (HTTP {e.code}): {body_text}")
|
|
215
|
-
return None
|
|
216
|
-
|
|
217
|
-
new_token = result.get("access_token")
|
|
218
|
-
expires_in = result.get("expires_in", "unknown")
|
|
219
|
-
if new_token:
|
|
220
|
-
log(f"Token refreshed: {new_token[:10]}...{new_token[-6:]}")
|
|
221
|
-
log(f"Expires in: {expires_in}s")
|
|
222
|
-
else:
|
|
223
|
-
log(f"No access_token in refresh response: {json.dumps(result, indent=2)}")
|
|
224
|
-
return new_token
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
def verify_token(token):
|
|
228
|
-
"""Verify token against Figma MCP server."""
|
|
229
|
-
try:
|
|
230
|
-
req = urllib.request.Request(
|
|
231
|
-
MCP_SERVER_URL,
|
|
232
|
-
data=json.dumps({
|
|
233
|
-
"jsonrpc": "2.0",
|
|
234
|
-
"method": "initialize",
|
|
235
|
-
"params": {
|
|
236
|
-
"capabilities": {},
|
|
237
|
-
"clientInfo": {"name": "token-verify", "version": "1.0.0"},
|
|
238
|
-
"protocolVersion": "2025-03-26"
|
|
239
|
-
},
|
|
240
|
-
"id": 1
|
|
241
|
-
}).encode(),
|
|
242
|
-
headers={
|
|
243
|
-
"Content-Type": "application/json",
|
|
244
|
-
"Accept": "application/json, text/event-stream",
|
|
245
|
-
"Authorization": f"Bearer {token}"
|
|
246
|
-
},
|
|
247
|
-
method="POST"
|
|
248
|
-
)
|
|
249
|
-
with urllib.request.urlopen(req) as resp:
|
|
250
|
-
return resp.status == 200
|
|
251
|
-
except urllib.error.HTTPError as e:
|
|
252
|
-
if e.code == 401:
|
|
253
|
-
return False
|
|
254
|
-
raise
|
|
255
|
-
except Exception:
|
|
256
|
-
return False
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
# ─── OAuth Flow ─────────────────────────────────────────────────────────────
|
|
260
|
-
|
|
261
|
-
class OAuthCallbackHandler(http.server.BaseHTTPRequestHandler):
|
|
262
|
-
"""HTTP handler that captures the OAuth callback."""
|
|
263
|
-
auth_code = None
|
|
264
|
-
auth_state = None
|
|
265
|
-
error = None
|
|
266
|
-
|
|
267
|
-
def do_GET(self):
|
|
268
|
-
parsed = urllib.parse.urlparse(self.path)
|
|
269
|
-
params = urllib.parse.parse_qs(parsed.query)
|
|
270
|
-
if parsed.path == "/callback":
|
|
271
|
-
if "error" in params:
|
|
272
|
-
OAuthCallbackHandler.error = params["error"][0]
|
|
273
|
-
self._respond(200, "<h2>Authorization failed.</h2><p>You can close this tab.</p>")
|
|
274
|
-
elif "code" in params:
|
|
275
|
-
OAuthCallbackHandler.auth_code = params["code"][0]
|
|
276
|
-
OAuthCallbackHandler.auth_state = params.get("state", [None])[0]
|
|
277
|
-
self._respond(200, "<h2>Authorization successful!</h2><p>Return to your terminal.</p>")
|
|
278
|
-
else:
|
|
279
|
-
self._respond(400, "Missing code parameter")
|
|
280
|
-
else:
|
|
281
|
-
self._respond(404, "Not found")
|
|
282
|
-
|
|
283
|
-
def _respond(self, code, body):
|
|
284
|
-
self.send_response(code)
|
|
285
|
-
self.send_header("Content-Type", "text/html")
|
|
286
|
-
self.end_headers()
|
|
287
|
-
self.wfile.write(f"<html><body>{body}</body></html>".encode())
|
|
288
|
-
|
|
289
|
-
def log_message(self, format, *args):
|
|
290
|
-
pass
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
def generate_pkce():
|
|
294
|
-
"""Generate PKCE code_verifier and code_challenge (S256)."""
|
|
295
|
-
verifier = secrets.token_urlsafe(64)[:128]
|
|
296
|
-
digest = hashlib.sha256(verifier.encode("ascii")).digest()
|
|
297
|
-
challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
|
|
298
|
-
return verifier, challenge
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
def run_oauth_flow(client_id, client_secret, port):
|
|
302
|
-
"""Run the full OAuth 2.0 + PKCE flow and return access_token."""
|
|
303
|
-
redirect_uri = f"http://localhost:{port}/callback"
|
|
304
|
-
|
|
305
|
-
# Reset handler state
|
|
306
|
-
OAuthCallbackHandler.auth_code = None
|
|
307
|
-
OAuthCallbackHandler.auth_state = None
|
|
308
|
-
OAuthCallbackHandler.error = None
|
|
309
|
-
|
|
310
|
-
# Generate PKCE
|
|
311
|
-
code_verifier, code_challenge = generate_pkce()
|
|
312
|
-
state = secrets.token_urlsafe(32)
|
|
313
|
-
|
|
314
|
-
# Start callback server
|
|
315
|
-
log(f"Starting callback server on port {port}...")
|
|
316
|
-
server = http.server.HTTPServer(("localhost", port), OAuthCallbackHandler)
|
|
317
|
-
server_thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
318
|
-
server_thread.start()
|
|
319
|
-
|
|
320
|
-
# Build authorization URL
|
|
321
|
-
auth_params = urllib.parse.urlencode({
|
|
322
|
-
"client_id": client_id,
|
|
323
|
-
"redirect_uri": redirect_uri,
|
|
324
|
-
"response_type": "code",
|
|
325
|
-
"scope": "mcp:connect",
|
|
326
|
-
"state": state,
|
|
327
|
-
"code_challenge": code_challenge,
|
|
328
|
-
"code_challenge_method": "S256",
|
|
329
|
-
})
|
|
330
|
-
auth_url = f"{AUTH_ENDPOINT}?{auth_params}"
|
|
331
|
-
|
|
332
|
-
log("Opening browser for Figma authorization...")
|
|
333
|
-
log(f" If browser doesn't open, visit:")
|
|
334
|
-
log(f" {auth_url}")
|
|
335
|
-
|
|
336
|
-
import webbrowser
|
|
337
|
-
webbrowser.open(auth_url)
|
|
338
|
-
|
|
339
|
-
# Wait for callback
|
|
340
|
-
log("Waiting for authorization (120s timeout)...")
|
|
341
|
-
timeout = 120
|
|
342
|
-
start = time.time()
|
|
343
|
-
while OAuthCallbackHandler.auth_code is None and OAuthCallbackHandler.error is None:
|
|
344
|
-
if time.time() - start > timeout:
|
|
345
|
-
server.shutdown()
|
|
346
|
-
log("Timeout waiting for authorization.")
|
|
347
|
-
return None
|
|
348
|
-
time.sleep(0.5)
|
|
349
|
-
|
|
350
|
-
server.shutdown()
|
|
351
|
-
|
|
352
|
-
if OAuthCallbackHandler.error:
|
|
353
|
-
log(f"Authorization error: {OAuthCallbackHandler.error}")
|
|
354
|
-
return None
|
|
355
|
-
|
|
356
|
-
if OAuthCallbackHandler.auth_state != state:
|
|
357
|
-
log("State mismatch - possible CSRF.")
|
|
358
|
-
return None
|
|
359
|
-
|
|
360
|
-
auth_code = OAuthCallbackHandler.auth_code
|
|
361
|
-
log("Authorization code received. Exchanging for token...")
|
|
362
|
-
|
|
363
|
-
# Exchange code for token
|
|
364
|
-
token_data = urllib.parse.urlencode({
|
|
365
|
-
"grant_type": "authorization_code",
|
|
366
|
-
"code": auth_code,
|
|
367
|
-
"redirect_uri": redirect_uri,
|
|
368
|
-
"client_id": client_id,
|
|
369
|
-
"client_secret": client_secret,
|
|
370
|
-
"code_verifier": code_verifier,
|
|
371
|
-
}).encode()
|
|
372
|
-
|
|
373
|
-
req = urllib.request.Request(
|
|
374
|
-
TOKEN_ENDPOINT,
|
|
375
|
-
data=token_data,
|
|
376
|
-
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
377
|
-
method="POST",
|
|
378
|
-
)
|
|
379
|
-
|
|
380
|
-
try:
|
|
381
|
-
with urllib.request.urlopen(req) as resp:
|
|
382
|
-
result = json.loads(resp.read().decode())
|
|
383
|
-
except urllib.error.HTTPError as e:
|
|
384
|
-
body = e.read().decode()
|
|
385
|
-
log(f"Token exchange failed (HTTP {e.code}): {body}")
|
|
386
|
-
return None
|
|
387
|
-
|
|
388
|
-
access_token = result.get("access_token")
|
|
389
|
-
refresh_token = result.get("refresh_token")
|
|
390
|
-
expires_in = result.get("expires_in", "unknown")
|
|
391
|
-
|
|
392
|
-
if access_token:
|
|
393
|
-
log(f"Token obtained: {access_token[:10]}...{access_token[-6:]}")
|
|
394
|
-
log(f"Expires in: {expires_in}s")
|
|
395
|
-
if refresh_token:
|
|
396
|
-
log(f"Refresh token obtained: {refresh_token[:10]}...{refresh_token[-6:]}")
|
|
397
|
-
else:
|
|
398
|
-
log(f"No access_token in response: {json.dumps(result, indent=2)}")
|
|
399
|
-
|
|
400
|
-
return access_token, refresh_token
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
# ─── Main ───────────────────────────────────────────────────────────────────
|
|
404
|
-
|
|
405
|
-
def main():
|
|
406
|
-
parser = argparse.ArgumentParser(
|
|
407
|
-
description="Retrieve or refresh Figma MCP OAuth token"
|
|
408
|
-
)
|
|
409
|
-
parser.add_argument("--oauth", action="store_true",
|
|
410
|
-
help="Run full OAuth flow (opens browser)")
|
|
411
|
-
parser.add_argument("--client-id",
|
|
412
|
-
help="OAuth client ID (extracted from Claude Code or provided manually)")
|
|
413
|
-
parser.add_argument("--client-secret",
|
|
414
|
-
help="OAuth client secret")
|
|
415
|
-
parser.add_argument("--show-clients", action="store_true",
|
|
416
|
-
help="Show registered OAuth clients from Claude Code credentials")
|
|
417
|
-
parser.add_argument("--save-keychain", action="store_true",
|
|
418
|
-
help=f"Save token to macOS Keychain as {KEYCHAIN_SERVICE}")
|
|
419
|
-
parser.add_argument("--verify", action="store_true",
|
|
420
|
-
help="Verify token against Figma MCP server")
|
|
421
|
-
parser.add_argument("--quiet", action="store_true",
|
|
422
|
-
help="Only print the token to stdout")
|
|
423
|
-
parser.add_argument("--port", type=int, default=DEFAULT_PORT,
|
|
424
|
-
help=f"Callback server port (default: {DEFAULT_PORT})")
|
|
425
|
-
args = parser.parse_args()
|
|
426
|
-
|
|
427
|
-
# ── Show clients mode ───────────────────────────────────────────────
|
|
428
|
-
if args.show_clients:
|
|
429
|
-
clients = get_client_credentials_from_claude()
|
|
430
|
-
if not clients:
|
|
431
|
-
log("No OAuth clients found in Claude Code credentials.")
|
|
432
|
-
sys.exit(1)
|
|
433
|
-
for i, c in enumerate(clients):
|
|
434
|
-
print(f"Client {i+1}:")
|
|
435
|
-
print(f" Server: {c['serverName']}")
|
|
436
|
-
print(f" URL: {c['serverUrl']}")
|
|
437
|
-
print(f" clientId: {c['clientId']}")
|
|
438
|
-
print(f" clientSecret: {c['clientSecret']}")
|
|
439
|
-
print()
|
|
440
|
-
log(f"Found {len(clients)} registered client(s).")
|
|
441
|
-
log("Use with: --oauth --client-id <id> --client-secret <secret>")
|
|
442
|
-
sys.exit(0)
|
|
443
|
-
|
|
444
|
-
# ── OAuth mode ──────────────────────────────────────────────────────
|
|
445
|
-
if args.oauth:
|
|
446
|
-
client_id = args.client_id
|
|
447
|
-
client_secret = args.client_secret
|
|
448
|
-
|
|
449
|
-
# Auto-extract from Claude Code credentials if not provided
|
|
450
|
-
if not client_id or not client_secret:
|
|
451
|
-
clients = get_client_credentials_from_claude()
|
|
452
|
-
figma_clients = [c for c in clients if "figma" in c.get("serverUrl", "")]
|
|
453
|
-
if figma_clients:
|
|
454
|
-
client_id = client_id or figma_clients[0]["clientId"]
|
|
455
|
-
client_secret = client_secret or figma_clients[0]["clientSecret"]
|
|
456
|
-
else:
|
|
457
|
-
log("No client credentials provided and none found in Claude Code.")
|
|
458
|
-
log("Use: --client-id <id> --client-secret <secret>")
|
|
459
|
-
sys.exit(1)
|
|
460
|
-
|
|
461
|
-
if not args.quiet:
|
|
462
|
-
log(f"Using client credentials ({client_id})")
|
|
463
|
-
|
|
464
|
-
token, refresh_token = run_oauth_flow(client_id, client_secret, args.port)
|
|
465
|
-
if not token:
|
|
466
|
-
sys.exit(1)
|
|
467
|
-
|
|
468
|
-
if args.verify:
|
|
469
|
-
if not args.quiet:
|
|
470
|
-
log("Verifying token...")
|
|
471
|
-
if verify_token(token):
|
|
472
|
-
if not args.quiet:
|
|
473
|
-
log("Token is VALID.")
|
|
474
|
-
else:
|
|
475
|
-
if not args.quiet:
|
|
476
|
-
log("Token is INVALID.")
|
|
477
|
-
sys.exit(2)
|
|
478
|
-
|
|
479
|
-
if args.save_keychain:
|
|
480
|
-
if save_to_keychain(token):
|
|
481
|
-
if not args.quiet:
|
|
482
|
-
log(f"Saved to Keychain as '{KEYCHAIN_SERVICE}'.")
|
|
483
|
-
else:
|
|
484
|
-
if not args.quiet:
|
|
485
|
-
log("Failed to save to Keychain.")
|
|
486
|
-
sys.exit(3)
|
|
487
|
-
# Save refresh_token alongside access_token
|
|
488
|
-
if refresh_token:
|
|
489
|
-
if save_to_keychain(refresh_token, service=KEYCHAIN_REFRESH_SERVICE):
|
|
490
|
-
if not args.quiet:
|
|
491
|
-
log(f"Saved refresh token to Keychain as '{KEYCHAIN_REFRESH_SERVICE}'.")
|
|
492
|
-
else:
|
|
493
|
-
if not args.quiet:
|
|
494
|
-
log("Failed to save refresh token to Keychain.")
|
|
495
|
-
# Save client credentials for future auto-refresh
|
|
496
|
-
save_to_keychain(client_id, service=KEYCHAIN_CLIENT_ID_SERVICE)
|
|
497
|
-
save_to_keychain(client_secret, service=KEYCHAIN_CLIENT_SECRET_SERVICE)
|
|
498
|
-
if not args.quiet:
|
|
499
|
-
log("Saved client credentials to Keychain for future auto-refresh.")
|
|
500
|
-
|
|
501
|
-
print(token)
|
|
502
|
-
sys.exit(0)
|
|
503
|
-
|
|
504
|
-
# ── Extract mode (default) ──────────────────────────────────────────
|
|
505
|
-
token, source = get_from_dedicated_keychain()
|
|
506
|
-
if not token:
|
|
507
|
-
token, source = get_from_claude_credentials()
|
|
508
|
-
|
|
509
|
-
if not token:
|
|
510
|
-
if not args.quiet:
|
|
511
|
-
log("No Figma MCP token found.")
|
|
512
|
-
log("")
|
|
513
|
-
log("Options:")
|
|
514
|
-
log(" 1. Run with --oauth to get a new token via browser")
|
|
515
|
-
log(" 2. Use an MCP-enabled session to trigger OAuth first")
|
|
516
|
-
log(" 3. Get credentials from a colleague: --show-clients")
|
|
517
|
-
sys.exit(1)
|
|
518
|
-
|
|
519
|
-
if not args.quiet:
|
|
520
|
-
log(f"Token from: {source}")
|
|
521
|
-
log(f"Token: {token[:10]}...{token[-6:]}")
|
|
522
|
-
expiry = get_expiry_from_claude_credentials()
|
|
523
|
-
if expiry:
|
|
524
|
-
log(f"Expires: {expiry}")
|
|
525
|
-
|
|
526
|
-
if args.verify:
|
|
527
|
-
if not args.quiet:
|
|
528
|
-
log("Verifying...")
|
|
529
|
-
if verify_token(token):
|
|
530
|
-
if not args.quiet:
|
|
531
|
-
log("Token is VALID.")
|
|
532
|
-
else:
|
|
533
|
-
# Token invalid - try auto-refresh before giving up
|
|
534
|
-
if not args.quiet:
|
|
535
|
-
log("Token is INVALID or EXPIRED. Attempting auto-refresh...")
|
|
536
|
-
refresh_tok = get_refresh_token_from_keychain()
|
|
537
|
-
refreshed = False
|
|
538
|
-
if refresh_tok:
|
|
539
|
-
# Need client credentials for refresh
|
|
540
|
-
cid = args.client_id
|
|
541
|
-
csec = args.client_secret
|
|
542
|
-
# Try Keychain first (saved during --oauth --save-keychain)
|
|
543
|
-
if not cid or not csec:
|
|
544
|
-
kc_cid, kc_csec = get_client_credentials_from_keychain()
|
|
545
|
-
cid = cid or kc_cid
|
|
546
|
-
csec = csec or kc_csec
|
|
547
|
-
# Fall back to Claude Code credentials
|
|
548
|
-
if not cid or not csec:
|
|
549
|
-
clients = get_client_credentials_from_claude()
|
|
550
|
-
figma_clients = [c for c in clients if "figma" in c.get("serverUrl", "")]
|
|
551
|
-
if figma_clients:
|
|
552
|
-
cid = cid or figma_clients[0]["clientId"]
|
|
553
|
-
csec = csec or figma_clients[0]["clientSecret"]
|
|
554
|
-
if cid and csec:
|
|
555
|
-
new_token = refresh_access_token(refresh_tok, cid, csec)
|
|
556
|
-
if new_token and verify_token(new_token):
|
|
557
|
-
token = new_token
|
|
558
|
-
refreshed = True
|
|
559
|
-
if not args.quiet:
|
|
560
|
-
log("Token refreshed successfully.")
|
|
561
|
-
if args.save_keychain or source == f"keychain:{KEYCHAIN_SERVICE}":
|
|
562
|
-
save_to_keychain(token)
|
|
563
|
-
if not args.quiet:
|
|
564
|
-
log(f"Updated Keychain '{KEYCHAIN_SERVICE}'.")
|
|
565
|
-
else:
|
|
566
|
-
if not args.quiet:
|
|
567
|
-
log("Refresh failed - token or credentials expired.")
|
|
568
|
-
else:
|
|
569
|
-
if not args.quiet:
|
|
570
|
-
log("No client credentials available for refresh.")
|
|
571
|
-
else:
|
|
572
|
-
if not args.quiet:
|
|
573
|
-
log("No refresh token in Keychain.")
|
|
574
|
-
if not refreshed:
|
|
575
|
-
if not args.quiet:
|
|
576
|
-
log("Try --oauth to get a new token via browser.")
|
|
577
|
-
sys.exit(2)
|
|
578
|
-
|
|
579
|
-
if args.save_keychain and source != f"keychain:{KEYCHAIN_SERVICE}":
|
|
580
|
-
if save_to_keychain(token):
|
|
581
|
-
if not args.quiet:
|
|
582
|
-
log(f"Saved to Keychain as '{KEYCHAIN_SERVICE}'.")
|
|
583
|
-
else:
|
|
584
|
-
if not args.quiet:
|
|
585
|
-
log("Failed to save to Keychain.")
|
|
586
|
-
sys.exit(3)
|
|
587
|
-
|
|
588
|
-
print(token)
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
if __name__ == "__main__":
|
|
592
|
-
main()
|