@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 @@
1
+ {"version":3,"file":"figma-node-filter.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-node-filter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Minimal Figma REST client.
3
+ *
4
+ * - Base URL: https://api.figma.com
5
+ * - Auth header: X-Figma-Token (PAT — never logged)
6
+ * - Retry: 429 → honor Retry-After (cap 3 attempts); 5xx → retry once
7
+ * - All HTTP errors mapped to typed FigmaError subclasses
8
+ */
9
+ import type { FigmaMeResponse, FigmaFileNodesResponse, FigmaImageUrlsResponse, FigmaImageOptions } from './figma-client-types';
10
+ /**
11
+ * GET /v1/me — validate PAT and return user info.
12
+ */
13
+ export declare function getMe(pat: string): Promise<FigmaMeResponse>;
14
+ /**
15
+ * GET /v1/files/:key/nodes?ids=<id,id,...>
16
+ * Returns the raw node document and current file version.
17
+ */
18
+ export declare function getFileNodes(pat: string, fileKey: string, nodeIds: string[]): Promise<FigmaFileNodesResponse>;
19
+ /**
20
+ * GET /v1/images/:key?ids=<id,id>&scale=<n>&format=<fmt>
21
+ * Returns signed S3 URLs — consume immediately, do not persist.
22
+ */
23
+ export declare function getImageUrls(pat: string, fileKey: string, nodeIds: string[], options?: FigmaImageOptions): Promise<FigmaImageUrlsResponse>;
24
+ //# sourceMappingURL=figma-rest-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-rest-client.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-rest-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,OAAO,KAAK,EACV,eAAe,EACf,sBAAsB,EACtB,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,sBAAsB,CAAC;AAkH9B;;GAEG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAIjE;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,sBAAsB,CAAC,CAQjC;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,sBAAsB,CAAC,CAYjC"}
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ /**
3
+ * Minimal Figma REST client.
4
+ *
5
+ * - Base URL: https://api.figma.com
6
+ * - Auth header: X-Figma-Token (PAT — never logged)
7
+ * - Retry: 429 → honor Retry-After (cap 3 attempts); 5xx → retry once
8
+ * - All HTTP errors mapped to typed FigmaError subclasses
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.getMe = getMe;
12
+ exports.getFileNodes = getFileNodes;
13
+ exports.getImageUrls = getImageUrls;
14
+ const figma_errors_1 = require("./figma-errors");
15
+ // ---------------------------------------------------------------------------
16
+ // Constants
17
+ // ---------------------------------------------------------------------------
18
+ const BASE_URL = 'https://api.figma.com';
19
+ /**
20
+ * Per-request timeout (ms). Figma can stall on very large nodes; without this,
21
+ * node fetch hangs the CLI indefinitely. Override via SUNGEN_FIGMA_TIMEOUT_MS.
22
+ */
23
+ const DEFAULT_REQUEST_TIMEOUT_MS = 90000;
24
+ function getRequestTimeoutMs() {
25
+ const raw = process.env.SUNGEN_FIGMA_TIMEOUT_MS;
26
+ const n = raw ? parseInt(raw, 10) : NaN;
27
+ return Number.isFinite(n) && n > 0 ? n : DEFAULT_REQUEST_TIMEOUT_MS;
28
+ }
29
+ // ---------------------------------------------------------------------------
30
+ // Internal helpers
31
+ // ---------------------------------------------------------------------------
32
+ /** Build headers with PAT — never expose PAT in logs/errors. */
33
+ function makeHeaders(pat) {
34
+ return { 'X-Figma-Token': pat };
35
+ }
36
+ /** Parse Retry-After header into seconds (default 60 when missing/invalid). */
37
+ function parseRetryAfter(headers) {
38
+ const value = headers.get('Retry-After');
39
+ if (!value)
40
+ return 60;
41
+ const parsed = parseInt(value, 10);
42
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 60;
43
+ }
44
+ /** Sleep for `ms` milliseconds. */
45
+ function sleep(ms) {
46
+ return new Promise((resolve) => setTimeout(resolve, ms));
47
+ }
48
+ /**
49
+ * Map a non-2xx HTTP status to a typed error.
50
+ * `url` is included only in a generic way — never includes PAT.
51
+ */
52
+ function mapHttpError(status, url) {
53
+ const safeUrl = url.replace(/\?.*$/, ''); // strip query params (may contain ids)
54
+ if (status === 401)
55
+ throw new figma_errors_1.FigmaAuthError();
56
+ if (status === 403)
57
+ throw new figma_errors_1.FigmaAccessError(403);
58
+ if (status === 404)
59
+ throw new figma_errors_1.FigmaAccessError(404);
60
+ throw new figma_errors_1.FigmaNetworkError(`Figma API error ${status} at ${safeUrl}`);
61
+ }
62
+ /**
63
+ * Core fetch wrapper with retry logic.
64
+ *
65
+ * Retry rules:
66
+ * - 429: up to MAX_RATE_LIMIT_RETRIES attempts, sleeping Retry-After seconds between each.
67
+ * - 5xx: retry once after 2 s.
68
+ * - Other errors: throw immediately.
69
+ */
70
+ async function figmaFetch(pat, url) {
71
+ let serverErrorRetried = false;
72
+ // eslint-disable-next-line no-constant-condition
73
+ while (true) {
74
+ let response;
75
+ const timeoutMs = getRequestTimeoutMs();
76
+ const controller = new AbortController();
77
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
78
+ try {
79
+ response = await fetch(url, { headers: makeHeaders(pat), signal: controller.signal });
80
+ }
81
+ catch (cause) {
82
+ const aborted = cause?.name === 'AbortError';
83
+ if (aborted) {
84
+ throw new figma_errors_1.FigmaNetworkError(`Figma API request timed out after ${Math.round(timeoutMs / 1000)}s. ` +
85
+ 'The node may be very large. Retry with --refresh, pick a smaller frame, ' +
86
+ 'or raise SUNGEN_FIGMA_TIMEOUT_MS.', cause);
87
+ }
88
+ throw new figma_errors_1.FigmaNetworkError('Network request to Figma API failed.', cause);
89
+ }
90
+ finally {
91
+ clearTimeout(timer);
92
+ }
93
+ if (response.ok)
94
+ return response;
95
+ // 429: fail fast. Figma rate-limit windows are per-minute, so in-CLI
96
+ // retries typically waste time. Surface the Retry-After hint and let
97
+ // the caller re-run after waiting.
98
+ if (response.status === 429) {
99
+ const retryAfter = parseRetryAfter(response.headers);
100
+ const planTier = response.headers.get('x-figma-plan-tier') ?? undefined;
101
+ const bucket = response.headers.get('x-figma-rate-limit-type') ?? undefined;
102
+ const upgradeLink = response.headers.get('x-figma-upgrade-link') ?? undefined;
103
+ throw new figma_errors_1.FigmaRateLimitError(retryAfter, undefined, { planTier, bucket, upgradeLink });
104
+ }
105
+ if (response.status >= 500 && !serverErrorRetried) {
106
+ serverErrorRetried = true;
107
+ await sleep(2000);
108
+ continue;
109
+ }
110
+ // For all other non-2xx, delegate to mapHttpError (throws)
111
+ mapHttpError(response.status, url);
112
+ }
113
+ }
114
+ // ---------------------------------------------------------------------------
115
+ // Public API
116
+ // ---------------------------------------------------------------------------
117
+ /**
118
+ * GET /v1/me — validate PAT and return user info.
119
+ */
120
+ async function getMe(pat) {
121
+ const url = `${BASE_URL}/v1/me`;
122
+ const response = await figmaFetch(pat, url);
123
+ return response.json();
124
+ }
125
+ /**
126
+ * GET /v1/files/:key/nodes?ids=<id,id,...>
127
+ * Returns the raw node document and current file version.
128
+ */
129
+ async function getFileNodes(pat, fileKey, nodeIds) {
130
+ if (nodeIds.length === 0) {
131
+ throw new Error('getFileNodes: nodeIds must be a non-empty array.');
132
+ }
133
+ const ids = nodeIds.join(',');
134
+ const url = `${BASE_URL}/v1/files/${encodeURIComponent(fileKey)}/nodes?ids=${encodeURIComponent(ids)}`;
135
+ const response = await figmaFetch(pat, url);
136
+ return response.json();
137
+ }
138
+ /**
139
+ * GET /v1/images/:key?ids=<id,id>&scale=<n>&format=<fmt>
140
+ * Returns signed S3 URLs — consume immediately, do not persist.
141
+ */
142
+ async function getImageUrls(pat, fileKey, nodeIds, options = {}) {
143
+ if (nodeIds.length === 0) {
144
+ throw new Error('getImageUrls: nodeIds must be a non-empty array.');
145
+ }
146
+ const scale = options.scale ?? 2;
147
+ const format = options.format ?? 'png';
148
+ const ids = nodeIds.join(',');
149
+ const url = `${BASE_URL}/v1/images/${encodeURIComponent(fileKey)}` +
150
+ `?ids=${encodeURIComponent(ids)}&scale=${scale}&format=${format}`;
151
+ const response = await figmaFetch(pat, url);
152
+ return response.json();
153
+ }
154
+ //# sourceMappingURL=figma-rest-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-rest-client.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-rest-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Offline Figma URL parser.
3
+ * Converts Figma share URLs → { fileKey, nodeId } without any network call.
4
+ *
5
+ * Supported URL forms:
6
+ * https://www.figma.com/design/<KEY>/<name>?node-id=1-23
7
+ * https://www.figma.com/file/<KEY>/<name>?node-id=1%3A23 (legacy + percent-encoded)
8
+ * https://www.figma.com/design/<KEY>/<name> (no node selected)
9
+ */
10
+ import type { FigmaNodeRef } from './figma-client-types';
11
+ /**
12
+ * Parse a Figma share URL into a structured reference.
13
+ *
14
+ * @param url - Full Figma URL string.
15
+ * @returns `{ fileKey, nodeId? }` on success, `null` if URL is not a valid Figma URL.
16
+ */
17
+ export declare function parseFigmaUrl(url: string): FigmaNodeRef | null;
18
+ //# sourceMappingURL=figma-url-parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-url-parser.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-url-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAuBzD;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAe9D"}
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ /**
3
+ * Offline Figma URL parser.
4
+ * Converts Figma share URLs → { fileKey, nodeId } without any network call.
5
+ *
6
+ * Supported URL forms:
7
+ * https://www.figma.com/design/<KEY>/<name>?node-id=1-23
8
+ * https://www.figma.com/file/<KEY>/<name>?node-id=1%3A23 (legacy + percent-encoded)
9
+ * https://www.figma.com/design/<KEY>/<name> (no node selected)
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.parseFigmaUrl = parseFigmaUrl;
13
+ /**
14
+ * Regex captures:
15
+ * group 1 → path type: "design" | "file"
16
+ * group 2 → fileKey (alphanumeric)
17
+ * group 3 → raw node-id query param value (may be absent)
18
+ */
19
+ const FIGMA_URL_RE = /^https?:\/\/(?:www\.)?figma\.com\/(design|file)\/([A-Za-z0-9]+)(?:\/[^?#]*)?(?:[?&].*?node-id=([\w%:.-]+))?/;
20
+ /**
21
+ * Convert URL-form node-id to Figma API form.
22
+ * "1-23" → "1:23"
23
+ * "1%3A23" → "1:23" (percent-encoded colon)
24
+ * "1:23" → "1:23" (already correct)
25
+ */
26
+ function normalizeNodeId(raw) {
27
+ const decoded = decodeURIComponent(raw);
28
+ // Replace dash separator only between two digit groups (Figma node-id pattern)
29
+ return decoded.replace(/^(\d+)-(\d+)$/, '$1:$2');
30
+ }
31
+ /**
32
+ * Parse a Figma share URL into a structured reference.
33
+ *
34
+ * @param url - Full Figma URL string.
35
+ * @returns `{ fileKey, nodeId? }` on success, `null` if URL is not a valid Figma URL.
36
+ */
37
+ function parseFigmaUrl(url) {
38
+ if (!url || typeof url !== 'string')
39
+ return null;
40
+ const match = FIGMA_URL_RE.exec(url.trim());
41
+ if (!match)
42
+ return null;
43
+ const fileKey = match[2];
44
+ const rawNodeId = match[3];
45
+ const result = { fileKey };
46
+ if (rawNodeId) {
47
+ result.nodeId = normalizeNodeId(rawNodeId);
48
+ }
49
+ return result;
50
+ }
51
+ //# sourceMappingURL=figma-url-parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"figma-url-parser.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-url-parser.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;AA+BH,sCAeC;AA1CD;;;;;GAKG;AACH,MAAM,YAAY,GAChB,6GAA6G,CAAC;AAEhH;;;;;GAKG;AACH,SAAS,eAAe,CAAC,GAAW;IAClC,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;IACxC,+EAA+E;IAC/E,OAAO,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC;AACnD,CAAC;AAED;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,GAAW;IACvC,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC;IAEjD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAE3B,MAAM,MAAM,GAAiB,EAAE,OAAO,EAAE,CAAC;IACzC,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,CAAC,MAAM,GAAG,eAAe,CAAC,SAAS,CAAC,CAAC;IAC7C,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Safe execFile wrapper — uses execFile (not exec) to prevent shell injection.
3
+ * Handles Windows compatibility and provides structured output.
4
+ */
5
+ export interface ExecFileResult {
6
+ stdout: string;
7
+ stderr: string;
8
+ /** Process exit code; 0 = success. */
9
+ status: number;
10
+ }
11
+ /**
12
+ * Run a command safely using execFile (no shell expansion).
13
+ * Never throws — all errors are captured in the result.
14
+ *
15
+ * @param cmd - Executable name or path (no shell features).
16
+ * @param args - Arguments as an array (each arg is passed verbatim).
17
+ * @param cwd - Optional working directory.
18
+ */
19
+ export declare function execFileNoThrow(cmd: string, args: string[], cwd?: string): Promise<ExecFileResult>;
20
+ //# sourceMappingURL=exec-file-no-throw.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-file-no-throw.d.ts","sourceRoot":"","sources":["../../src/utils/exec-file-no-throw.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAOH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,cAAc,CAAC,CAezB"}
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ /**
3
+ * Safe execFile wrapper — uses execFile (not exec) to prevent shell injection.
4
+ * Handles Windows compatibility and provides structured output.
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.execFileNoThrow = execFileNoThrow;
8
+ const child_process_1 = require("child_process");
9
+ const util_1 = require("util");
10
+ const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
11
+ /**
12
+ * Run a command safely using execFile (no shell expansion).
13
+ * Never throws — all errors are captured in the result.
14
+ *
15
+ * @param cmd - Executable name or path (no shell features).
16
+ * @param args - Arguments as an array (each arg is passed verbatim).
17
+ * @param cwd - Optional working directory.
18
+ */
19
+ async function execFileNoThrow(cmd, args, cwd) {
20
+ try {
21
+ const { stdout, stderr } = await execFileAsync(cmd, args, {
22
+ cwd,
23
+ // Prevent accidental shell expansion on Windows too
24
+ shell: false,
25
+ });
26
+ return { stdout: stdout ?? '', stderr: stderr ?? '', status: 0 };
27
+ }
28
+ catch (err) {
29
+ return {
30
+ stdout: err.stdout ?? '',
31
+ stderr: err.stderr ?? '',
32
+ status: typeof err.code === 'number' ? err.code : 1,
33
+ };
34
+ }
35
+ }
36
+ //# sourceMappingURL=exec-file-no-throw.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec-file-no-throw.js","sourceRoot":"","sources":["../../src/utils/exec-file-no-throw.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AAsBH,0CAmBC;AAvCD,iDAAyC;AACzC,+BAAiC;AAEjC,MAAM,aAAa,GAAG,IAAA,gBAAS,EAAC,wBAAQ,CAAC,CAAC;AAS1C;;;;;;;GAOG;AACI,KAAK,UAAU,eAAe,CACnC,GAAW,EACX,IAAc,EACd,GAAY;IAEZ,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE;YACxD,GAAG;YACH,oDAAoD;YACpD,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;QACH,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;IACnE,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE;YACxB,MAAM,EAAE,GAAG,CAAC,MAAM,IAAI,EAAE;YACxB,MAAM,EAAE,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;SACpD,CAAC;IACJ,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sun-asterisk/sungen",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
4
4
  "description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,4 +1,6 @@
1
1
  import { Command } from 'commander';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
2
4
 
3
5
  export function registerAddCommand(program: Command): void {
4
6
  program
@@ -9,6 +11,10 @@ export function registerAddCommand(program: Command): void {
9
11
  .option('-c, --capture', 'Auto-capture a live-page screenshot to requirements/ui/ (requires --path)')
10
12
  .option('-f, --feature <name>', 'Add additional feature file to existing screen')
11
13
  .option('-d, --description <text>', 'Screen description')
14
+ .option('--figma <url>', "Figma share URL — fetch design node and generate spec_figma.md + ui/*.png. QUOTE the URL ('...'): the '&' in Figma links backgrounds the shell otherwise.")
15
+ .option('--refresh', 'Bypass Figma cache and re-fetch from API (use with --figma)')
16
+ .option('--scale <n>', 'PNG export scale factor (default: 2)', parseFloat, 2)
17
+ .option('--hi-res', 'Export at 4× scale (shorthand for --scale 4)')
12
18
  .action(async (options) => {
13
19
  try {
14
20
  if (options.capture && !options.path) {
@@ -16,16 +22,81 @@ export function registerAddCommand(program: Command): void {
16
22
  process.exit(1);
17
23
  }
18
24
 
19
- const { ScreenManager } = require('../../orchestrator/screen-manager');
20
- const manager = new ScreenManager();
25
+ // When --figma is set on an existing screen, skip scaffolding and
26
+ // just refresh Figma-derived files (spec_figma.md, ui/*.png).
27
+ const screenDir = path.join(process.cwd(), 'qa', 'screens', options.screen);
28
+ const featureFile = path.join(screenDir, 'features', `${options.screen}.feature`);
29
+ const screenAlreadyExists = fs.existsSync(featureFile);
21
30
 
22
- await manager.addScreen({
23
- name: options.screen,
24
- path: options.path,
25
- capture: options.capture,
26
- feature: options.feature,
27
- description: options.description,
28
- });
31
+ if (!(options.figma && screenAlreadyExists)) {
32
+ const { ScreenManager } = require('../../orchestrator/screen-manager');
33
+ const manager = new ScreenManager();
34
+
35
+ await manager.addScreen({
36
+ name: options.screen,
37
+ path: options.path,
38
+ capture: options.capture,
39
+ feature: options.feature,
40
+ description: options.description,
41
+ });
42
+ } else {
43
+ console.log(`Screen "${options.screen}" already exists — refreshing Figma assets only.`);
44
+ }
45
+
46
+ // Figma branch: run after directory scaffolding is complete
47
+ if (options.figma) {
48
+ const { FigmaScaffolder } = require('../../orchestrator/figma/figma-scaffolder');
49
+ const scale: number = options.hiRes ? 4 : (options.scale ?? 2);
50
+
51
+ try {
52
+ const result = await FigmaScaffolder.run({
53
+ screenName: options.screen,
54
+ figmaUrl: options.figma,
55
+ cwd: process.cwd(),
56
+ refresh: options.refresh ?? false,
57
+ scale,
58
+ });
59
+
60
+ console.log('\nFigma import complete:');
61
+ console.log(` ${result.specFigmaPath}`);
62
+ for (const img of result.imagePaths) {
63
+ console.log(` qa/screens/${options.screen}/requirements/${img}`);
64
+ }
65
+ if (result.specMdCreated) {
66
+ console.log(` qa/screens/${options.screen}/requirements/spec.md (stub created)`);
67
+ }
68
+
69
+ // Loud banner: Tell the calling agent to
70
+ // invoke the `sungen-figma-source` skill to synthesize the narrative sections in spec_figma.md, since
71
+ // the CLI cannot do this (synthesis is LLM-driven off the cached raw JSON).
72
+ console.log('');
73
+ console.log('==============================================================');
74
+ console.log(' NEXT STEP (REQUIRED — DO NOT SKIP)');
75
+ console.log('==============================================================');
76
+ console.log(' spec_figma.md envelope is written but narrative is EMPTY.');
77
+ console.log(' Invoke the `sungen-figma-source` skill NOW to synthesize');
78
+ console.log(' the 7 sections (Purpose / ASCII Layout / Regions / Actions /');
79
+ console.log(' Form Fields / Data Columns / Navigation) below the');
80
+ console.log(' `<!-- SYNTHESIS-BELOW -->` marker.');
81
+ console.log('');
82
+ console.log(' Read the cached raw node JSON at:');
83
+ console.log(` .sungen/figma-cache/<fileKey>/<versionId>/<nodeId>-raw.json`);
84
+ console.log(' and append the narrative below the marker in:');
85
+ console.log(` ${result.specFigmaPath}`);
86
+ console.log('==============================================================');
87
+ } catch (figmaError) {
88
+ const msg = figmaError instanceof Error ? figmaError.message : String(figmaError);
89
+ // Surface remediation hint when available
90
+ const remediation =
91
+ figmaError instanceof Error &&
92
+ 'remediation' in figmaError &&
93
+ typeof (figmaError as { remediation: unknown }).remediation === 'string'
94
+ ? `\nHint: ${(figmaError as { remediation: string }).remediation}`
95
+ : '';
96
+ console.error(`\nFigma import failed: ${msg}${remediation}`);
97
+ process.exit(1);
98
+ }
99
+ }
29
100
  } catch (error) {
30
101
  console.error('Error:', error instanceof Error ? error.message : error);
31
102
  process.exit(1);
@@ -0,0 +1,162 @@
1
+ /**
2
+ * `sungen figma` subcommand dispatcher.
3
+ *
4
+ * Subcommands:
5
+ * sungen figma auth set — prompt for PAT, validate via /v1/me, persist to .env
6
+ * sungen figma auth check — load PAT, call /v1/me, print handle + email
7
+ * sungen figma auth clear — remove FIGMA_PAT from .env
8
+ */
9
+
10
+ import { Command } from 'commander';
11
+ import * as readline from 'readline';
12
+ import { loadPat, savePat, clearPat, assertSafeToUse } from '../../tools/figma/figma-auth';
13
+ import type { FigmaMeResponse } from '../../tools/figma/figma-client-types';
14
+
15
+ const FIGMA_API_BASE = 'https://api.figma.com';
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Helpers
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /** Read a line from stdin without echoing characters (hidden input). */
22
+ function readHiddenInput(prompt: string): Promise<string> {
23
+ return new Promise((resolve) => {
24
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
25
+ // Disable echoing by replacing the _writeToOutput method
26
+ (rl as any)._writeToOutput = (str: string) => {
27
+ // Only write the prompt, not the typed characters
28
+ if (str === prompt) (rl as any).output.write(str);
29
+ };
30
+ rl.question(prompt, (answer) => {
31
+ (rl as any).output.write('\n');
32
+ rl.close();
33
+ resolve(answer.trim());
34
+ });
35
+ });
36
+ }
37
+
38
+ /**
39
+ * Call GET /v1/me with the given PAT.
40
+ * Returns parsed body on 2xx, throws with a scrubbed error message on failure.
41
+ */
42
+ async function callFigmaMe(pat: string): Promise<FigmaMeResponse> {
43
+ let response: Response;
44
+ try {
45
+ response = await fetch(`${FIGMA_API_BASE}/v1/me`, {
46
+ headers: { 'X-Figma-Token': pat },
47
+ });
48
+ } catch (err: any) {
49
+ throw new Error(`Network error reaching Figma API: ${err.message ?? 'unknown'}`);
50
+ }
51
+
52
+ if (!response.ok) {
53
+ const status = response.status;
54
+ if (status === 403 || status === 401) {
55
+ throw new Error(`Figma API returned ${status}: token is invalid or lacks read scope.`);
56
+ }
57
+ throw new Error(`Figma API returned unexpected status ${status}.`);
58
+ }
59
+
60
+ return response.json() as Promise<FigmaMeResponse>;
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Subcommand handlers
65
+ // ---------------------------------------------------------------------------
66
+
67
+ async function handleAuthSet(options: { token?: string }, cwd: string): Promise<void> {
68
+ let pat = options.token;
69
+
70
+ if (!pat) {
71
+ pat = await readHiddenInput('Enter Figma Personal Access Token: ');
72
+ }
73
+
74
+ if (!pat) {
75
+ console.error('Error: token cannot be empty.');
76
+ process.exit(1);
77
+ }
78
+
79
+ console.log('Validating token with Figma API…');
80
+ let me: FigmaMeResponse;
81
+ try {
82
+ me = await callFigmaMe(pat);
83
+ } catch (err: any) {
84
+ console.error(`Error: ${err.message}`);
85
+ process.exit(1);
86
+ }
87
+
88
+ try {
89
+ await savePat(cwd, pat);
90
+ } catch (err: any) {
91
+ console.error(`Error: ${err.message}`);
92
+ process.exit(1);
93
+ }
94
+
95
+ console.log(`Token validated and saved. Authenticated as: ${me.handle} <${me.email}>`);
96
+ }
97
+
98
+ async function handleAuthCheck(cwd: string): Promise<void> {
99
+ try {
100
+ await assertSafeToUse(cwd);
101
+ } catch (err: any) {
102
+ console.error(`Error: ${err.message}`);
103
+ process.exit(1);
104
+ }
105
+
106
+ const pat = loadPat(cwd);
107
+ if (!pat) {
108
+ console.error('Error: No FIGMA_PAT configured. Run `sungen figma auth set` first.');
109
+ process.exit(1);
110
+ }
111
+
112
+ let me: FigmaMeResponse;
113
+ try {
114
+ me = await callFigmaMe(pat);
115
+ } catch (err: any) {
116
+ console.error(`Error: ${err.message}`);
117
+ process.exit(1);
118
+ }
119
+
120
+ console.log(`Authenticated as: ${me.handle} <${me.email}>`);
121
+ }
122
+
123
+ async function handleAuthClear(cwd: string): Promise<void> {
124
+ clearPat(cwd);
125
+ console.log('FIGMA_PAT removed from .env.');
126
+ }
127
+
128
+ // ---------------------------------------------------------------------------
129
+ // Command registration
130
+ // ---------------------------------------------------------------------------
131
+
132
+ export function registerFigmaCommand(program: Command): void {
133
+ const figma = program
134
+ .command('figma')
135
+ .description('Figma integration — manage authentication and (future) asset fetching');
136
+
137
+ const auth = figma
138
+ .command('auth')
139
+ .description('Manage Figma Personal Access Token');
140
+
141
+ auth
142
+ .command('set')
143
+ .description('Validate and save a Figma PAT to .env')
144
+ .option('--token <token>', 'PAT value (for scripting; prefer interactive prompt)')
145
+ .action(async (options) => {
146
+ await handleAuthSet(options, process.cwd());
147
+ });
148
+
149
+ auth
150
+ .command('check')
151
+ .description('Verify saved PAT by calling /v1/me and print account info')
152
+ .action(async () => {
153
+ await handleAuthCheck(process.cwd());
154
+ });
155
+
156
+ auth
157
+ .command('clear')
158
+ .description('Remove FIGMA_PAT from .env')
159
+ .action(async () => {
160
+ await handleAuthClear(process.cwd());
161
+ });
162
+ }
package/src/cli/index.ts CHANGED
@@ -11,6 +11,7 @@ import { registerGenerateCommand } from './commands/generate';
11
11
  import { registerMakeauthCommand } from './commands/makeauth';
12
12
  import { registerUpdateCommand } from './commands/update';
13
13
  import { registerDeliveryCommand } from './commands/delivery';
14
+ import { registerFigmaCommand } from './commands/figma';
14
15
 
15
16
  async function main() {
16
17
  const program = new Command();
@@ -18,19 +19,20 @@ async function main() {
18
19
  program
19
20
  .name('sungen')
20
21
  .description('Deterministic E2E Test Compiler — Gherkin + Selectors → Playwright')
21
- .version('2.5.0');
22
+ .version('2.5.2');
22
23
 
23
24
  // Global options
24
25
  program
25
26
  .option('-v, --verbose', 'Enable verbose logging');
26
27
 
27
- // Register commands (6)
28
+ // Register commands (7)
28
29
  registerInitCommand(program);
29
30
  registerAddCommand(program);
30
31
  registerGenerateCommand(program);
31
32
  registerMakeauthCommand(program);
32
33
  registerUpdateCommand(program);
33
34
  registerDeliveryCommand(program);
35
+ registerFigmaCommand(program);
34
36
 
35
37
  await program.parseAsync(process.argv);
36
38
  }