@sun-asterisk/sungen 2.5.0 → 2.5.2

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.
Files changed (131) hide show
  1. package/README.md +88 -7
  2. package/dist/cli/commands/add.d.ts.map +1 -1
  3. package/dist/cli/commands/add.js +109 -9
  4. package/dist/cli/commands/add.js.map +1 -1
  5. package/dist/cli/commands/figma.d.ts +11 -0
  6. package/dist/cli/commands/figma.d.ts.map +1 -0
  7. package/dist/cli/commands/figma.js +178 -0
  8. package/dist/cli/commands/figma.js.map +1 -0
  9. package/dist/cli/index.js +4 -2
  10. package/dist/cli/index.js.map +1 -1
  11. package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
  12. package/dist/orchestrator/ai-rules-updater.js +2 -0
  13. package/dist/orchestrator/ai-rules-updater.js.map +1 -1
  14. package/dist/orchestrator/figma/figma-scaffolder-helpers.d.ts +33 -0
  15. package/dist/orchestrator/figma/figma-scaffolder-helpers.d.ts.map +1 -0
  16. package/dist/orchestrator/figma/figma-scaffolder-helpers.js +135 -0
  17. package/dist/orchestrator/figma/figma-scaffolder-helpers.js.map +1 -0
  18. package/dist/orchestrator/figma/figma-scaffolder-types.d.ts +25 -0
  19. package/dist/orchestrator/figma/figma-scaffolder-types.d.ts.map +1 -0
  20. package/dist/orchestrator/figma/figma-scaffolder-types.js +7 -0
  21. package/dist/orchestrator/figma/figma-scaffolder-types.js.map +1 -0
  22. package/dist/orchestrator/figma/figma-scaffolder.d.ts +23 -0
  23. package/dist/orchestrator/figma/figma-scaffolder.d.ts.map +1 -0
  24. package/dist/orchestrator/figma/figma-scaffolder.js +212 -0
  25. package/dist/orchestrator/figma/figma-scaffolder.js.map +1 -0
  26. package/dist/orchestrator/figma/node-path-collapser.d.ts +16 -0
  27. package/dist/orchestrator/figma/node-path-collapser.d.ts.map +1 -0
  28. package/dist/orchestrator/figma/node-path-collapser.js +37 -0
  29. package/dist/orchestrator/figma/node-path-collapser.js.map +1 -0
  30. package/dist/orchestrator/figma/spec-figma-renderer.d.ts +44 -0
  31. package/dist/orchestrator/figma/spec-figma-renderer.d.ts.map +1 -0
  32. package/dist/orchestrator/figma/spec-figma-renderer.js +45 -0
  33. package/dist/orchestrator/figma/spec-figma-renderer.js.map +1 -0
  34. package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts +23 -0
  35. package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts.map +1 -0
  36. package/dist/orchestrator/figma/spec-figma-section-renderers.js +47 -0
  37. package/dist/orchestrator/figma/spec-figma-section-renderers.js.map +1 -0
  38. package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +56 -11
  39. package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +30 -17
  40. package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +4 -3
  41. package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +33 -1
  42. package/dist/orchestrator/templates/ai-instructions/claude-config.md +1 -0
  43. package/dist/orchestrator/templates/ai-instructions/claude-skill-figma-source.md +151 -0
  44. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +39 -20
  45. package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +2 -0
  46. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +53 -9
  47. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +21 -16
  48. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +2 -2
  49. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +4 -3
  50. package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +36 -4
  51. package/dist/orchestrator/templates/ai-instructions/copilot-config.md +1 -0
  52. package/dist/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +151 -0
  53. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +151 -0
  54. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +61 -0
  55. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +51 -25
  56. package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +20 -0
  57. package/dist/tools/figma/figma-auth.d.ts +36 -0
  58. package/dist/tools/figma/figma-auth.d.ts.map +1 -0
  59. package/dist/tools/figma/figma-auth.js +182 -0
  60. package/dist/tools/figma/figma-auth.js.map +1 -0
  61. package/dist/tools/figma/figma-cache.d.ts +45 -0
  62. package/dist/tools/figma/figma-cache.d.ts.map +1 -0
  63. package/dist/tools/figma/figma-cache.js +191 -0
  64. package/dist/tools/figma/figma-cache.js.map +1 -0
  65. package/dist/tools/figma/figma-client-types.d.ts +112 -0
  66. package/dist/tools/figma/figma-client-types.d.ts.map +1 -0
  67. package/dist/tools/figma/figma-client-types.js +7 -0
  68. package/dist/tools/figma/figma-client-types.js.map +1 -0
  69. package/dist/tools/figma/figma-errors.d.ts +49 -0
  70. package/dist/tools/figma/figma-errors.d.ts.map +1 -0
  71. package/dist/tools/figma/figma-errors.js +105 -0
  72. package/dist/tools/figma/figma-errors.js.map +1 -0
  73. package/dist/tools/figma/figma-image-downloader.d.ts +25 -0
  74. package/dist/tools/figma/figma-image-downloader.d.ts.map +1 -0
  75. package/dist/tools/figma/figma-image-downloader.js +128 -0
  76. package/dist/tools/figma/figma-image-downloader.js.map +1 -0
  77. package/dist/tools/figma/figma-node-filter.d.ts +26 -0
  78. package/dist/tools/figma/figma-node-filter.d.ts.map +1 -0
  79. package/dist/tools/figma/figma-node-filter.js +164 -0
  80. package/dist/tools/figma/figma-node-filter.js.map +1 -0
  81. package/dist/tools/figma/figma-rest-client.d.ts +24 -0
  82. package/dist/tools/figma/figma-rest-client.d.ts.map +1 -0
  83. package/dist/tools/figma/figma-rest-client.js +154 -0
  84. package/dist/tools/figma/figma-rest-client.js.map +1 -0
  85. package/dist/tools/figma/figma-url-parser.d.ts +18 -0
  86. package/dist/tools/figma/figma-url-parser.d.ts.map +1 -0
  87. package/dist/tools/figma/figma-url-parser.js +51 -0
  88. package/dist/tools/figma/figma-url-parser.js.map +1 -0
  89. package/dist/utils/exec-file-no-throw.d.ts +20 -0
  90. package/dist/utils/exec-file-no-throw.d.ts.map +1 -0
  91. package/dist/utils/exec-file-no-throw.js +36 -0
  92. package/dist/utils/exec-file-no-throw.js.map +1 -0
  93. package/package.json +1 -1
  94. package/src/cli/commands/add.ts +80 -9
  95. package/src/cli/commands/figma.ts +162 -0
  96. package/src/cli/index.ts +4 -2
  97. package/src/orchestrator/ai-rules-updater.ts +2 -0
  98. package/src/orchestrator/figma/figma-scaffolder-helpers.ts +126 -0
  99. package/src/orchestrator/figma/figma-scaffolder-types.ts +26 -0
  100. package/src/orchestrator/figma/figma-scaffolder.ts +209 -0
  101. package/src/orchestrator/figma/node-path-collapser.ts +38 -0
  102. package/src/orchestrator/figma/spec-figma-renderer.ts +80 -0
  103. package/src/orchestrator/figma/spec-figma-section-renderers.ts +46 -0
  104. package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +56 -11
  105. package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +30 -17
  106. package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +4 -3
  107. package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +33 -1
  108. package/src/orchestrator/templates/ai-instructions/claude-config.md +1 -0
  109. package/src/orchestrator/templates/ai-instructions/claude-skill-figma-source.md +151 -0
  110. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +39 -20
  111. package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +2 -0
  112. package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +53 -9
  113. package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +21 -16
  114. package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +2 -2
  115. package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +4 -3
  116. package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +36 -4
  117. package/src/orchestrator/templates/ai-instructions/copilot-config.md +1 -0
  118. package/src/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +151 -0
  119. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +151 -0
  120. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +61 -0
  121. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +51 -25
  122. package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +20 -0
  123. package/src/tools/figma/figma-auth.ts +161 -0
  124. package/src/tools/figma/figma-cache.ts +184 -0
  125. package/src/tools/figma/figma-client-types.ts +125 -0
  126. package/src/tools/figma/figma-errors.ts +127 -0
  127. package/src/tools/figma/figma-image-downloader.ts +112 -0
  128. package/src/tools/figma/figma-node-filter.ts +198 -0
  129. package/src/tools/figma/figma-rest-client.ts +183 -0
  130. package/src/tools/figma/figma-url-parser.ts +55 -0
  131. package/src/utils/exec-file-no-throw.ts +45 -0
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Shared types for Figma integration.
3
+ * Used by figma-auth.ts, figma-url-parser.ts, and REST client modules.
4
+ */
5
+ /** Authentication configuration for Figma API. */
6
+ export interface FigmaAuthConfig {
7
+ /** Personal Access Token — never log this value. */
8
+ pat: string;
9
+ }
10
+ /** Parsed reference to a specific Figma node. */
11
+ export interface FigmaNodeRef {
12
+ /** Figma file key extracted from URL (alphanumeric, 20+ chars). */
13
+ fileKey: string;
14
+ /** Node ID in API form: "1:23" (colon-separated). Absent when URL has no node-id param. */
15
+ nodeId?: string;
16
+ }
17
+ /** Minimal /v1/me response shape used for PAT validation. */
18
+ export interface FigmaMeResponse {
19
+ id: string;
20
+ email: string;
21
+ handle: string;
22
+ }
23
+ /** Result of a PAT validation call. */
24
+ export interface FigmaAuthCheckResult {
25
+ valid: boolean;
26
+ handle?: string;
27
+ email?: string;
28
+ error?: string;
29
+ }
30
+ /** Raw Figma node as returned by the API (loose — unknown fields allowed). */
31
+ export interface FigmaRawNode {
32
+ id: string;
33
+ name: string;
34
+ type: string;
35
+ /** Text content for TEXT nodes. */
36
+ characters?: string;
37
+ /** Component set variant properties. */
38
+ componentPropertyDefinitions?: Record<string, unknown>;
39
+ /** Variant property values for COMPONENT nodes. */
40
+ componentProperties?: Record<string, {
41
+ value: string;
42
+ type: string;
43
+ }>;
44
+ /** Bounding box from absoluteBoundingBox. */
45
+ absoluteBoundingBox?: {
46
+ x: number;
47
+ y: number;
48
+ width: number;
49
+ height: number;
50
+ };
51
+ /** Child nodes. */
52
+ children?: FigmaRawNode[];
53
+ /** Allow any additional Figma fields. */
54
+ [key: string]: unknown;
55
+ }
56
+ /** Response from GET /v1/files/:key/nodes */
57
+ export interface FigmaFileNodesResponse {
58
+ nodes: Record<string, {
59
+ document: FigmaRawNode;
60
+ } | null>;
61
+ /** Current file version ID. */
62
+ version: string;
63
+ }
64
+ /** Response from GET /v1/images/:key */
65
+ export interface FigmaImageUrlsResponse {
66
+ /** Map of nodeId → signed S3 URL (transient — do not persist). */
67
+ images: Record<string, string | null>;
68
+ err: string | null;
69
+ }
70
+ /** Role inferred from component name suffix. */
71
+ export type FigmaNodeRole = 'button' | 'textbox' | 'link' | 'image' | null;
72
+ /** Pruned, minimal Figma node for downstream LLM / test generation. */
73
+ export interface FilteredFigmaNode {
74
+ id: string;
75
+ name: string;
76
+ type: string;
77
+ /** Text content (TEXT nodes only). */
78
+ text?: string;
79
+ /** Component set name (COMPONENT / COMPONENT_SET nodes). */
80
+ componentName?: string;
81
+ /** Variant property map from componentProperties. */
82
+ variantProps?: Record<string, string>;
83
+ /** Inferred accessibility / interaction role. */
84
+ role?: FigmaNodeRole;
85
+ /** Bounding rectangle in file coordinates. */
86
+ boundingBox?: {
87
+ x: number;
88
+ y: number;
89
+ w: number;
90
+ h: number;
91
+ };
92
+ children: FilteredFigmaNode[];
93
+ }
94
+ /** A single text label extracted from the node tree. */
95
+ export interface FigmaTextLabel {
96
+ text: string;
97
+ nodePath: string;
98
+ }
99
+ /** A single variant definition extracted from a COMPONENT_SET. */
100
+ export interface FigmaVariantDefinition {
101
+ nodeId: string;
102
+ name: string;
103
+ variantProps: Record<string, string>;
104
+ }
105
+ export interface FigmaImageOptions {
106
+ /** Render scale. Default 2. */
107
+ scale?: number;
108
+ /** Image format. Default 'png'. */
109
+ format?: 'png' | 'jpg' | 'svg' | 'pdf';
110
+ }
111
+ export type FigmaCacheKind = 'json' | 'raw' | `${number}x`;
112
+ //# sourceMappingURL=figma-client-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-client-types.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-client-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ /**
3
+ * Shared types for Figma integration.
4
+ * Used by figma-auth.ts, figma-url-parser.ts, and REST client modules.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ //# sourceMappingURL=figma-client-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-client-types.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-client-types.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Typed error classes for Figma API failures.
3
+ * Each error carries an actionable remediation message for CLI output.
4
+ *
5
+ * Never include PAT values in error messages or remediation text.
6
+ */
7
+ /** Common base for all Figma errors. */
8
+ declare abstract class FigmaBaseError extends Error {
9
+ /** Human-readable instruction shown to the user on failure. */
10
+ readonly remediation: string;
11
+ constructor(message: string, remediation: string);
12
+ }
13
+ /**
14
+ * HTTP 401 — PAT is missing, expired, or invalid.
15
+ */
16
+ export declare class FigmaAuthError extends FigmaBaseError {
17
+ constructor(message?: string);
18
+ }
19
+ /**
20
+ * HTTP 403 / 404 — PAT valid but insufficient access, or resource not found.
21
+ */
22
+ export declare class FigmaAccessError extends FigmaBaseError {
23
+ readonly statusCode: 403 | 404;
24
+ constructor(statusCode: 403 | 404, message?: string);
25
+ }
26
+ /**
27
+ * HTTP 429 — Rate limited by Figma API.
28
+ */
29
+ export declare class FigmaRateLimitError extends FigmaBaseError {
30
+ /** Seconds to wait before retrying, parsed from Retry-After header. */
31
+ readonly retryAfter: number;
32
+ readonly planTier?: string;
33
+ readonly bucket?: string;
34
+ readonly upgradeLink?: string;
35
+ constructor(retryAfter: number, message?: string, opts?: {
36
+ planTier?: string;
37
+ bucket?: string;
38
+ upgradeLink?: string;
39
+ });
40
+ }
41
+ /**
42
+ * Network / transport failure (timeout, DNS, stream error, etc.).
43
+ */
44
+ export declare class FigmaNetworkError extends FigmaBaseError {
45
+ readonly cause: unknown;
46
+ constructor(message: string, cause?: unknown);
47
+ }
48
+ export {};
49
+ //# sourceMappingURL=figma-errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-errors.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-errors.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,wCAAwC;AACxC,uBAAe,cAAe,SAAQ,KAAK;IACzC,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM;CAKjD;AAMD;;GAEG;AACH,qBAAa,cAAe,SAAQ,cAAc;gBACpC,OAAO,SAAuC;CAM3D;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,cAAc;IAClD,QAAQ,CAAC,UAAU,EAAE,GAAG,GAAG,GAAG,CAAC;gBAEnB,UAAU,EAAE,GAAG,GAAG,GAAG,EAAE,OAAO,CAAC,EAAE,MAAM;CAYpD;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,cAAc;IACrD,uEAAuE;IACvE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;gBAG5B,UAAU,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,MAAM,EAChB,IAAI,GAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAO;CAyB1E;AAgBD;;GAEG;AACH,qBAAa,iBAAkB,SAAQ,cAAc;IACnD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;gBAEZ,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAO7C"}
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ /**
3
+ * Typed error classes for Figma API failures.
4
+ * Each error carries an actionable remediation message for CLI output.
5
+ *
6
+ * Never include PAT values in error messages or remediation text.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.FigmaNetworkError = exports.FigmaRateLimitError = exports.FigmaAccessError = exports.FigmaAuthError = void 0;
10
+ // ---------------------------------------------------------------------------
11
+ // Base
12
+ // ---------------------------------------------------------------------------
13
+ /** Common base for all Figma errors. */
14
+ class FigmaBaseError extends Error {
15
+ constructor(message, remediation) {
16
+ super(message);
17
+ this.name = this.constructor.name;
18
+ this.remediation = remediation;
19
+ }
20
+ }
21
+ // ---------------------------------------------------------------------------
22
+ // Concrete error types
23
+ // ---------------------------------------------------------------------------
24
+ /**
25
+ * HTTP 401 — PAT is missing, expired, or invalid.
26
+ */
27
+ class FigmaAuthError extends FigmaBaseError {
28
+ constructor(message = 'Figma authentication failed (401).') {
29
+ super(message, 'Your Figma PAT is invalid or expired. Re-authenticate by running: sungen figma auth set');
30
+ }
31
+ }
32
+ exports.FigmaAuthError = FigmaAuthError;
33
+ /**
34
+ * HTTP 403 / 404 — PAT valid but insufficient access, or resource not found.
35
+ */
36
+ class FigmaAccessError extends FigmaBaseError {
37
+ constructor(statusCode, message) {
38
+ const defaultMsg = statusCode === 403
39
+ ? 'Figma access denied (403).'
40
+ : 'Figma resource not found (404).';
41
+ const remediation = statusCode === 403
42
+ ? 'Request view access to the Figma file, or ensure your PAT has the file:read scope.'
43
+ : 'Check the Figma URL — the file key or node ID may be incorrect.';
44
+ super(message ?? defaultMsg, remediation);
45
+ this.statusCode = statusCode;
46
+ }
47
+ }
48
+ exports.FigmaAccessError = FigmaAccessError;
49
+ /**
50
+ * HTTP 429 — Rate limited by Figma API.
51
+ */
52
+ class FigmaRateLimitError extends FigmaBaseError {
53
+ constructor(retryAfter, message, opts = {}) {
54
+ const human = formatRetryAfter(retryAfter);
55
+ const tierSuffix = opts.planTier ? ` (plan: ${opts.planTier}${opts.bucket ? `, bucket: ${opts.bucket}` : ''})` : '';
56
+ const msg = message ?? `Figma API rate limit hit (429)${tierSuffix}. Retry after ${human}.`;
57
+ // Multi-day Retry-After → almost certainly the Starter-tier daily quota.
58
+ const multiDay = retryAfter > 24 * 60 * 60;
59
+ const remediation = multiDay
60
+ ? [
61
+ `This is a Figma ${opts.planTier ?? 'Starter'}-tier daily/multi-day REST quota (not a transient burst).`,
62
+ 'To continue today you must either:',
63
+ ' 1) Re-run with a cached version: sungen add --screen <name> --figma <url> (no --refresh)',
64
+ ' → reuses .sungen/figma-cache raw JSON; skips the /nodes API call entirely.',
65
+ opts.upgradeLink ? ` 2) Upgrade the Figma account: ${opts.upgradeLink}` : ' 2) Upgrade the Figma account to a paid tier.',
66
+ ' 3) Wait for the quota window to reset.',
67
+ ].join('\n')
68
+ : 'Wait and retry, or reduce request frequency. In the meantime you can re-run without --refresh to reuse cached node JSON.';
69
+ super(msg, remediation);
70
+ this.retryAfter = retryAfter;
71
+ this.planTier = opts.planTier;
72
+ this.bucket = opts.bucket;
73
+ this.upgradeLink = opts.upgradeLink;
74
+ }
75
+ }
76
+ exports.FigmaRateLimitError = FigmaRateLimitError;
77
+ /** Turn `369943` → `4d 6h 45m`. */
78
+ function formatRetryAfter(secs) {
79
+ if (!Number.isFinite(secs) || secs <= 0)
80
+ return `${secs}s`;
81
+ const d = Math.floor(secs / 86400);
82
+ const h = Math.floor((secs % 86400) / 3600);
83
+ const m = Math.floor((secs % 3600) / 60);
84
+ const parts = [];
85
+ if (d)
86
+ parts.push(`${d}d`);
87
+ if (h)
88
+ parts.push(`${h}h`);
89
+ if (m && !d)
90
+ parts.push(`${m}m`);
91
+ if (!parts.length)
92
+ parts.push(`${secs}s`);
93
+ return `${parts.join(' ')} (${secs}s)`;
94
+ }
95
+ /**
96
+ * Network / transport failure (timeout, DNS, stream error, etc.).
97
+ */
98
+ class FigmaNetworkError extends FigmaBaseError {
99
+ constructor(message, cause) {
100
+ super(message, 'Check your internet connection and verify api.figma.com is reachable. If the issue persists, retry.');
101
+ this.cause = cause;
102
+ }
103
+ }
104
+ exports.FigmaNetworkError = FigmaNetworkError;
105
+ //# sourceMappingURL=figma-errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-errors.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Download a Figma-signed image URL to disk.
3
+ *
4
+ * - Streams response body to a .tmp file then atomically renames to destPath.
5
+ * - Verifies the final file is non-zero bytes before committing.
6
+ * - Enforces a configurable timeout (default 30 s).
7
+ * - Surfaces all failures as FigmaNetworkError.
8
+ *
9
+ * Security: signed S3 URLs are consumed transiently — never logged.
10
+ */
11
+ export interface DownloadOptions {
12
+ /** Request timeout in milliseconds. Default: 30 000. */
13
+ timeoutMs?: number;
14
+ }
15
+ /**
16
+ * Download the image at `url` and write it to `destPath`.
17
+ *
18
+ * Uses atomic write: streams to `<destPath>.tmp`, verifies size > 0,
19
+ * then renames to `destPath`. Cleans up .tmp on failure.
20
+ *
21
+ * @throws FigmaNetworkError on any network, timeout, or I/O failure.
22
+ * @throws FigmaNetworkError when the downloaded file has zero bytes.
23
+ */
24
+ export declare function downloadToPath(url: string, destPath: string, options?: DownloadOptions): Promise<void>;
25
+ //# sourceMappingURL=figma-image-downloader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-image-downloader.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-image-downloader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,MAAM,WAAW,eAAe;IAC9B,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,IAAI,CAAC,CAgEf"}
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ /**
3
+ * Download a Figma-signed image URL to disk.
4
+ *
5
+ * - Streams response body to a .tmp file then atomically renames to destPath.
6
+ * - Verifies the final file is non-zero bytes before committing.
7
+ * - Enforces a configurable timeout (default 30 s).
8
+ * - Surfaces all failures as FigmaNetworkError.
9
+ *
10
+ * Security: signed S3 URLs are consumed transiently — never logged.
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.downloadToPath = downloadToPath;
47
+ const fs = __importStar(require("node:fs"));
48
+ const path = __importStar(require("node:path"));
49
+ const promises_1 = require("node:stream/promises");
50
+ const node_stream_1 = require("node:stream");
51
+ const figma_errors_1 = require("./figma-errors");
52
+ /**
53
+ * Download the image at `url` and write it to `destPath`.
54
+ *
55
+ * Uses atomic write: streams to `<destPath>.tmp`, verifies size > 0,
56
+ * then renames to `destPath`. Cleans up .tmp on failure.
57
+ *
58
+ * @throws FigmaNetworkError on any network, timeout, or I/O failure.
59
+ * @throws FigmaNetworkError when the downloaded file has zero bytes.
60
+ */
61
+ async function downloadToPath(url, destPath, options = {}) {
62
+ const timeoutMs = options.timeoutMs ?? 30000;
63
+ const tmpPath = `${destPath}.tmp`;
64
+ // Ensure destination directory exists
65
+ const dir = path.dirname(destPath);
66
+ fs.mkdirSync(dir, { recursive: true });
67
+ const controller = new AbortController();
68
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
69
+ let response;
70
+ try {
71
+ response = await fetch(url, { signal: controller.signal });
72
+ }
73
+ catch (cause) {
74
+ clearTimeout(timer);
75
+ cleanupTmp(tmpPath);
76
+ const isTimeout = cause instanceof Error && cause.name === 'AbortError';
77
+ throw new figma_errors_1.FigmaNetworkError(isTimeout
78
+ ? `Image download timed out after ${timeoutMs}ms.`
79
+ : 'Image download failed due to a network error.', cause);
80
+ }
81
+ finally {
82
+ clearTimeout(timer);
83
+ }
84
+ if (!response.ok) {
85
+ cleanupTmp(tmpPath);
86
+ throw new figma_errors_1.FigmaNetworkError(`Image download received HTTP ${response.status}.`);
87
+ }
88
+ if (!response.body) {
89
+ cleanupTmp(tmpPath);
90
+ throw new figma_errors_1.FigmaNetworkError('Image download response has no body.');
91
+ }
92
+ // Stream to .tmp file
93
+ const writeStream = fs.createWriteStream(tmpPath);
94
+ try {
95
+ await (0, promises_1.pipeline)(node_stream_1.Readable.fromWeb(response.body), writeStream);
96
+ }
97
+ catch (cause) {
98
+ cleanupTmp(tmpPath);
99
+ throw new figma_errors_1.FigmaNetworkError('Failed to write image to disk.', cause);
100
+ }
101
+ // Verify non-zero file size
102
+ const stat = fs.statSync(tmpPath);
103
+ if (stat.size === 0) {
104
+ cleanupTmp(tmpPath);
105
+ throw new figma_errors_1.FigmaNetworkError('Downloaded image file is empty (0 bytes).');
106
+ }
107
+ // Atomic rename
108
+ try {
109
+ fs.renameSync(tmpPath, destPath);
110
+ }
111
+ catch (cause) {
112
+ cleanupTmp(tmpPath);
113
+ throw new figma_errors_1.FigmaNetworkError('Failed to finalize image file on disk.', cause);
114
+ }
115
+ }
116
+ // ---------------------------------------------------------------------------
117
+ // Internal helpers
118
+ // ---------------------------------------------------------------------------
119
+ function cleanupTmp(tmpPath) {
120
+ try {
121
+ if (fs.existsSync(tmpPath))
122
+ fs.unlinkSync(tmpPath);
123
+ }
124
+ catch {
125
+ // Best-effort cleanup — ignore errors
126
+ }
127
+ }
128
+ //# sourceMappingURL=figma-image-downloader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-image-downloader.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-image-downloader.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsBH,wCAoEC;AAxFD,4CAA8B;AAC9B,gDAAkC;AAClC,mDAAgD;AAChD,6CAAuC;AACvC,iDAAmD;AAOnD;;;;;;;;GAQG;AACI,KAAK,UAAU,cAAc,CAClC,GAAW,EACX,QAAgB,EAChB,UAA2B,EAAE;IAE7B,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAM,CAAC;IAC9C,MAAM,OAAO,GAAG,GAAG,QAAQ,MAAM,CAAC;IAElC,sCAAsC;IACtC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEvC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAE9D,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,UAAU,CAAC,OAAO,CAAC,CAAC;QACpB,MAAM,SAAS,GACb,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,CAAC;QACxD,MAAM,IAAI,gCAAiB,CACzB,SAAS;YACP,CAAC,CAAC,kCAAkC,SAAS,KAAK;YAClD,CAAC,CAAC,+CAA+C,EACnD,KAAK,CACN,CAAC;IACJ,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,UAAU,CAAC,OAAO,CAAC,CAAC;QACpB,MAAM,IAAI,gCAAiB,CACzB,gCAAgC,QAAQ,CAAC,MAAM,GAAG,CACnD,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnB,UAAU,CAAC,OAAO,CAAC,CAAC;QACpB,MAAM,IAAI,gCAAiB,CAAC,sCAAsC,CAAC,CAAC;IACtE,CAAC;IAED,sBAAsB;IACtB,MAAM,WAAW,GAAG,EAAE,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAClD,IAAI,CAAC;QACH,MAAM,IAAA,mBAAQ,EAAC,sBAAQ,CAAC,OAAO,CAAC,QAAQ,CAAC,IAA2C,CAAC,EAAE,WAAW,CAAC,CAAC;IACtG,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,UAAU,CAAC,OAAO,CAAC,CAAC;QACpB,MAAM,IAAI,gCAAiB,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;IACvE,CAAC;IAED,4BAA4B;IAC5B,MAAM,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACpB,UAAU,CAAC,OAAO,CAAC,CAAC;QACpB,MAAM,IAAI,gCAAiB,CAAC,2CAA2C,CAAC,CAAC;IAC3E,CAAC;IAED,gBAAgB;IAChB,IAAI,CAAC;QACH,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,UAAU,CAAC,OAAO,CAAC,CAAC;QACpB,MAAM,IAAI,gCAAiB,CAAC,wCAAwC,EAAE,KAAK,CAAC,CAAC;IAC/E,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E,SAAS,UAAU,CAAC,OAAe;IACjC,IAAI,CAAC;QACH,IAAI,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC;YAAE,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,sCAAsC;IACxC,CAAC;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Prune raw Figma node trees to a minimal shape for LLM / test-generation use.
3
+ *
4
+ * Drops: vectors, fills, effects, strokes, constraints, style references.
5
+ * Keeps: id, name, type, text content, component/variant metadata, bounding box, children.
6
+ *
7
+ * Role inference: component name suffix → button | textbox | link | image | null.
8
+ */
9
+ import type { FilteredFigmaNode, FigmaTextLabel, FigmaVariantDefinition } from './figma-client-types';
10
+ /**
11
+ * Validate and filter a raw Figma node tree.
12
+ *
13
+ * @throws ZodError when the input fails basic structural validation.
14
+ */
15
+ export declare function filterFigmaNode(rawNode: unknown): FilteredFigmaNode;
16
+ /**
17
+ * Recursively collect all text labels from a filtered node tree.
18
+ * Returns a flat list of { text, nodePath } pairs.
19
+ */
20
+ export declare function extractTextLabels(node: FilteredFigmaNode, _path?: string): FigmaTextLabel[];
21
+ /**
22
+ * Extract variant definitions from a COMPONENT_SET node.
23
+ * Each direct child COMPONENT represents one variant combination.
24
+ */
25
+ export declare function extractVariants(componentSetNode: FilteredFigmaNode): FigmaVariantDefinition[];
26
+ //# sourceMappingURL=figma-node-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-node-filter.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-node-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EACV,iBAAiB,EAEjB,cAAc,EACd,sBAAsB,EACvB,MAAM,sBAAsB,CAAC;AAgI9B;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,iBAAiB,CASnE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,iBAAiB,EACvB,KAAK,SAAK,GACT,cAAc,EAAE,CAalB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,gBAAgB,EAAE,iBAAiB,GAClC,sBAAsB,EAAE,CAU1B"}
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ /**
3
+ * Prune raw Figma node trees to a minimal shape for LLM / test-generation use.
4
+ *
5
+ * Drops: vectors, fills, effects, strokes, constraints, style references.
6
+ * Keeps: id, name, type, text content, component/variant metadata, bounding box, children.
7
+ *
8
+ * Role inference: component name suffix → button | textbox | link | image | null.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.filterFigmaNode = filterFigmaNode;
12
+ exports.extractTextLabels = extractTextLabels;
13
+ exports.extractVariants = extractVariants;
14
+ const zod_1 = require("zod");
15
+ // ---------------------------------------------------------------------------
16
+ // Zod schema — loose, unknown fields allowed via passthrough
17
+ // ---------------------------------------------------------------------------
18
+ // Zod v4: z.record() requires explicit key schema (z.string())
19
+ const RawNodeSchema = zod_1.z
20
+ .object({
21
+ id: zod_1.z.string(),
22
+ name: zod_1.z.string(),
23
+ type: zod_1.z.string(),
24
+ characters: zod_1.z.string().optional(),
25
+ componentPropertyDefinitions: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
26
+ componentProperties: zod_1.z
27
+ .record(zod_1.z.string(), zod_1.z.object({ value: zod_1.z.string(), type: zod_1.z.string() }))
28
+ .optional(),
29
+ absoluteBoundingBox: zod_1.z
30
+ .object({ x: zod_1.z.number(), y: zod_1.z.number(), width: zod_1.z.number(), height: zod_1.z.number() })
31
+ .optional(),
32
+ children: zod_1.z.array(zod_1.z.lazy(() => RawNodeSchema)).optional(),
33
+ })
34
+ .passthrough();
35
+ // ---------------------------------------------------------------------------
36
+ // Role inference
37
+ // ---------------------------------------------------------------------------
38
+ const ROLE_SUFFIX_MAP = [
39
+ [/button$/i, 'button'],
40
+ [/input|textbox|text.?field|text.?input/i, 'textbox'],
41
+ [/link$/i, 'link'],
42
+ [/icon|image|img|illustration/i, 'image'],
43
+ ];
44
+ function inferRole(name) {
45
+ for (const [pattern, role] of ROLE_SUFFIX_MAP) {
46
+ if (pattern.test(name))
47
+ return role;
48
+ }
49
+ return null;
50
+ }
51
+ // Node types to skip entirely (leaf-only visual noise)
52
+ const SKIP_TYPES = new Set([
53
+ 'VECTOR',
54
+ 'STAR',
55
+ 'POLYGON',
56
+ 'BOOLEAN_OPERATION',
57
+ 'ELLIPSE',
58
+ 'LINE',
59
+ ]);
60
+ // ---------------------------------------------------------------------------
61
+ // Core filter
62
+ // ---------------------------------------------------------------------------
63
+ /**
64
+ * Recursively prune a raw Figma node to the minimal FilteredFigmaNode shape.
65
+ * Returns null when the node type should be dropped entirely.
66
+ *
67
+ * @param raw Validated (but loosely typed) raw Figma node object.
68
+ * @param maxDepth Maximum recursion depth (default 30 — safety guard).
69
+ */
70
+ function filterNode(raw, depth = 0, maxDepth = 30) {
71
+ const id = raw['id'];
72
+ const name = raw['name'];
73
+ const type = raw['type'];
74
+ if (SKIP_TYPES.has(type))
75
+ return null;
76
+ if (depth > maxDepth)
77
+ return null;
78
+ // Bounding box
79
+ let boundingBox;
80
+ const bb = raw['absoluteBoundingBox'];
81
+ if (bb) {
82
+ boundingBox = { x: bb.x, y: bb.y, w: bb.width, h: bb.height };
83
+ }
84
+ // Text content
85
+ const text = type === 'TEXT' ? raw['characters'] : undefined;
86
+ // Component / variant metadata
87
+ let componentName;
88
+ let variantProps;
89
+ if (type === 'COMPONENT' || type === 'COMPONENT_SET' || type === 'INSTANCE') {
90
+ componentName = name;
91
+ const props = raw['componentProperties'];
92
+ if (props) {
93
+ variantProps = Object.fromEntries(Object.entries(props).map(([k, v]) => [k, v.value]));
94
+ }
95
+ }
96
+ // Role (only meaningful for component-like nodes)
97
+ const role = componentName != null ? inferRole(name) : null;
98
+ // Recurse into children
99
+ const rawChildren = raw['children'] ?? [];
100
+ const children = rawChildren
101
+ .map((child) => filterNode(child, depth + 1, maxDepth))
102
+ .filter((n) => n !== null);
103
+ const node = { id, name, type, children };
104
+ if (text !== undefined)
105
+ node.text = text;
106
+ if (componentName !== undefined)
107
+ node.componentName = componentName;
108
+ if (variantProps !== undefined)
109
+ node.variantProps = variantProps;
110
+ if (role)
111
+ node.role = role;
112
+ if (boundingBox !== undefined)
113
+ node.boundingBox = boundingBox;
114
+ return node;
115
+ }
116
+ // ---------------------------------------------------------------------------
117
+ // Public API
118
+ // ---------------------------------------------------------------------------
119
+ /**
120
+ * Validate and filter a raw Figma node tree.
121
+ *
122
+ * @throws ZodError when the input fails basic structural validation.
123
+ */
124
+ function filterFigmaNode(rawNode) {
125
+ const validated = RawNodeSchema.parse(rawNode);
126
+ const result = filterNode(validated);
127
+ // Root node should never be null (root type is never in SKIP_TYPES)
128
+ if (!result) {
129
+ const id = rawNode['id'] ?? 'unknown';
130
+ throw new Error(`Root node (id=${id}) was filtered out — unexpected type.`);
131
+ }
132
+ return result;
133
+ }
134
+ /**
135
+ * Recursively collect all text labels from a filtered node tree.
136
+ * Returns a flat list of { text, nodePath } pairs.
137
+ */
138
+ function extractTextLabels(node, _path = '') {
139
+ const currentPath = _path ? `${_path} > ${node.name}` : node.name;
140
+ const labels = [];
141
+ if (node.text) {
142
+ labels.push({ text: node.text, nodePath: currentPath });
143
+ }
144
+ for (const child of node.children) {
145
+ labels.push(...extractTextLabels(child, currentPath));
146
+ }
147
+ return labels;
148
+ }
149
+ /**
150
+ * Extract variant definitions from a COMPONENT_SET node.
151
+ * Each direct child COMPONENT represents one variant combination.
152
+ */
153
+ function extractVariants(componentSetNode) {
154
+ if (componentSetNode.type !== 'COMPONENT_SET')
155
+ return [];
156
+ return componentSetNode.children
157
+ .filter((child) => child.type === 'COMPONENT')
158
+ .map((child) => ({
159
+ nodeId: child.id,
160
+ name: child.name,
161
+ variantProps: child.variantProps ?? {},
162
+ }));
163
+ }
164
+ //# sourceMappingURL=figma-node-filter.js.map