@agiflowai/style-system 0.0.1

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.
@@ -0,0 +1,3251 @@
1
+ //#region rolldown:runtime
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+
23
+ //#endregion
24
+ let __modelcontextprotocol_sdk_server_index_js = require("@modelcontextprotocol/sdk/server/index.js");
25
+ __modelcontextprotocol_sdk_server_index_js = __toESM(__modelcontextprotocol_sdk_server_index_js);
26
+ let __modelcontextprotocol_sdk_types_js = require("@modelcontextprotocol/sdk/types.js");
27
+ __modelcontextprotocol_sdk_types_js = __toESM(__modelcontextprotocol_sdk_types_js);
28
+ let node_path = require("node:path");
29
+ node_path = __toESM(node_path);
30
+ let __agiflowai_aicode_utils = require("@agiflowai/aicode-utils");
31
+ __agiflowai_aicode_utils = __toESM(__agiflowai_aicode_utils);
32
+ let node_fs = require("node:fs");
33
+ node_fs = __toESM(node_fs);
34
+ let js_yaml = require("js-yaml");
35
+ js_yaml = __toESM(js_yaml);
36
+ let postcss = require("postcss");
37
+ postcss = __toESM(postcss);
38
+ let glob = require("glob");
39
+ glob = __toESM(glob);
40
+ let node_crypto = require("node:crypto");
41
+ node_crypto = __toESM(node_crypto);
42
+ let __storybook_csf_tools = require("@storybook/csf-tools");
43
+ __storybook_csf_tools = __toESM(__storybook_csf_tools);
44
+ let __tailwindcss_vite = require("@tailwindcss/vite");
45
+ __tailwindcss_vite = __toESM(__tailwindcss_vite);
46
+ let vite_plugin_singlefile = require("vite-plugin-singlefile");
47
+ vite_plugin_singlefile = __toESM(vite_plugin_singlefile);
48
+ let node_os = require("node:os");
49
+ node_os = __toESM(node_os);
50
+ let playwright = require("playwright");
51
+ playwright = __toESM(playwright);
52
+ let sharp = require("sharp");
53
+ sharp = __toESM(sharp);
54
+ let __modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
55
+ __modelcontextprotocol_sdk_server_stdio_js = __toESM(__modelcontextprotocol_sdk_server_stdio_js);
56
+
57
+ //#region src/config.ts
58
+ /**
59
+ * Default tags for identifying shared/design system components
60
+ */
61
+ const DEFAULT_SHARED_COMPONENT_TAGS = ["style-system"];
62
+ /**
63
+ * Default configuration for apps without style-system config
64
+ */
65
+ const DEFAULT_CONFIG = {
66
+ type: "tailwind",
67
+ themeProvider: "@agimonai/web-ui",
68
+ sharedComponentTags: DEFAULT_SHARED_COMPONENT_TAGS
69
+ };
70
+ /**
71
+ * Validate style-system configuration from project.json.
72
+ * Ensures required fields are present and have correct types.
73
+ *
74
+ * @param config - The config object to validate
75
+ * @param projectName - Project name for error messages
76
+ * @returns Validated DesignSystemConfig
77
+ * @throws Error if validation fails
78
+ */
79
+ function validateDesignSystemConfig(config, projectName) {
80
+ if (typeof config !== "object" || config === null) throw new Error(`[${projectName}] style-system config must be an object`);
81
+ const cfg = config;
82
+ if (!cfg.type || cfg.type !== "tailwind" && cfg.type !== "shadcn") throw new Error(`[${projectName}] style-system.type must be 'tailwind' or 'shadcn'`);
83
+ if (!cfg.themeProvider || typeof cfg.themeProvider !== "string") throw new Error(`[${projectName}] style-system.themeProvider is required and must be a string`);
84
+ if (cfg.tailwindConfig !== void 0 && typeof cfg.tailwindConfig !== "string") throw new Error(`[${projectName}] style-system.tailwindConfig must be a string`);
85
+ if (cfg.rootComponent !== void 0 && typeof cfg.rootComponent !== "string") throw new Error(`[${projectName}] style-system.rootComponent must be a string`);
86
+ if (cfg.cssFiles !== void 0) {
87
+ if (!Array.isArray(cfg.cssFiles) || !cfg.cssFiles.every((f) => typeof f === "string")) throw new Error(`[${projectName}] style-system.cssFiles must be an array of strings`);
88
+ }
89
+ if (cfg.componentLibrary !== void 0 && typeof cfg.componentLibrary !== "string") throw new Error(`[${projectName}] style-system.componentLibrary must be a string`);
90
+ if (cfg.themePath !== void 0 && typeof cfg.themePath !== "string") throw new Error(`[${projectName}] style-system.themePath must be a string`);
91
+ if (cfg.sharedComponentTags !== void 0) {
92
+ if (!Array.isArray(cfg.sharedComponentTags) || !cfg.sharedComponentTags.every((t) => typeof t === "string")) throw new Error(`[${projectName}] style-system.sharedComponentTags must be an array of strings`);
93
+ }
94
+ return config;
95
+ }
96
+ /**
97
+ * Read design system configuration from an app's project.json.
98
+ *
99
+ * @param appPath - Path to the app directory (relative or absolute)
100
+ * @returns Validated DesignSystemConfig
101
+ * @throws Error if appPath is invalid, project.json cannot be read, or config validation fails
102
+ */
103
+ async function getAppDesignSystemConfig(appPath) {
104
+ if (!appPath || typeof appPath !== "string") throw new Error("appPath is required and must be a non-empty string");
105
+ const monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
106
+ const resolvedAppPath = node_path.default.isAbsolute(appPath) ? appPath : node_path.default.join(monorepoRoot, appPath);
107
+ const projectJsonPath = node_path.default.join(resolvedAppPath, "project.json");
108
+ try {
109
+ const content = await node_fs.promises.readFile(projectJsonPath, "utf-8");
110
+ let projectJson;
111
+ try {
112
+ projectJson = JSON.parse(content);
113
+ } catch (parseError) {
114
+ throw new Error(`Invalid JSON in ${projectJsonPath}: ${parseError instanceof Error ? parseError.message : String(parseError)}`);
115
+ }
116
+ if (typeof projectJson !== "object" || projectJson === null) throw new Error(`${projectJsonPath} must contain a JSON object`);
117
+ const project = projectJson;
118
+ const projectName = typeof project.name === "string" ? project.name : node_path.default.basename(resolvedAppPath);
119
+ if (project["style-system"]) {
120
+ const validatedConfig = validateDesignSystemConfig(project["style-system"], projectName);
121
+ __agiflowai_aicode_utils.log.info(`[Config] Loaded and validated style-system config for ${projectName}`);
122
+ return validatedConfig;
123
+ }
124
+ __agiflowai_aicode_utils.log.info(`[Config] No style-system config found for ${projectName}, using defaults`);
125
+ return DEFAULT_CONFIG;
126
+ } catch (error) {
127
+ throw new Error(`Failed to read style-system config from ${projectJsonPath}: ${error instanceof Error ? error.message : String(error)}`);
128
+ }
129
+ }
130
+ /**
131
+ * Get shared component tags from toolkit.yaml or use defaults.
132
+ *
133
+ * Reads configuration from toolkit.yaml at workspace root.
134
+ * Falls back to DEFAULT_SHARED_COMPONENT_TAGS if not configured.
135
+ *
136
+ * @returns Array of tag names that identify shared components
137
+ */
138
+ async function getSharedComponentTags() {
139
+ const monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
140
+ const toolkitYamlPath = node_path.default.join(monorepoRoot, "toolkit.yaml");
141
+ try {
142
+ const content = await node_fs.promises.readFile(toolkitYamlPath, "utf-8");
143
+ const config = js_yaml.default.load(content);
144
+ if (config?.["style-system"]?.sharedComponentTags?.length) {
145
+ const tags = config["style-system"].sharedComponentTags;
146
+ if (Array.isArray(tags) && tags.every((tag) => typeof tag === "string")) {
147
+ __agiflowai_aicode_utils.log.info(`[Config] Loaded sharedComponentTags from toolkit.yaml: ${tags.join(", ")}`);
148
+ return tags;
149
+ }
150
+ __agiflowai_aicode_utils.log.warn("[Config] sharedComponentTags in toolkit.yaml is not a valid string array, using defaults");
151
+ }
152
+ } catch (error) {
153
+ if (error instanceof Error && "code" in error && error.code !== "ENOENT") __agiflowai_aicode_utils.log.warn(`[Config] Failed to parse toolkit.yaml: ${error.message}`);
154
+ }
155
+ __agiflowai_aicode_utils.log.info(`[Config] Using default sharedComponentTags: ${DEFAULT_SHARED_COMPONENT_TAGS.join(", ")}`);
156
+ return DEFAULT_SHARED_COMPONENT_TAGS;
157
+ }
158
+ /**
159
+ * Get getCssClasses tool configuration from toolkit.yaml.
160
+ *
161
+ * Reads configuration from toolkit.yaml at workspace root under
162
+ * style-system.getCssClasses key.
163
+ *
164
+ * @returns GetCssClassesConfig or undefined if not configured
165
+ */
166
+ async function getGetCssClassesConfig() {
167
+ const monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
168
+ const toolkitYamlPath = node_path.default.join(monorepoRoot, "toolkit.yaml");
169
+ try {
170
+ const content = await node_fs.promises.readFile(toolkitYamlPath, "utf-8");
171
+ const config = js_yaml.default.load(content);
172
+ if (config?.["style-system"]?.getCssClasses) {
173
+ const getCssClassesConfig = config["style-system"].getCssClasses;
174
+ if (getCssClassesConfig.customService !== void 0 && typeof getCssClassesConfig.customService !== "string") {
175
+ __agiflowai_aicode_utils.log.warn("[Config] style-system.getCssClasses.customService must be a string, ignoring");
176
+ return;
177
+ }
178
+ __agiflowai_aicode_utils.log.info(`[Config] Loaded getCssClasses config from toolkit.yaml`);
179
+ return getCssClassesConfig;
180
+ }
181
+ } catch (error) {
182
+ if (error instanceof Error && "code" in error && error.code !== "ENOENT") __agiflowai_aicode_utils.log.warn(`[Config] Failed to parse toolkit.yaml: ${error.message}`);
183
+ }
184
+ }
185
+ /**
186
+ * Get bundler service configuration from toolkit.yaml.
187
+ *
188
+ * Reads configuration from toolkit.yaml at workspace root under
189
+ * style-system.bundler key.
190
+ *
191
+ * @returns BundlerConfig or undefined if not configured
192
+ */
193
+ async function getBundlerConfig() {
194
+ const monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
195
+ const toolkitYamlPath = node_path.default.join(monorepoRoot, "toolkit.yaml");
196
+ try {
197
+ const content = await node_fs.promises.readFile(toolkitYamlPath, "utf-8");
198
+ const config = js_yaml.default.load(content);
199
+ if (config?.["style-system"]?.bundler) {
200
+ const bundlerConfig = config["style-system"].bundler;
201
+ if (bundlerConfig.customService !== void 0 && typeof bundlerConfig.customService !== "string") {
202
+ __agiflowai_aicode_utils.log.warn("[Config] style-system.bundler.customService must be a string, ignoring");
203
+ return;
204
+ }
205
+ __agiflowai_aicode_utils.log.info(`[Config] Loaded bundler config from toolkit.yaml`);
206
+ return bundlerConfig;
207
+ }
208
+ } catch (error) {
209
+ if (error instanceof Error && "code" in error && error.code !== "ENOENT") __agiflowai_aicode_utils.log.warn(`[Config] Failed to parse toolkit.yaml: ${error.message}`);
210
+ }
211
+ }
212
+
213
+ //#endregion
214
+ //#region src/services/CssClasses/BaseCSSClassesService.ts
215
+ /**
216
+ * Abstract base class for CSS class extraction services.
217
+ *
218
+ * Subclasses must implement the `extractClasses` method to provide
219
+ * framework-specific CSS class extraction logic.
220
+ *
221
+ * @example
222
+ * ```typescript
223
+ * class MyCustomCSSService extends BaseCSSClassesService {
224
+ * async extractClasses(category: CSSClassCategory, themePath: string): Promise<CSSClassesResult> {
225
+ * // Custom extraction logic
226
+ * }
227
+ * }
228
+ * ```
229
+ */
230
+ var BaseCSSClassesService = class {
231
+ config;
232
+ /**
233
+ * Creates a new CSS classes service instance
234
+ * @param config - Style system configuration from toolkit.yaml
235
+ */
236
+ constructor(config) {
237
+ this.config = config;
238
+ }
239
+ /**
240
+ * Validate that the theme path exists and is readable.
241
+ * Can be overridden by subclasses for custom validation.
242
+ *
243
+ * @param themePath - Path to validate
244
+ * @throws Error if path is invalid or unreadable
245
+ */
246
+ async validateThemePath(themePath) {
247
+ try {
248
+ await node_fs.promises.access(themePath);
249
+ } catch {
250
+ throw new Error(`Theme file not found or not readable: ${themePath}`);
251
+ }
252
+ }
253
+ };
254
+
255
+ //#endregion
256
+ //#region src/services/CssClasses/TailwindCSSClassesService.ts
257
+ /**
258
+ * Tailwind CSS class extraction service.
259
+ *
260
+ * Extracts CSS classes from Tailwind theme files by parsing CSS variables
261
+ * using postcss AST and generating corresponding utility class names.
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * const service = new TailwindCSSClassesService(config);
266
+ * const result = await service.extractClasses('colors', '/path/to/theme.css');
267
+ * console.log(result.classes.colors); // Array of color utility classes
268
+ * ```
269
+ */
270
+ var TailwindCSSClassesService = class extends BaseCSSClassesService {
271
+ /**
272
+ * Creates a new TailwindCSSClassesService instance
273
+ * @param config - Style system configuration from toolkit.yaml
274
+ */
275
+ constructor(config) {
276
+ super(config);
277
+ }
278
+ /**
279
+ * Get the CSS framework identifier
280
+ * @returns Framework identifier string 'tailwind'
281
+ */
282
+ getFrameworkId() {
283
+ return "tailwind";
284
+ }
285
+ /**
286
+ * Extract Tailwind CSS classes from a theme file.
287
+ *
288
+ * Uses postcss to parse the CSS AST and safely extract variable declarations,
289
+ * then generates corresponding Tailwind utility classes (e.g., bg-*, text-*, border-*).
290
+ *
291
+ * Note: If an unrecognized category is passed, the method returns a result with
292
+ * empty classes object. Use valid categories: 'colors', 'typography', 'spacing', 'effects', 'all'.
293
+ *
294
+ * @param category - Category filter ('colors', 'typography', 'spacing', 'effects', 'all')
295
+ * @param themePath - Absolute path to the theme CSS file
296
+ * @returns Promise resolving to extracted CSS classes organized by category
297
+ * @throws Error if theme file cannot be read or parsed
298
+ */
299
+ async extractClasses(category, themePath) {
300
+ try {
301
+ await this.validateThemePath(themePath);
302
+ const resolvedThemePath = node_path.default.resolve(themePath);
303
+ const themeContent = await node_fs.promises.readFile(resolvedThemePath, "utf-8");
304
+ const variables = await this.extractVariablesWithPostCSS(themeContent);
305
+ return this.generateClassesFromVariables(variables, category);
306
+ } catch (error) {
307
+ throw new Error(`Failed to extract classes from theme file ${themePath}: ${error instanceof Error ? error.message : String(error)}`);
308
+ }
309
+ }
310
+ /**
311
+ * Extract CSS variables with their values from theme content using postcss AST.
312
+ *
313
+ * Walks the CSS AST to find all custom property declarations (--*),
314
+ * handling multi-line values, comments, and any CSS formatting.
315
+ *
316
+ * @param themeContent - Raw CSS content from theme file
317
+ * @returns Promise resolving to Map of variable names (without --) to their values
318
+ *
319
+ * @example
320
+ * ```typescript
321
+ * // Handles standard declarations
322
+ * // --color-primary: #3b82f6;
323
+ *
324
+ * // Handles multi-line declarations
325
+ * // --shadow-lg:
326
+ * // 0 10px 15px -3px rgba(0, 0, 0, 0.1),
327
+ * // 0 4px 6px -4px rgba(0, 0, 0, 0.1);
328
+ *
329
+ * // Handles compressed CSS
330
+ * // --color-primary:#3b82f6;--color-secondary:#10b981;
331
+ * ```
332
+ */
333
+ async extractVariablesWithPostCSS(themeContent) {
334
+ const variables = /* @__PURE__ */ new Map();
335
+ try {
336
+ postcss.default.parse(themeContent).walkDecls((decl) => {
337
+ if (decl.prop.startsWith("--")) {
338
+ const varName = decl.prop.slice(2);
339
+ const varValue = decl.value.trim();
340
+ if (!variables.has(varName)) variables.set(varName, varValue);
341
+ }
342
+ });
343
+ } catch (error) {
344
+ throw new Error(`Failed to parse CSS content: ${error instanceof Error ? error.message : String(error)}`);
345
+ }
346
+ return variables;
347
+ }
348
+ /**
349
+ * Generate utility classes with actual values from CSS variables.
350
+ *
351
+ * Maps CSS variable naming conventions to Tailwind utility classes:
352
+ * - color-* → bg-*, text-*, border-*, ring-*
353
+ * - sidebar* → bg-*, text-*, border-*
354
+ * - text-* → text-* (typography)
355
+ * - font-* → font-* (typography)
356
+ * - space-* → p-*, m-*, gap-*
357
+ * - shadow-* → shadow-*
358
+ *
359
+ * Note: If the variables Map is empty, returns a result with empty arrays
360
+ * for all requested categories.
361
+ *
362
+ * @param variables - Map of CSS variable names to values
363
+ * @param category - Category filter for which classes to generate
364
+ * @returns CSSClassesResult with organized classes by category
365
+ *
366
+ * @example
367
+ * ```typescript
368
+ * const variables = new Map([
369
+ * ['color-primary', '#3b82f6'],
370
+ * ['shadow-md', '0 4px 6px rgba(0,0,0,0.1)']
371
+ * ]);
372
+ * const result = generateClassesFromVariables(variables, 'colors');
373
+ * // Returns:
374
+ * // {
375
+ * // category: 'colors',
376
+ * // classes: {
377
+ * // colors: [
378
+ * // { class: 'bg-primary', value: '#3b82f6' },
379
+ * // { class: 'text-primary', value: '#3b82f6' },
380
+ * // { class: 'border-primary', value: '#3b82f6' },
381
+ * // { class: 'ring-primary', value: '#3b82f6' }
382
+ * // ]
383
+ * // },
384
+ * // totalClasses: 4
385
+ * // }
386
+ * ```
387
+ */
388
+ generateClassesFromVariables(variables, category) {
389
+ const colorClasses = [];
390
+ const typographyClasses = [];
391
+ const spacingClasses = [];
392
+ const effectsClasses = [];
393
+ for (const [varName, varValue] of variables.entries()) {
394
+ if (category === "all" || category === "colors") {
395
+ if (varName.startsWith("color-")) {
396
+ const colorName = varName.replace("color-", "");
397
+ colorClasses.push({
398
+ class: `bg-${colorName}`,
399
+ value: varValue
400
+ }, {
401
+ class: `text-${colorName}`,
402
+ value: varValue
403
+ }, {
404
+ class: `border-${colorName}`,
405
+ value: varValue
406
+ }, {
407
+ class: `ring-${colorName}`,
408
+ value: varValue
409
+ });
410
+ } else if (varName.startsWith("sidebar")) colorClasses.push({
411
+ class: `bg-${varName}`,
412
+ value: varValue
413
+ }, {
414
+ class: `text-${varName}`,
415
+ value: varValue
416
+ }, {
417
+ class: `border-${varName}`,
418
+ value: varValue
419
+ });
420
+ }
421
+ if (category === "all" || category === "typography") {
422
+ if (varName.startsWith("text-")) typographyClasses.push({
423
+ class: varName,
424
+ value: varValue
425
+ });
426
+ if (varName.startsWith("font-")) {
427
+ const fontName = varName.replace("font-", "");
428
+ if (fontName.startsWith("weight-")) typographyClasses.push({
429
+ class: `font-${fontName.replace("weight-", "")}`,
430
+ value: varValue
431
+ });
432
+ else typographyClasses.push({
433
+ class: `font-${fontName}`,
434
+ value: varValue
435
+ });
436
+ }
437
+ }
438
+ if (category === "all" || category === "spacing") {
439
+ if (varName.startsWith("space-")) {
440
+ const spaceName = varName.replace("space-", "");
441
+ const calcValue = `calc(var(--${varName}) * 1)`;
442
+ spacingClasses.push({
443
+ class: `p-${spaceName}`,
444
+ value: calcValue
445
+ }, {
446
+ class: `m-${spaceName}`,
447
+ value: calcValue
448
+ }, {
449
+ class: `gap-${spaceName}`,
450
+ value: calcValue
451
+ });
452
+ } else if (varName === "spacing") spacingClasses.push({
453
+ class: "space",
454
+ value: varValue
455
+ });
456
+ }
457
+ if (category === "all" || category === "effects") {
458
+ if (varName.startsWith("shadow-")) {
459
+ const shadowName = varName.replace("shadow-", "");
460
+ effectsClasses.push({
461
+ class: `shadow-${shadowName}`,
462
+ value: varValue
463
+ });
464
+ }
465
+ }
466
+ }
467
+ const result = {
468
+ category,
469
+ classes: {},
470
+ totalClasses: colorClasses.length + typographyClasses.length + spacingClasses.length + effectsClasses.length
471
+ };
472
+ if (category === "all" || category === "colors") result.classes.colors = colorClasses;
473
+ if (category === "all" || category === "typography") result.classes.typography = typographyClasses;
474
+ if (category === "all" || category === "spacing") result.classes.spacing = spacingClasses;
475
+ if (category === "all" || category === "effects") result.classes.effects = effectsClasses;
476
+ return result;
477
+ }
478
+ };
479
+
480
+ //#endregion
481
+ //#region src/services/CssClasses/types.ts
482
+ /**
483
+ * Default configuration values
484
+ */
485
+ const DEFAULT_STYLE_SYSTEM_CONFIG = { cssFramework: "tailwind" };
486
+
487
+ //#endregion
488
+ //#region src/services/CssClasses/CSSClassesServiceFactory.ts
489
+ /** Valid file extensions for custom service modules */
490
+ const VALID_SERVICE_EXTENSIONS$1 = [
491
+ ".ts",
492
+ ".js",
493
+ ".mjs",
494
+ ".cjs"
495
+ ];
496
+ /**
497
+ * Factory for creating CSS classes service instances.
498
+ *
499
+ * Supports built-in frameworks (tailwind) and custom service implementations
500
+ * loaded dynamically from user-specified paths.
501
+ *
502
+ * @example
503
+ * ```typescript
504
+ * const factory = new CSSClassesServiceFactory();
505
+ * const service = await factory.createService({ cssFramework: 'tailwind' });
506
+ * const classes = await service.extractClasses('colors', '/path/to/theme.css');
507
+ * ```
508
+ */
509
+ var CSSClassesServiceFactory = class {
510
+ /**
511
+ * Create a CSS classes service based on configuration.
512
+ *
513
+ * @param config - Style system configuration (defaults to tailwind)
514
+ * @returns Promise resolving to a CSS classes service instance
515
+ * @throws Error if framework is unknown or custom service cannot be loaded
516
+ */
517
+ async createService(config = {}) {
518
+ const resolvedConfig = {
519
+ ...DEFAULT_STYLE_SYSTEM_CONFIG,
520
+ ...config
521
+ };
522
+ if (resolvedConfig.customServicePath) return this.loadCustomService(resolvedConfig);
523
+ return this.createBuiltInService(resolvedConfig);
524
+ }
525
+ /**
526
+ * Create a built-in CSS classes service based on framework identifier.
527
+ *
528
+ * @param config - Resolved style system configuration
529
+ * @returns CSS classes service instance
530
+ * @throws Error if framework is not supported
531
+ */
532
+ createBuiltInService(config) {
533
+ switch (config.cssFramework) {
534
+ case "tailwind": return new TailwindCSSClassesService(config);
535
+ default: throw new Error(`Unsupported CSS framework: ${config.cssFramework}. Supported frameworks: tailwind. Use customServicePath to provide a custom implementation.`);
536
+ }
537
+ }
538
+ /**
539
+ * Load a custom CSS classes service from user-specified path.
540
+ *
541
+ * The custom service must export a class that extends BaseCSSClassesService.
542
+ *
543
+ * @param config - Configuration with customServicePath set
544
+ * @returns Promise resolving to custom service instance
545
+ * @throws Error if service cannot be loaded or is invalid
546
+ */
547
+ async loadCustomService(config) {
548
+ const servicePath = config.customServicePath;
549
+ if (!servicePath) throw new Error("customServicePath is required for custom service loading");
550
+ const monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
551
+ const resolvedPath = node_path.default.resolve(monorepoRoot, servicePath);
552
+ const normalizedWorkspaceRoot = node_path.default.resolve(monorepoRoot);
553
+ if (!resolvedPath.startsWith(normalizedWorkspaceRoot + node_path.default.sep)) throw new Error(`Security error: customServicePath "${servicePath}" resolves outside workspace root`);
554
+ const ext = node_path.default.extname(resolvedPath).toLowerCase();
555
+ if (!VALID_SERVICE_EXTENSIONS$1.includes(ext)) throw new Error(`Invalid file extension "${ext}" for customServicePath. Expected one of: ${VALID_SERVICE_EXTENSIONS$1.join(", ")}`);
556
+ __agiflowai_aicode_utils.log.info(`[CSSClassesServiceFactory] Loading custom CSS service from: ${resolvedPath}`);
557
+ try {
558
+ const customModule = await import(resolvedPath);
559
+ const ServiceClass = customModule.default || customModule.CSSClassesService || customModule.CustomCSSClassesService;
560
+ if (!ServiceClass) throw new Error(`Custom service module at ${resolvedPath} must export a default class, CSSClassesService, or CustomCSSClassesService that extends BaseCSSClassesService`);
561
+ const instance = new ServiceClass(config);
562
+ if (!(instance instanceof BaseCSSClassesService)) throw new Error(`Custom service at ${resolvedPath} must extend BaseCSSClassesService`);
563
+ __agiflowai_aicode_utils.log.info(`[CSSClassesServiceFactory] Custom CSS service loaded successfully`);
564
+ return instance;
565
+ } catch (error) {
566
+ if (error instanceof Error && error.message.includes("BaseCSSClassesService")) throw error;
567
+ throw new Error(`Failed to load custom CSS classes service from ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`);
568
+ }
569
+ }
570
+ };
571
+
572
+ //#endregion
573
+ //#region src/tools/GetCSSClassesTool.ts
574
+ /**
575
+ * Valid CSS class category values
576
+ */
577
+ const VALID_CATEGORIES = [
578
+ "colors",
579
+ "typography",
580
+ "spacing",
581
+ "effects",
582
+ "all"
583
+ ];
584
+ /**
585
+ * Type guard to validate category input
586
+ * @param value - Value to check
587
+ * @returns True if value is a valid CSSClassCategory
588
+ */
589
+ function isValidCategory(value) {
590
+ return VALID_CATEGORIES.includes(value);
591
+ }
592
+ /**
593
+ * MCP Tool for extracting CSS classes from theme files.
594
+ *
595
+ * Uses the CSSClassesServiceFactory to create the appropriate service
596
+ * based on configuration, supporting Tailwind and custom CSS frameworks.
597
+ *
598
+ * @example
599
+ * ```typescript
600
+ * const tool = new GetCSSClassesTool();
601
+ * const result = await tool.execute({ category: 'colors' });
602
+ * ```
603
+ */
604
+ var GetCSSClassesTool = class GetCSSClassesTool {
605
+ static TOOL_NAME = "get_css_classes";
606
+ static CSS_REUSE_INSTRUCTION = "IMPORTANT: Always reuse these existing CSS classes from the theme as much as possible instead of creating custom styles. This ensures design consistency and reduces CSS bloat.";
607
+ serviceFactory;
608
+ service = null;
609
+ defaultThemePath;
610
+ /**
611
+ * Creates a new GetCSSClassesTool instance
612
+ * @param defaultThemePath - Default path to theme CSS file (relative to workspace root)
613
+ */
614
+ constructor(defaultThemePath = "packages/frontend/web-theme/src/agimon-theme.css") {
615
+ this.serviceFactory = new CSSClassesServiceFactory();
616
+ this.defaultThemePath = defaultThemePath;
617
+ }
618
+ /**
619
+ * Returns the tool definition for MCP registration
620
+ * @returns Tool definition with name, description, and input schema
621
+ */
622
+ getDefinition() {
623
+ return {
624
+ name: GetCSSClassesTool.TOOL_NAME,
625
+ description: "Extract and return all supported CSS classes from the theme file. Call this tool BEFORE writing any component styles or class names to ensure you use existing theme classes.",
626
+ inputSchema: {
627
+ type: "object",
628
+ properties: {
629
+ category: {
630
+ type: "string",
631
+ enum: [
632
+ "colors",
633
+ "typography",
634
+ "spacing",
635
+ "effects",
636
+ "all"
637
+ ],
638
+ description: "Category filter: 'colors', 'typography', 'spacing', 'effects', 'all' (default)"
639
+ },
640
+ appPath: {
641
+ type: "string",
642
+ description: "Optional app path (relative or absolute) to read theme path from project.json style-system config (e.g., \"apps/agiflow-app\")"
643
+ }
644
+ },
645
+ additionalProperties: false
646
+ }
647
+ };
648
+ }
649
+ /**
650
+ * Executes the CSS class extraction
651
+ * @param input - Tool input parameters
652
+ * @returns CallToolResult with extracted CSS classes or error
653
+ */
654
+ async execute(input) {
655
+ try {
656
+ const category = input.category || "all";
657
+ if (!isValidCategory(category)) throw new Error(`Invalid category: '${category}'. Must be one of: ${VALID_CATEGORIES.join(", ")}`);
658
+ const themePath = await this.resolveThemePath(input.appPath);
659
+ if (!this.service) {
660
+ const toolkitConfig = await getGetCssClassesConfig();
661
+ this.service = await this.serviceFactory.createService({ customServicePath: toolkitConfig?.customService });
662
+ }
663
+ const result = await this.service.extractClasses(category, themePath);
664
+ return { content: [{
665
+ type: "text",
666
+ text: `${GetCSSClassesTool.CSS_REUSE_INSTRUCTION}\n\n${JSON.stringify(result, null, 2)}`
667
+ }] };
668
+ } catch (error) {
669
+ return {
670
+ content: [{
671
+ type: "text",
672
+ text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`
673
+ }],
674
+ isError: true
675
+ };
676
+ }
677
+ }
678
+ /**
679
+ * Resolves the theme file path based on app configuration or defaults.
680
+ *
681
+ * Resolution strategy:
682
+ * 1. If appPath provided, read themePath from project.json style-system config
683
+ * 2. themePath is resolved relative to the app directory (where project.json is)
684
+ * 3. Fall back to default theme path if not configured
685
+ *
686
+ * @param appPath - Optional app path to read config from
687
+ * @returns Absolute path to the theme file
688
+ */
689
+ async resolveThemePath(appPath) {
690
+ const workspaceRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
691
+ if (appPath) {
692
+ const config = await getAppDesignSystemConfig(appPath);
693
+ if (config.themePath) {
694
+ const resolvedAppPath = node_path.default.isAbsolute(appPath) ? appPath : node_path.default.join(workspaceRoot, appPath);
695
+ return node_path.default.resolve(resolvedAppPath, config.themePath);
696
+ }
697
+ }
698
+ return node_path.default.resolve(workspaceRoot, this.defaultThemePath);
699
+ }
700
+ };
701
+
702
+ //#endregion
703
+ //#region src/services/StoriesIndexService/StoriesIndexService.ts
704
+ var StoriesIndexService = class {
705
+ componentIndex = /* @__PURE__ */ new Map();
706
+ monorepoRoot;
707
+ initialized = false;
708
+ /** Last initialization result for error reporting */
709
+ lastInitResult = null;
710
+ /**
711
+ * Creates a new StoriesIndexService instance
712
+ */
713
+ constructor() {
714
+ this.monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
715
+ }
716
+ /**
717
+ * Initialize the index by scanning all .stories files.
718
+ * @returns Initialization result with success/failure statistics
719
+ */
720
+ async initialize() {
721
+ if (this.initialized && this.lastInitResult) return this.lastInitResult;
722
+ __agiflowai_aicode_utils.log.info("[StoriesIndexService] Initializing story index...");
723
+ const storyFiles = await (0, glob.glob)("**/*.stories.{ts,tsx}", {
724
+ cwd: this.monorepoRoot,
725
+ ignore: [
726
+ "**/node_modules/**",
727
+ "**/dist/**",
728
+ "**/.next/**",
729
+ "**/build/**"
730
+ ],
731
+ absolute: true
732
+ });
733
+ __agiflowai_aicode_utils.log.info(`[StoriesIndexService] Found ${storyFiles.length} story files`);
734
+ const failures = [];
735
+ let successCount = 0;
736
+ for (const filePath of storyFiles) try {
737
+ await this.indexStoryFile(filePath);
738
+ successCount++;
739
+ } catch (error) {
740
+ const errorMessage = error instanceof Error ? error.message : String(error);
741
+ __agiflowai_aicode_utils.log.error(`[StoriesIndexService] Error indexing ${filePath}: ${errorMessage}`);
742
+ failures.push({
743
+ filePath,
744
+ error: errorMessage
745
+ });
746
+ }
747
+ this.initialized = true;
748
+ this.lastInitResult = {
749
+ totalFiles: storyFiles.length,
750
+ successCount,
751
+ failureCount: failures.length,
752
+ failures
753
+ };
754
+ if (failures.length > 0) __agiflowai_aicode_utils.log.warn(`[StoriesIndexService] Indexed ${successCount}/${storyFiles.length} files successfully. ${failures.length} files failed.`);
755
+ else __agiflowai_aicode_utils.log.info(`[StoriesIndexService] Indexed ${this.componentIndex.size} components successfully`);
756
+ return this.lastInitResult;
757
+ }
758
+ /**
759
+ * Get the last initialization result.
760
+ * @returns Initialization result or null if not initialized
761
+ */
762
+ getLastInitResult() {
763
+ return this.lastInitResult;
764
+ }
765
+ /**
766
+ * Index a single story file using @storybook/csf-tools.
767
+ *
768
+ * Uses the official Storybook CSF parser which handles all CSF formats
769
+ * including TypeScript satisfies/as expressions.
770
+ */
771
+ async indexStoryFile(filePath) {
772
+ const content = await node_fs.promises.readFile(filePath, "utf-8");
773
+ const fileHash = this.hashContent(content);
774
+ const csf = (0, __storybook_csf_tools.loadCsf)(content, {
775
+ fileName: filePath,
776
+ makeTitle: (title) => title
777
+ });
778
+ await csf.parse();
779
+ if (!csf.meta?.title) {
780
+ __agiflowai_aicode_utils.log.warn(`[StoriesIndexService] No valid meta title in ${filePath}`);
781
+ return;
782
+ }
783
+ const stories = csf.stories.map((story) => story.name).filter((name) => !!name);
784
+ const tags = Array.isArray(csf.meta.tags) ? csf.meta.tags.filter((t) => typeof t === "string") : [];
785
+ const description = this.extractDescription(content, csf.meta);
786
+ const meta = {
787
+ title: csf.meta.title,
788
+ tags
789
+ };
790
+ const componentInfo = {
791
+ title: meta.title,
792
+ filePath,
793
+ fileHash,
794
+ tags,
795
+ stories,
796
+ meta,
797
+ description
798
+ };
799
+ this.componentIndex.set(meta.title, componentInfo);
800
+ }
801
+ /**
802
+ * Extract component description from file header JSDoc or meta.parameters.docs.description.
803
+ *
804
+ * Priority:
805
+ * 1. meta.parameters.docs.description.component (Storybook standard)
806
+ * 2. File header JSDoc comment (first block comment in file)
807
+ *
808
+ * @param content - Raw file content
809
+ * @param meta - Parsed meta object from csf-tools
810
+ * @returns Description string or undefined
811
+ */
812
+ extractDescription(content, meta) {
813
+ const docsDescription = (((meta?.parameters)?.docs)?.description)?.component;
814
+ if (typeof docsDescription === "string" && docsDescription.trim()) return docsDescription.trim();
815
+ const jsDocMatch = content.match(/^\s*\/\*\*\s*([\s\S]*?)\s*\*\//);
816
+ if (jsDocMatch?.[1]) {
817
+ const description = jsDocMatch[1].split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim()).filter((line) => !line.startsWith("@")).join(" ").replace(/\s+/g, " ").trim();
818
+ if (description) return description;
819
+ }
820
+ }
821
+ /**
822
+ * Hash file content for cache invalidation
823
+ */
824
+ hashContent(content) {
825
+ return (0, node_crypto.createHash)("sha256").update(content).digest("hex");
826
+ }
827
+ /**
828
+ * Get all components filtered by tags
829
+ * @param tags - Optional array of tags to filter by
830
+ * @returns Array of matching components
831
+ */
832
+ getComponentsByTags(tags) {
833
+ const components = Array.from(this.componentIndex.values());
834
+ if (!tags || tags.length === 0) return components;
835
+ return components.filter((component) => tags.some((tag) => component.tags.includes(tag)));
836
+ }
837
+ /**
838
+ * Get component by title
839
+ * @param title - Exact title to match (e.g., "Components/Button")
840
+ * @returns Component info or undefined
841
+ */
842
+ getComponentByTitle(title) {
843
+ return this.componentIndex.get(title);
844
+ }
845
+ /**
846
+ * Find component by partial name match
847
+ * @param name - Partial name to search for
848
+ * @returns First matching component or undefined
849
+ */
850
+ findComponentByName(name) {
851
+ const lowerName = name.toLowerCase();
852
+ for (const component of this.componentIndex.values()) if ((component.title.split("/").pop() || component.title).toLowerCase() === lowerName) return component;
853
+ for (const component of this.componentIndex.values()) if ((component.title.split("/").pop() || component.title).toLowerCase().includes(lowerName)) return component;
854
+ }
855
+ /**
856
+ * Refresh a specific file if it has changed
857
+ * @param filePath - Absolute path to story file
858
+ * @returns True if file was updated, false if unchanged
859
+ */
860
+ async refreshFile(filePath) {
861
+ const content = await node_fs.promises.readFile(filePath, "utf-8");
862
+ const newHash = this.hashContent(content);
863
+ const existingComponent = Array.from(this.componentIndex.values()).find((c) => c.filePath === filePath);
864
+ if (existingComponent && existingComponent.fileHash === newHash) return false;
865
+ await this.indexStoryFile(filePath);
866
+ return true;
867
+ }
868
+ /**
869
+ * Get all indexed components
870
+ * @returns Array of all component info objects
871
+ */
872
+ getAllComponents() {
873
+ return Array.from(this.componentIndex.values());
874
+ }
875
+ /**
876
+ * Clear the index (useful for testing)
877
+ */
878
+ clear() {
879
+ this.componentIndex.clear();
880
+ this.initialized = false;
881
+ this.lastInitResult = null;
882
+ }
883
+ /**
884
+ * Get all unique tags from indexed components
885
+ * @returns Sorted array of unique tag names
886
+ */
887
+ getAllTags() {
888
+ const tagSet = /* @__PURE__ */ new Set();
889
+ for (const component of this.componentIndex.values()) for (const tag of component.tags) tagSet.add(tag);
890
+ return Array.from(tagSet).sort();
891
+ }
892
+ };
893
+
894
+ //#endregion
895
+ //#region src/services/AppComponentsService/types.ts
896
+ /**
897
+ * Default configuration values.
898
+ */
899
+ const DEFAULT_APP_COMPONENTS_CONFIG = { pageSize: 50 };
900
+
901
+ //#endregion
902
+ //#region src/services/AppComponentsService/AppComponentsService.ts
903
+ /**
904
+ * AppComponentsService handles listing app-specific and package components.
905
+ *
906
+ * Detects components by file path (within app directory) and resolves
907
+ * workspace dependencies to find package components.
908
+ *
909
+ * @example
910
+ * ```typescript
911
+ * const service = new AppComponentsService();
912
+ * const result = await service.listComponents({ appPath: 'apps/my-app' });
913
+ * // Returns: { app: 'my-app', appComponents: ['Button'], packageComponents: {...}, pagination: {...} }
914
+ * ```
915
+ */
916
+ var AppComponentsService = class {
917
+ config;
918
+ /**
919
+ * Creates a new AppComponentsService instance.
920
+ * @param config - Service configuration options
921
+ */
922
+ constructor(config = {}) {
923
+ this.config = {
924
+ ...DEFAULT_APP_COMPONENTS_CONFIG,
925
+ ...config
926
+ };
927
+ }
928
+ /**
929
+ * List app-specific and package components for a given application.
930
+ * @param input - Object containing appPath and optional cursor for pagination
931
+ * @returns Promise resolving to paginated component list
932
+ * @throws Error if input validation fails, app path does not exist, or stories index fails to initialize
933
+ */
934
+ async listComponents(input) {
935
+ if (!input.appPath || typeof input.appPath !== "string") throw new Error("appPath is required and must be a non-empty string");
936
+ if (input.cursor !== void 0 && typeof input.cursor !== "string") throw new Error("cursor must be a string");
937
+ const { appPath, cursor } = input;
938
+ const { offset } = cursor ? this.decodeCursor(cursor) : { offset: 0 };
939
+ const monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
940
+ const resolvedAppPath = node_path.default.isAbsolute(appPath) ? appPath : node_path.default.join(monorepoRoot, appPath);
941
+ try {
942
+ await node_fs.promises.access(resolvedAppPath);
943
+ } catch {
944
+ throw new Error(`App path does not exist: ${resolvedAppPath}`);
945
+ }
946
+ const appName = await this.getAppName(resolvedAppPath);
947
+ const workspaceDependencies = await this.getWorkspaceDependencies(resolvedAppPath);
948
+ __agiflowai_aicode_utils.log.info(`[AppComponentsService] Found ${workspaceDependencies.length} workspace dependencies for ${appName}`);
949
+ const packageMap = await this.buildPackageMap(monorepoRoot);
950
+ const storiesIndex = new StoriesIndexService();
951
+ await storiesIndex.initialize();
952
+ const allComponents = storiesIndex.getAllComponents();
953
+ const { appComponentsArray, packageComponents, totalPackageComponents } = this.categorizeComponents(allComponents, resolvedAppPath, workspaceDependencies, packageMap);
954
+ const totalComponents = appComponentsArray.length + totalPackageComponents;
955
+ const { paginatedAppComponents, paginatedPackageComponents, totalReturned } = this.paginateComponents(appComponentsArray, packageComponents, offset);
956
+ const hasMore = offset + totalReturned < totalComponents;
957
+ const result = {
958
+ app: appName,
959
+ appComponents: paginatedAppComponents,
960
+ packageComponents: paginatedPackageComponents,
961
+ pagination: {
962
+ offset,
963
+ pageSize: this.config.pageSize,
964
+ totalComponents,
965
+ hasMore
966
+ }
967
+ };
968
+ if (hasMore) result.nextCursor = this.encodeCursor(offset + totalReturned);
969
+ __agiflowai_aicode_utils.log.info(`[AppComponentsService] Page ${Math.floor(offset / this.config.pageSize) + 1}: Returned ${totalReturned} of ${totalComponents} total components (hasMore: ${hasMore})`);
970
+ return result;
971
+ }
972
+ /**
973
+ * Get app name from project.json.
974
+ * @param resolvedAppPath - Absolute path to the app directory
975
+ * @returns App name from project.json or directory basename as fallback
976
+ */
977
+ async getAppName(resolvedAppPath) {
978
+ const projectJsonPath = node_path.default.join(resolvedAppPath, "project.json");
979
+ let appName = node_path.default.basename(resolvedAppPath);
980
+ try {
981
+ appName = JSON.parse(await node_fs.promises.readFile(projectJsonPath, "utf-8")).name || appName;
982
+ } catch (error) {
983
+ __agiflowai_aicode_utils.log.warn(`[AppComponentsService] Could not read project.json for ${resolvedAppPath}:`, error);
984
+ }
985
+ return appName;
986
+ }
987
+ /**
988
+ * Get workspace dependencies from package.json.
989
+ * @param resolvedAppPath - Absolute path to the app directory
990
+ * @returns Array of workspace dependency package names
991
+ */
992
+ async getWorkspaceDependencies(resolvedAppPath) {
993
+ const packageJsonPath = node_path.default.join(resolvedAppPath, "package.json");
994
+ try {
995
+ const packageJson = JSON.parse(await node_fs.promises.readFile(packageJsonPath, "utf-8"));
996
+ const allDeps = {
997
+ ...packageJson.dependencies,
998
+ ...packageJson.devDependencies
999
+ };
1000
+ return Object.entries(allDeps).filter(([_name, version]) => typeof version === "string" && version.startsWith("workspace:")).map(([name]) => name);
1001
+ } catch (error) {
1002
+ __agiflowai_aicode_utils.log.warn(`[AppComponentsService] Could not read package.json for ${resolvedAppPath}:`, error);
1003
+ return [];
1004
+ }
1005
+ }
1006
+ /**
1007
+ * Find all package.json files in the monorepo and build a map of package name → directory path.
1008
+ * @param monorepoRoot - The root directory of the monorepo
1009
+ * @returns Promise resolving to a Map where keys are package names and values are directory paths
1010
+ * @throws Error if scanning for package.json files fails
1011
+ */
1012
+ async buildPackageMap(monorepoRoot) {
1013
+ const packageMap = /* @__PURE__ */ new Map();
1014
+ let packageJsonFiles;
1015
+ try {
1016
+ packageJsonFiles = await (0, glob.glob)("**/package.json", {
1017
+ cwd: monorepoRoot,
1018
+ ignore: [
1019
+ "**/node_modules/**",
1020
+ "**/dist/**",
1021
+ "**/.next/**",
1022
+ "**/build/**"
1023
+ ],
1024
+ absolute: true
1025
+ });
1026
+ } catch (error) {
1027
+ throw new Error(`Failed to scan for package.json files in ${monorepoRoot}: ${error instanceof Error ? error.message : String(error)}`);
1028
+ }
1029
+ for (const pkgJsonPath of packageJsonFiles) try {
1030
+ const content = await node_fs.promises.readFile(pkgJsonPath, "utf-8");
1031
+ const pkgJson = JSON.parse(content);
1032
+ if (pkgJson.name) {
1033
+ const pkgDir = node_path.default.dirname(pkgJsonPath);
1034
+ packageMap.set(pkgJson.name, pkgDir);
1035
+ }
1036
+ } catch (error) {
1037
+ __agiflowai_aicode_utils.log.debug(`[AppComponentsService] Skipping invalid package.json at ${pkgJsonPath}:`, error);
1038
+ }
1039
+ return packageMap;
1040
+ }
1041
+ /**
1042
+ * Categorize components into app-specific and package components.
1043
+ * App components are detected by file path (within app directory).
1044
+ * Package components are matched to workspace dependencies.
1045
+ *
1046
+ * @param allComponents - All components from stories index
1047
+ * @param resolvedAppPath - Absolute path to the app directory
1048
+ * @param workspaceDependencies - List of workspace dependency package names
1049
+ * @param packageMap - Map of package name to directory path
1050
+ * @returns Categorized components with totals
1051
+ */
1052
+ categorizeComponents(allComponents, resolvedAppPath, workspaceDependencies, packageMap) {
1053
+ const appComponentsMap = /* @__PURE__ */ new Map();
1054
+ const packageComponentsMap = {};
1055
+ for (const component of allComponents) {
1056
+ const componentName = component.title.split("/").pop() || component.title;
1057
+ const componentBrief = {
1058
+ name: componentName,
1059
+ ...component.description && { description: component.description }
1060
+ };
1061
+ if (component.filePath.startsWith(resolvedAppPath)) appComponentsMap.set(componentName, componentBrief);
1062
+ for (const dep of workspaceDependencies) {
1063
+ const packageDir = packageMap.get(dep);
1064
+ if (packageDir && component.filePath.startsWith(packageDir)) {
1065
+ if (!packageComponentsMap[dep]) packageComponentsMap[dep] = /* @__PURE__ */ new Map();
1066
+ packageComponentsMap[dep].set(componentName, componentBrief);
1067
+ }
1068
+ }
1069
+ }
1070
+ const packageComponents = {};
1071
+ for (const [pkg, componentsMap] of Object.entries(packageComponentsMap)) packageComponents[pkg] = Array.from(componentsMap.values()).sort((a, b) => a.name.localeCompare(b.name));
1072
+ return {
1073
+ appComponentsArray: Array.from(appComponentsMap.values()).sort((a, b) => a.name.localeCompare(b.name)),
1074
+ packageComponents,
1075
+ totalPackageComponents: Object.values(packageComponents).reduce((sum, arr) => sum + arr.length, 0)
1076
+ };
1077
+ }
1078
+ /**
1079
+ * Apply pagination to component lists.
1080
+ *
1081
+ * Pagination strategy:
1082
+ * 1. First, fill page with app components starting from offset
1083
+ * 2. Then, fill remaining page space with package components in order
1084
+ * 3. Track total returned for cursor calculation
1085
+ *
1086
+ * @param appComponentsArray - Sorted array of app component briefs
1087
+ * @param packageComponents - Record of package name to component briefs
1088
+ * @param offset - Current pagination offset (0-indexed)
1089
+ * @returns Paginated components and total returned count
1090
+ */
1091
+ paginateComponents(appComponentsArray, packageComponents, offset) {
1092
+ const totalPackageComponents = Object.values(packageComponents).reduce((sum, arr) => sum + arr.length, 0);
1093
+ const totalComponents = appComponentsArray.length + totalPackageComponents;
1094
+ const paginatedAppComponents = appComponentsArray.slice(offset, offset + this.config.pageSize);
1095
+ const remainingSpace = this.config.pageSize - paginatedAppComponents.length;
1096
+ const paginatedPackageComponents = {};
1097
+ let packageComponentsConsumed = 0;
1098
+ if (remainingSpace > 0 && offset < totalComponents) {
1099
+ const packageOffset = Math.max(0, offset - appComponentsArray.length);
1100
+ let itemsToTake = remainingSpace;
1101
+ let currentOffset = packageOffset;
1102
+ for (const [pkg, components] of Object.entries(packageComponents)) {
1103
+ if (itemsToTake <= 0) break;
1104
+ if (currentOffset < components.length) {
1105
+ const sliced = components.slice(currentOffset, currentOffset + itemsToTake);
1106
+ paginatedPackageComponents[pkg] = sliced;
1107
+ packageComponentsConsumed += sliced.length;
1108
+ itemsToTake -= sliced.length;
1109
+ currentOffset = 0;
1110
+ } else currentOffset -= components.length;
1111
+ }
1112
+ }
1113
+ return {
1114
+ paginatedAppComponents,
1115
+ paginatedPackageComponents,
1116
+ totalReturned: paginatedAppComponents.length + packageComponentsConsumed
1117
+ };
1118
+ }
1119
+ /**
1120
+ * Encode pagination state into a base64 cursor string.
1121
+ * @param offset - The current offset position in the component list
1122
+ * @returns Base64-encoded cursor string for the next page
1123
+ */
1124
+ encodeCursor(offset) {
1125
+ return Buffer.from(JSON.stringify({ offset })).toString("base64");
1126
+ }
1127
+ /**
1128
+ * Decode cursor string into pagination state.
1129
+ * @param cursor - Base64-encoded cursor string from previous response
1130
+ * @returns Object with offset position; defaults to 0 if cursor is invalid
1131
+ */
1132
+ decodeCursor(cursor) {
1133
+ try {
1134
+ const decoded = Buffer.from(cursor, "base64").toString("utf-8");
1135
+ return { offset: JSON.parse(decoded).offset || 0 };
1136
+ } catch {
1137
+ __agiflowai_aicode_utils.log.debug("[AppComponentsService] Invalid cursor, resetting to offset 0");
1138
+ return { offset: 0 };
1139
+ }
1140
+ }
1141
+ };
1142
+
1143
+ //#endregion
1144
+ //#region src/services/BundlerService/BaseBundlerService.ts
1145
+ /**
1146
+ * Abstract base class for bundler service implementations.
1147
+ *
1148
+ * Subclasses must implement the abstract methods to provide
1149
+ * bundler-specific (Vite, Webpack, etc.) and framework-specific
1150
+ * (React, Vue, etc.) component rendering logic.
1151
+ *
1152
+ * @example
1153
+ * ```typescript
1154
+ * class MyCustomBundlerService extends BaseBundlerService {
1155
+ * async startDevServer(appPath: string): Promise<DevServerResult> {
1156
+ * // Custom dev server logic
1157
+ * }
1158
+ * // ... implement other abstract methods
1159
+ * }
1160
+ * ```
1161
+ */
1162
+ var BaseBundlerService = class {
1163
+ config;
1164
+ /**
1165
+ * Creates a new bundler service instance.
1166
+ * @param config - Service configuration options
1167
+ */
1168
+ constructor(config = {}) {
1169
+ this.config = config;
1170
+ }
1171
+ };
1172
+
1173
+ //#endregion
1174
+ //#region src/services/BundlerService/ViteReactBundlerService.ts
1175
+ /**
1176
+ * Maximum age for story configs in milliseconds (5 minutes).
1177
+ * Configs older than this are cleaned up to prevent memory leaks.
1178
+ */
1179
+ const STORY_CONFIG_MAX_AGE_MS = 300 * 1e3;
1180
+ /**
1181
+ * Maximum number of story configs to keep in memory.
1182
+ * Oldest configs are removed when this limit is exceeded.
1183
+ */
1184
+ const STORY_CONFIG_MAX_COUNT = 100;
1185
+ /**
1186
+ * Maximum size for serialized args in bytes (1MB).
1187
+ * Prevents memory issues with extremely large payloads.
1188
+ */
1189
+ const MAX_ARGS_SIZE_BYTES = 1024 * 1024;
1190
+ /**
1191
+ * Valid pattern for story names.
1192
+ * Allows alphanumeric characters, underscores, hyphens, and spaces.
1193
+ */
1194
+ const VALID_STORY_NAME_PATTERN = /^[a-zA-Z0-9_\- ]+$/;
1195
+ /**
1196
+ * Valid pattern for component paths.
1197
+ * Must end with .stories.tsx, .stories.ts, .stories.jsx, or .stories.js
1198
+ */
1199
+ const VALID_COMPONENT_PATH_PATTERN = /\.stories\.(tsx?|jsx?)$/;
1200
+ /**
1201
+ * Validates a story name to prevent code injection.
1202
+ * @param storyName - The story name to validate
1203
+ * @throws Error if the story name contains invalid characters
1204
+ */
1205
+ function validateStoryName(storyName) {
1206
+ if (!storyName || typeof storyName !== "string") throw new Error("Story name is required and must be a string");
1207
+ if (!VALID_STORY_NAME_PATTERN.test(storyName)) throw new Error(`Story name "${storyName}" contains invalid characters. Only alphanumeric characters, underscores, hyphens, and spaces are allowed.`);
1208
+ }
1209
+ /**
1210
+ * Validates a component path to prevent code injection.
1211
+ * @param componentPath - The component path to validate
1212
+ * @throws Error if the component path is invalid
1213
+ */
1214
+ function validateComponentPath(componentPath) {
1215
+ if (!componentPath || typeof componentPath !== "string") throw new Error("Component path is required and must be a string");
1216
+ if (!VALID_COMPONENT_PATH_PATTERN.test(componentPath)) throw new Error(`Component path "${componentPath}" must be a valid Storybook story file (*.stories.tsx, *.stories.ts, *.stories.jsx, or *.stories.js)`);
1217
+ if (componentPath.includes("..")) throw new Error("Component path must not contain path traversal sequences (..)");
1218
+ }
1219
+ /**
1220
+ * Validates args object size to prevent memory issues.
1221
+ * @param args - The args object to validate
1222
+ * @throws Error if args exceed size limit
1223
+ */
1224
+ function validateArgsSize(args) {
1225
+ const serialized = JSON.stringify(args);
1226
+ const sizeBytes = Buffer.byteLength(serialized, "utf-8");
1227
+ if (sizeBytes > MAX_ARGS_SIZE_BYTES) throw new Error(`Args payload too large: ${sizeBytes} bytes exceeds maximum of ${MAX_ARGS_SIZE_BYTES} bytes (1MB)`);
1228
+ }
1229
+ /**
1230
+ * Validates CSS file paths to prevent path traversal attacks.
1231
+ * @param cssFiles - Array of CSS file paths to validate
1232
+ * @param workspaceRoot - The workspace root directory
1233
+ * @throws Error if any path is invalid or escapes workspace
1234
+ */
1235
+ function validateCssFiles(cssFiles, workspaceRoot) {
1236
+ for (const cssFile of cssFiles) {
1237
+ if (typeof cssFile !== "string") throw new Error("CSS file path must be a string");
1238
+ if (cssFile.includes("..")) throw new Error(`CSS file path "${cssFile}" must not contain path traversal sequences (..)`);
1239
+ if (node_path.default.isAbsolute(cssFile)) {
1240
+ if (!node_path.default.normalize(cssFile).startsWith(workspaceRoot)) throw new Error(`CSS file path "${cssFile}" must be within workspace boundaries`);
1241
+ }
1242
+ const ext = node_path.default.extname(cssFile).toLowerCase();
1243
+ if (![
1244
+ ".css",
1245
+ ".scss",
1246
+ ".sass",
1247
+ ".less",
1248
+ ".pcss",
1249
+ ".postcss"
1250
+ ].includes(ext)) throw new Error(`CSS file "${cssFile}" must have a valid CSS extension (.css, .scss, .sass, .less, .pcss)`);
1251
+ }
1252
+ }
1253
+ /**
1254
+ * Helper to create a Vite plugin that serves story entry files from memory.
1255
+ */
1256
+ function createStoryEntryPlugin(getStoryConfig, generateCode) {
1257
+ return {
1258
+ name: "vite-plugin-story-entry",
1259
+ enforce: "pre",
1260
+ resolveId(id) {
1261
+ if (id.includes("virtual:story-entry")) {
1262
+ __agiflowai_aicode_utils.log.debug(`[vite-plugin-story-entry] resolveId: ${id}`);
1263
+ return `\0${id.replace(/^\//, "")}`;
1264
+ }
1265
+ },
1266
+ load(id) {
1267
+ if (id.includes("virtual:story-entry")) {
1268
+ __agiflowai_aicode_utils.log.debug(`[vite-plugin-story-entry] load: ${id}`);
1269
+ const storyId = id.match(/id=([^&]+)/)?.[1];
1270
+ if (storyId) {
1271
+ const config = getStoryConfig(storyId);
1272
+ if (config) return generateCode(config);
1273
+ }
1274
+ throw new Error(`[Vite] Story config not found for id: ${storyId}`);
1275
+ }
1276
+ }
1277
+ };
1278
+ }
1279
+ /**
1280
+ * ViteReactBundlerService provides Vite + React bundling for component rendering.
1281
+ *
1282
+ * This is the default implementation of BaseBundlerService that uses Vite
1283
+ * as the bundler and React as the framework for rendering components.
1284
+ *
1285
+ * @example
1286
+ * ```typescript
1287
+ * const service = ViteReactBundlerService.getInstance();
1288
+ * await service.startDevServer('apps/my-app');
1289
+ * const { url } = await service.serveComponent({
1290
+ * componentPath: '/path/to/Button.stories.tsx',
1291
+ * storyName: 'Primary',
1292
+ * appPath: 'apps/my-app'
1293
+ * });
1294
+ * ```
1295
+ */
1296
+ var ViteReactBundlerService = class ViteReactBundlerService extends BaseBundlerService {
1297
+ static instance = null;
1298
+ server = null;
1299
+ monorepoRoot;
1300
+ serverUrl = null;
1301
+ serverPort = null;
1302
+ currentAppPath = null;
1303
+ storyConfigs = /* @__PURE__ */ new Map();
1304
+ /** Timestamps for when each story config was created, used for cleanup */
1305
+ storyConfigTimestamps = /* @__PURE__ */ new Map();
1306
+ /** Promise that resolves when server startup completes, prevents race conditions */
1307
+ serverStartPromise = null;
1308
+ /**
1309
+ * Creates a new ViteReactBundlerService instance.
1310
+ * Use getInstance() for singleton access.
1311
+ * @param config - Service configuration options
1312
+ */
1313
+ constructor(config = {}) {
1314
+ super(config);
1315
+ this.monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
1316
+ }
1317
+ /**
1318
+ * Get the singleton instance of ViteReactBundlerService.
1319
+ * Singleton pattern ensures only one dev server runs at a time,
1320
+ * preventing port conflicts and resource duplication.
1321
+ * @returns The singleton ViteReactBundlerService instance
1322
+ */
1323
+ static getInstance() {
1324
+ if (!ViteReactBundlerService.instance) ViteReactBundlerService.instance = new ViteReactBundlerService();
1325
+ return ViteReactBundlerService.instance;
1326
+ }
1327
+ /**
1328
+ * Reset the singleton instance.
1329
+ * This is primarily used in testing to ensure a fresh instance.
1330
+ * @example
1331
+ * ```typescript
1332
+ * afterEach(() => {
1333
+ * ViteReactBundlerService.resetInstance();
1334
+ * });
1335
+ * ```
1336
+ */
1337
+ static resetInstance() {
1338
+ ViteReactBundlerService.instance = null;
1339
+ }
1340
+ /**
1341
+ * Get the bundler identifier.
1342
+ * @returns The bundler ID string ('vite')
1343
+ */
1344
+ getBundlerId() {
1345
+ return "vite";
1346
+ }
1347
+ /**
1348
+ * Get the framework identifier.
1349
+ * @returns The framework ID string ('react')
1350
+ */
1351
+ getFrameworkId() {
1352
+ return "react";
1353
+ }
1354
+ /**
1355
+ * Get the current server URL.
1356
+ * @returns Server URL or null if not running
1357
+ */
1358
+ getServerUrl() {
1359
+ return this.serverUrl;
1360
+ }
1361
+ /**
1362
+ * Get the current server port.
1363
+ * @returns Server port or null if not running
1364
+ */
1365
+ getServerPort() {
1366
+ return this.serverPort;
1367
+ }
1368
+ /**
1369
+ * Check if the dev server is running.
1370
+ * @returns True if server is running
1371
+ */
1372
+ isServerRunning() {
1373
+ return this.server !== null && this.serverUrl !== null;
1374
+ }
1375
+ /**
1376
+ * Get the current app path being served.
1377
+ * @returns App path or null if not running
1378
+ */
1379
+ getCurrentAppPath() {
1380
+ return this.currentAppPath;
1381
+ }
1382
+ /**
1383
+ * Start a Vite dev server for hot reload and caching.
1384
+ * Handles concurrent calls by returning the same promise if server is already starting.
1385
+ * @param appPath - Absolute or relative path to the app directory
1386
+ * @returns Promise resolving to server URL and port
1387
+ * @throws Error if server fails to start
1388
+ */
1389
+ async startDevServer(appPath) {
1390
+ const resolvedAppPath = node_path.default.isAbsolute(appPath) ? appPath : node_path.default.join(this.monorepoRoot, appPath);
1391
+ if (this.isServerRunning() && this.currentAppPath === resolvedAppPath) {
1392
+ __agiflowai_aicode_utils.log.info(`[ViteReactBundlerService] Server already running for ${resolvedAppPath}`);
1393
+ return {
1394
+ url: this.serverUrl,
1395
+ port: this.serverPort
1396
+ };
1397
+ }
1398
+ if (this.serverStartPromise) {
1399
+ __agiflowai_aicode_utils.log.info("[ViteReactBundlerService] Server start already in progress, waiting...");
1400
+ return this.serverStartPromise;
1401
+ }
1402
+ this.serverStartPromise = this.doStartDevServer(resolvedAppPath);
1403
+ try {
1404
+ return await this.serverStartPromise;
1405
+ } finally {
1406
+ this.serverStartPromise = null;
1407
+ }
1408
+ }
1409
+ /**
1410
+ * Internal method that performs the actual server startup.
1411
+ * @param resolvedAppPath - Resolved absolute path to the app directory
1412
+ * @returns Promise resolving to server URL and port
1413
+ */
1414
+ async doStartDevServer(resolvedAppPath) {
1415
+ const tmpDir = node_path.default.join(resolvedAppPath, ".tmp");
1416
+ try {
1417
+ await node_fs.promises.mkdir(tmpDir, { recursive: true });
1418
+ const { createServer: createServer$1 } = await import("vite");
1419
+ this.server = await createServer$1({
1420
+ root: tmpDir,
1421
+ base: "/",
1422
+ configFile: false,
1423
+ plugins: [(0, __tailwindcss_vite.default)(), createStoryEntryPlugin((id) => this.storyConfigs.get(id), (opts) => this.generateEntryFile(opts))],
1424
+ resolve: { alias: { "@": node_path.default.join(resolvedAppPath, "src") } },
1425
+ esbuild: {
1426
+ jsx: "automatic",
1427
+ jsxImportSource: "react"
1428
+ },
1429
+ server: {
1430
+ strictPort: false,
1431
+ open: false
1432
+ }
1433
+ });
1434
+ this.server.middlewares.use(async (req, res, next) => {
1435
+ const url$1 = req.url || "/";
1436
+ const match = url$1.match(/^\/preview\/([^/?]+)/);
1437
+ if (match) {
1438
+ const storyId = match[1];
1439
+ const config = this.storyConfigs.get(storyId);
1440
+ if (config) try {
1441
+ const htmlTemplate = this.generateHtmlTemplate(`@virtual:story-entry?id=${storyId}`, config.darkMode);
1442
+ const transformedHtml = await this.server.transformIndexHtml(url$1, htmlTemplate);
1443
+ res.statusCode = 200;
1444
+ res.setHeader("Content-Type", "text/html");
1445
+ res.end(transformedHtml);
1446
+ return;
1447
+ } catch (e) {
1448
+ const err = e;
1449
+ __agiflowai_aicode_utils.log.error(`[ViteMiddleware] Error serving preview: ${err.message}`);
1450
+ next(err);
1451
+ return;
1452
+ }
1453
+ }
1454
+ next();
1455
+ });
1456
+ await this.server.listen();
1457
+ const address = this.server.httpServer?.address();
1458
+ if (!address || typeof address === "string") throw new Error("Failed to start Vite dev server. Ensure no other process is using the port.");
1459
+ const port = address.port;
1460
+ const url = `http://localhost:${port}`;
1461
+ this.serverUrl = url;
1462
+ this.serverPort = port;
1463
+ this.currentAppPath = resolvedAppPath;
1464
+ __agiflowai_aicode_utils.log.info(`[ViteReactBundlerService] Vite dev server started at ${url}`);
1465
+ return {
1466
+ url,
1467
+ port
1468
+ };
1469
+ } catch (error) {
1470
+ const err = /* @__PURE__ */ new Error(`Failed to start dev server for ${resolvedAppPath}: ${error instanceof Error ? error.message : String(error)}`);
1471
+ err.cause = error;
1472
+ throw err;
1473
+ }
1474
+ }
1475
+ /**
1476
+ * Serve a component dynamically through the dev server.
1477
+ * @param options - Component rendering options
1478
+ * @returns Promise resolving to the component URL and HTML file path
1479
+ * @throws Error if dev server is not running or file operations fail
1480
+ */
1481
+ async serveComponent(options) {
1482
+ if (!this.isServerRunning()) throw new Error("Dev server is not running. Start it first using startDevServer().");
1483
+ const { componentPath, storyName, args = {}, darkMode = false, appPath, cssFiles = [], rootComponent } = options;
1484
+ validateStoryName(storyName);
1485
+ validateComponentPath(componentPath);
1486
+ validateArgsSize(args);
1487
+ validateCssFiles(cssFiles, this.monorepoRoot);
1488
+ const resolvedAppPath = node_path.default.isAbsolute(appPath) ? appPath : node_path.default.join(this.monorepoRoot, appPath);
1489
+ if (this.currentAppPath !== resolvedAppPath) throw new Error(`Dev server is running for ${this.currentAppPath} but requested ${resolvedAppPath}.`);
1490
+ const tmpDir = node_path.default.join(resolvedAppPath, ".tmp");
1491
+ try {
1492
+ this.cleanupStaleStoryConfigs();
1493
+ await node_fs.promises.mkdir(tmpDir, { recursive: true });
1494
+ const wrapperCssPath = node_path.default.join(tmpDir, "tailwind-wrapper.css");
1495
+ const wrapperCssContent = this.generateWrapperCss(resolvedAppPath, cssFiles);
1496
+ await node_fs.promises.writeFile(wrapperCssPath, wrapperCssContent, "utf-8");
1497
+ const timestamp = Date.now();
1498
+ const storyId = `${timestamp}-${Math.random().toString(36).slice(2)}`;
1499
+ this.storyConfigs.set(storyId, {
1500
+ componentPath,
1501
+ storyName,
1502
+ args,
1503
+ appPath: resolvedAppPath,
1504
+ darkMode,
1505
+ cssFiles,
1506
+ rootComponent,
1507
+ tmpDir
1508
+ });
1509
+ this.storyConfigTimestamps.set(storyId, timestamp);
1510
+ const url = `${this.serverUrl}/preview/${storyId}`;
1511
+ const htmlContent = this.generateHtmlTemplate(`@virtual:story-entry?id=${storyId}`, darkMode);
1512
+ __agiflowai_aicode_utils.log.info(`[ViteReactBundlerService] Component served at: ${url}`);
1513
+ return {
1514
+ url,
1515
+ htmlContent
1516
+ };
1517
+ } catch (error) {
1518
+ const err = /* @__PURE__ */ new Error(`Failed to serve component ${storyName}: ${error instanceof Error ? error.message : String(error)}`);
1519
+ err.cause = error;
1520
+ throw err;
1521
+ }
1522
+ }
1523
+ /**
1524
+ * Pre-render a component to a static HTML file.
1525
+ * @param options - Component rendering options
1526
+ * @returns Promise resolving to the HTML file path
1527
+ * @throws Error if build fails
1528
+ */
1529
+ async prerenderComponent(options) {
1530
+ const { componentPath, storyName, args = {}, darkMode = false, appPath, cssFiles = [], rootComponent } = options;
1531
+ validateStoryName(storyName);
1532
+ validateComponentPath(componentPath);
1533
+ validateArgsSize(args);
1534
+ validateCssFiles(cssFiles, this.monorepoRoot);
1535
+ const resolvedAppPath = node_path.default.isAbsolute(appPath) ? appPath : node_path.default.join(this.monorepoRoot, appPath);
1536
+ const tmpDir = node_path.default.join(resolvedAppPath, ".tmp");
1537
+ try {
1538
+ await node_fs.promises.mkdir(tmpDir, { recursive: true });
1539
+ return { htmlFilePath: await this.buildComponent({
1540
+ componentPath,
1541
+ storyName,
1542
+ args,
1543
+ appPath: resolvedAppPath,
1544
+ darkMode,
1545
+ cssFiles,
1546
+ rootComponent,
1547
+ tmpDir
1548
+ }) };
1549
+ } catch (error) {
1550
+ const err = /* @__PURE__ */ new Error(`Failed to prerender component ${storyName}: ${error instanceof Error ? error.message : String(error)}`);
1551
+ err.cause = error;
1552
+ throw err;
1553
+ }
1554
+ }
1555
+ /**
1556
+ * Clean up server resources and reset state.
1557
+ * Closes the Vite dev server if running.
1558
+ *
1559
+ * Note: Errors during server close are intentionally logged but not re-thrown.
1560
+ * This ensures cleanup always completes and state is reset, even if the server
1561
+ * is in an unexpected state. Callers should not depend on cleanup failure detection.
1562
+ */
1563
+ async cleanup() {
1564
+ if (this.server) {
1565
+ __agiflowai_aicode_utils.log.info("[ViteReactBundlerService] Closing Vite dev server...");
1566
+ try {
1567
+ await this.server.close();
1568
+ } catch (error) {
1569
+ __agiflowai_aicode_utils.log.error(`[ViteReactBundlerService] Error closing server: ${error instanceof Error ? error.message : String(error)}`);
1570
+ }
1571
+ this.server = null;
1572
+ this.serverUrl = null;
1573
+ this.serverPort = null;
1574
+ this.currentAppPath = null;
1575
+ this.serverStartPromise = null;
1576
+ this.storyConfigs.clear();
1577
+ this.storyConfigTimestamps.clear();
1578
+ __agiflowai_aicode_utils.log.info("[ViteReactBundlerService] Vite dev server closed");
1579
+ }
1580
+ }
1581
+ /**
1582
+ * Clean up stale story configs to prevent memory leaks.
1583
+ * Removes configs that are older than STORY_CONFIG_MAX_AGE_MS or
1584
+ * when the number of configs exceeds STORY_CONFIG_MAX_COUNT.
1585
+ */
1586
+ cleanupStaleStoryConfigs() {
1587
+ const now = Date.now();
1588
+ const entriesToDelete = [];
1589
+ for (const [storyId, timestamp] of this.storyConfigTimestamps) if (now - timestamp > STORY_CONFIG_MAX_AGE_MS) entriesToDelete.push(storyId);
1590
+ for (const storyId of entriesToDelete) {
1591
+ this.storyConfigs.delete(storyId);
1592
+ this.storyConfigTimestamps.delete(storyId);
1593
+ }
1594
+ if (entriesToDelete.length > 0) __agiflowai_aicode_utils.log.debug(`[ViteReactBundlerService] Cleaned up ${entriesToDelete.length} stale story configs`);
1595
+ if (this.storyConfigs.size > STORY_CONFIG_MAX_COUNT) {
1596
+ const toRemove = Array.from(this.storyConfigTimestamps.entries()).sort((a, b) => a[1] - b[1]).slice(0, this.storyConfigs.size - STORY_CONFIG_MAX_COUNT);
1597
+ for (const [storyId] of toRemove) {
1598
+ this.storyConfigs.delete(storyId);
1599
+ this.storyConfigTimestamps.delete(storyId);
1600
+ }
1601
+ __agiflowai_aicode_utils.log.debug(`[ViteReactBundlerService] Removed ${toRemove.length} oldest story configs to stay under limit`);
1602
+ }
1603
+ }
1604
+ /**
1605
+ * Generate a wrapper CSS file with @source directive for Tailwind v4.
1606
+ * This tells Tailwind where to scan for class names when building from .tmp directory.
1607
+ * @param appPath - Absolute path to the app directory
1608
+ * @param cssFiles - Array of CSS file paths to import
1609
+ * @returns Generated CSS content with @source directive
1610
+ */
1611
+ generateWrapperCss(appPath, cssFiles) {
1612
+ const cssImportStatements = cssFiles.map((cssFile) => {
1613
+ if (cssFile.startsWith("@") || cssFile.startsWith("tailwindcss/")) return `@import '${cssFile}';`;
1614
+ if (cssFile.startsWith("packages/") || cssFile.startsWith("apps/")) return `@import '${node_path.default.join(this.monorepoRoot, cssFile)}';`;
1615
+ return `@import '${node_path.default.join(appPath, cssFile)}';`;
1616
+ }).join("\n");
1617
+ return `/* Tailwind v4 source configuration for component scanning */
1618
+ @source "${node_path.default.join(appPath, "src")}";
1619
+
1620
+ ${cssImportStatements}
1621
+ `;
1622
+ }
1623
+ /**
1624
+ * Generate the React entry file content for rendering a story.
1625
+ * @param options - Build options including component path and story name
1626
+ * @returns Generated TypeScript/JSX entry file content
1627
+ */
1628
+ generateEntryFile(options) {
1629
+ const { componentPath, storyName, args, appPath, darkMode, rootComponent, tmpDir } = options;
1630
+ const argsJson = JSON.stringify(args, null, 2);
1631
+ return `${`import '${node_path.default.join(tmpDir, "tailwind-wrapper.css").replace(/\\/g, "/")}';`}
1632
+
1633
+ import React from 'react';
1634
+ import { createRoot } from 'react-dom/client';
1635
+ import * as Stories from '${componentPath}';
1636
+ ${rootComponent ? `import { RootDocument } from '${node_path.default.join(appPath, rootComponent).replace(/\\/g, "/")}';` : ""}
1637
+
1638
+ // Extract story metadata and the specific story to render
1639
+ const meta = Stories.default;
1640
+ const Story = Stories['${storyName}'];
1641
+ const storyArgs = Story?.args || {};
1642
+ // Merge story's default args with any custom args passed in
1643
+ const args = { ...storyArgs, ...${argsJson} };
1644
+
1645
+ // Create the story element - Storybook stories can define rendering in two ways:
1646
+ // 1. A render() function that receives args and context
1647
+ // 2. A component reference in meta.component
1648
+ let element;
1649
+ if (Story?.render) {
1650
+ // Story has custom render function - call it with args and minimal context
1651
+ const RenderComponent = () => Story.render(args, { loaded: {}, args });
1652
+ element = React.createElement(RenderComponent);
1653
+ } else {
1654
+ // Story uses component from meta - render with merged args as props
1655
+ const Component = meta.component;
1656
+ element = React.createElement(Component, args);
1657
+ }
1658
+
1659
+ // Wrap element with root component (theme provider, etc.) if specified
1660
+ const Wrapper = ${rootComponent ? "RootDocument" : "React.Fragment"};
1661
+ const wrappedElement = React.createElement(Wrapper, ${rootComponent ? `{ darkMode: ${darkMode} }` : "{}"}, element);
1662
+
1663
+ // Mount to DOM
1664
+ const rootEl = document.getElementById('root');
1665
+ if (!rootEl) throw new Error('Root element not found');
1666
+ const root = createRoot(rootEl);
1667
+ root.render(wrappedElement);
1668
+ `;
1669
+ }
1670
+ /**
1671
+ * Generate the HTML template for component preview.
1672
+ * @param entryFileName - Name of the entry file to include
1673
+ * @param darkMode - Whether to add dark mode class to HTML
1674
+ * @returns Generated HTML template string
1675
+ */
1676
+ generateHtmlTemplate(entryFileName, darkMode) {
1677
+ return `<!DOCTYPE html>
1678
+ <html class="${darkMode ? "dark" : ""}">
1679
+ <head>
1680
+ <meta charset="UTF-8">
1681
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1682
+ <title>Component Preview</title>
1683
+ <style>
1684
+ body { margin: 0; padding: 0; }
1685
+ #root { display: inline-block; }
1686
+ </style>
1687
+ </head>
1688
+ <body>
1689
+ <div id="root"></div>
1690
+ <script type="module" src="/${entryFileName}"><\/script>
1691
+ </body>
1692
+ </html>`;
1693
+ }
1694
+ async buildComponent(options) {
1695
+ const { appPath, darkMode, tmpDir, cssFiles = [] } = options;
1696
+ const timestamp = Date.now();
1697
+ try {
1698
+ const wrapperCssPath = node_path.default.join(tmpDir, "tailwind-wrapper.css");
1699
+ const wrapperCssContent = this.generateWrapperCss(appPath, cssFiles);
1700
+ await node_fs.promises.writeFile(wrapperCssPath, wrapperCssContent, "utf-8");
1701
+ const storyId = `build-${timestamp}`;
1702
+ const virtualModuleId = `@virtual:story-entry?id=${storyId}`;
1703
+ const htmlTemplate = this.generateHtmlTemplate(virtualModuleId, darkMode);
1704
+ const htmlTemplateFileName = `index-${timestamp}.html`;
1705
+ const htmlTemplatePath = node_path.default.join(tmpDir, htmlTemplateFileName);
1706
+ await node_fs.promises.writeFile(htmlTemplatePath, htmlTemplate, "utf-8");
1707
+ const { build } = await import("vite");
1708
+ const outDir = node_path.default.join(tmpDir, `dist-${timestamp}`);
1709
+ const buildStoryConfigs = /* @__PURE__ */ new Map();
1710
+ buildStoryConfigs.set(storyId, options);
1711
+ await build({
1712
+ root: tmpDir,
1713
+ base: "./",
1714
+ configFile: false,
1715
+ plugins: [
1716
+ (0, __tailwindcss_vite.default)(),
1717
+ (0, vite_plugin_singlefile.viteSingleFile)(),
1718
+ createStoryEntryPlugin((id) => buildStoryConfigs.get(id), (opts) => this.generateEntryFile(opts))
1719
+ ],
1720
+ resolve: { alias: { "@": node_path.default.join(appPath, "src") } },
1721
+ esbuild: {
1722
+ jsx: "automatic",
1723
+ jsxImportSource: "react"
1724
+ },
1725
+ build: {
1726
+ outDir,
1727
+ emptyOutDir: false,
1728
+ cssCodeSplit: false,
1729
+ rollupOptions: {
1730
+ input: htmlTemplatePath,
1731
+ output: { inlineDynamicImports: true }
1732
+ }
1733
+ }
1734
+ });
1735
+ const builtHtmlPath = node_path.default.join(outDir, htmlTemplateFileName);
1736
+ __agiflowai_aicode_utils.log.info(`[ViteReactBundlerService] Component built to: ${builtHtmlPath}`);
1737
+ await node_fs.promises.unlink(htmlTemplatePath).catch((err) => __agiflowai_aicode_utils.log.debug(`[ViteReactBundlerService] Failed to cleanup temp file: ${err.message}`));
1738
+ return builtHtmlPath;
1739
+ } catch (error) {
1740
+ const err = /* @__PURE__ */ new Error(`Failed to build component: ${error instanceof Error ? error.message : String(error)}`);
1741
+ err.cause = error;
1742
+ throw err;
1743
+ }
1744
+ }
1745
+ };
1746
+
1747
+ //#endregion
1748
+ //#region src/services/BundlerService/BundlerServiceFactory.ts
1749
+ /** Valid file extensions for custom service modules */
1750
+ const VALID_SERVICE_EXTENSIONS = [
1751
+ ".ts",
1752
+ ".js",
1753
+ ".mjs",
1754
+ ".cjs"
1755
+ ];
1756
+ /**
1757
+ * Default factory that creates a ViteReactBundlerService instance.
1758
+ * Uses the singleton pattern to ensure only one dev server runs at a time.
1759
+ *
1760
+ * @returns The singleton ViteReactBundlerService instance
1761
+ *
1762
+ * @example
1763
+ * ```typescript
1764
+ * import { createDefaultBundlerService } from './BundlerServiceFactory';
1765
+ *
1766
+ * const bundler = createDefaultBundlerService();
1767
+ * await bundler.startDevServer('apps/my-app');
1768
+ * ```
1769
+ */
1770
+ function createDefaultBundlerService() {
1771
+ return ViteReactBundlerService.getInstance();
1772
+ }
1773
+ /**
1774
+ * Registry of available bundler service factories.
1775
+ * Allows registration of custom bundler implementations by key.
1776
+ *
1777
+ * @example
1778
+ * ```typescript
1779
+ * // Register a custom bundler
1780
+ * bundlerRegistry.set('webpack-react', () => new WebpackReactBundlerService());
1781
+ *
1782
+ * // Get a bundler by key
1783
+ * const factory = bundlerRegistry.get('webpack-react');
1784
+ * const bundler = factory?.() ?? createDefaultBundlerService();
1785
+ * ```
1786
+ */
1787
+ const bundlerRegistry = /* @__PURE__ */ new Map();
1788
+ bundlerRegistry.set("vite-react", createDefaultBundlerService);
1789
+ /** Cached bundler service instance loaded from config */
1790
+ let cachedBundlerService = null;
1791
+ /**
1792
+ * Get bundler service based on toolkit.yaml configuration.
1793
+ *
1794
+ * If a custom service is configured in toolkit.yaml under style-system.bundler.customService,
1795
+ * it will be dynamically loaded. Otherwise, returns the default ViteReactBundlerService.
1796
+ *
1797
+ * The custom service module must:
1798
+ * - Export a class that extends BaseBundlerService as default export, OR
1799
+ * - Export an instance of BaseBundlerService as default export, OR
1800
+ * - Export a getInstance() function that returns a BaseBundlerService
1801
+ *
1802
+ * @returns Promise resolving to a bundler service instance
1803
+ *
1804
+ * @example
1805
+ * ```typescript
1806
+ * // In toolkit.yaml:
1807
+ * // style-system:
1808
+ * // bundler:
1809
+ * // customService: packages/my-app/src/bundler/CustomBundlerService.ts
1810
+ *
1811
+ * const bundler = await getBundlerServiceFromConfig();
1812
+ * await bundler.startDevServer('apps/my-app');
1813
+ * ```
1814
+ */
1815
+ async function getBundlerServiceFromConfig() {
1816
+ if (cachedBundlerService) return cachedBundlerService;
1817
+ const config = await getBundlerConfig();
1818
+ if (!config?.customService) {
1819
+ cachedBundlerService = createDefaultBundlerService();
1820
+ return cachedBundlerService;
1821
+ }
1822
+ const monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
1823
+ const customServicePath = node_path.default.resolve(monorepoRoot, config.customService);
1824
+ const normalizedWorkspaceRoot = node_path.default.resolve(monorepoRoot);
1825
+ if (!customServicePath.startsWith(normalizedWorkspaceRoot + node_path.default.sep)) {
1826
+ __agiflowai_aicode_utils.log.error(`[BundlerServiceFactory] Security error: customService path "${config.customService}" resolves outside workspace root`);
1827
+ cachedBundlerService = createDefaultBundlerService();
1828
+ return cachedBundlerService;
1829
+ }
1830
+ const ext = node_path.default.extname(customServicePath).toLowerCase();
1831
+ if (!VALID_SERVICE_EXTENSIONS.includes(ext)) {
1832
+ __agiflowai_aicode_utils.log.error(`[BundlerServiceFactory] Invalid file extension "${ext}" for customService. Expected one of: ${VALID_SERVICE_EXTENSIONS.join(", ")}`);
1833
+ cachedBundlerService = createDefaultBundlerService();
1834
+ return cachedBundlerService;
1835
+ }
1836
+ try {
1837
+ __agiflowai_aicode_utils.log.info(`[BundlerServiceFactory] Loading custom bundler service from: ${customServicePath}`);
1838
+ const module$1 = await import(customServicePath);
1839
+ if (module$1.default) {
1840
+ if (typeof module$1.default === "function" && module$1.default.prototype) if (typeof module$1.default.getInstance === "function") cachedBundlerService = module$1.default.getInstance();
1841
+ else cachedBundlerService = new module$1.default();
1842
+ else if (typeof module$1.default === "object") cachedBundlerService = module$1.default;
1843
+ } else if (typeof module$1.getInstance === "function") cachedBundlerService = module$1.getInstance();
1844
+ if (!cachedBundlerService) throw new Error("Custom bundler service module must export a class extending BaseBundlerService as default, an instance as default, or a getInstance() function");
1845
+ const missingMethods = [
1846
+ "getBundlerId",
1847
+ "getFrameworkId",
1848
+ "startDevServer",
1849
+ "serveComponent",
1850
+ "prerenderComponent",
1851
+ "isServerRunning",
1852
+ "getServerUrl",
1853
+ "getServerPort",
1854
+ "getCurrentAppPath",
1855
+ "cleanup"
1856
+ ].filter((method) => typeof cachedBundlerService[method] !== "function");
1857
+ if (missingMethods.length > 0) throw new Error(`Custom bundler service must implement BaseBundlerService interface. Missing methods: ${missingMethods.join(", ")}`);
1858
+ __agiflowai_aicode_utils.log.info(`[BundlerServiceFactory] Custom bundler service loaded successfully`);
1859
+ return cachedBundlerService;
1860
+ } catch (error) {
1861
+ const message = error instanceof Error ? error.message : String(error);
1862
+ __agiflowai_aicode_utils.log.error(`[BundlerServiceFactory] Failed to load custom bundler service: ${message}`);
1863
+ __agiflowai_aicode_utils.log.info("[BundlerServiceFactory] Falling back to default ViteReactBundlerService");
1864
+ cachedBundlerService = createDefaultBundlerService();
1865
+ return cachedBundlerService;
1866
+ }
1867
+ }
1868
+
1869
+ //#endregion
1870
+ //#region src/utils/screenshot.ts
1871
+ const browsers = {
1872
+ chromium: playwright.chromium,
1873
+ firefox: playwright.firefox,
1874
+ webkit: playwright.webkit
1875
+ };
1876
+ /**
1877
+ * Browser launch configurations to try in order of preference.
1878
+ * Prefers system-installed Chrome before falling back to Playwright's bundled browsers.
1879
+ */
1880
+ const browserLaunchConfigs = [
1881
+ {
1882
+ browserType: playwright.chromium,
1883
+ channel: "chrome",
1884
+ name: "System Chrome"
1885
+ },
1886
+ {
1887
+ browserType: playwright.chromium,
1888
+ channel: void 0,
1889
+ name: "Playwright Chromium"
1890
+ },
1891
+ {
1892
+ browserType: playwright.firefox,
1893
+ channel: void 0,
1894
+ name: "Playwright Firefox"
1895
+ }
1896
+ ];
1897
+ /**
1898
+ * Attempts to launch a browser, trying multiple configurations in order of preference.
1899
+ * @returns Launched browser instance
1900
+ * @throws Error if no browser can be launched
1901
+ */
1902
+ async function launchBrowserWithFallback() {
1903
+ const errors = [];
1904
+ for (const config of browserLaunchConfigs) try {
1905
+ const browser = await config.browserType.launch({
1906
+ headless: true,
1907
+ channel: config.channel
1908
+ });
1909
+ console.log(`[screenshot] Using ${config.name}`);
1910
+ return browser;
1911
+ } catch (error) {
1912
+ const message = error instanceof Error ? error.message : String(error);
1913
+ errors.push(`${config.name}: ${message}`);
1914
+ }
1915
+ throw new Error(`No browser available. Tried:\n${errors.join("\n")}\n\nPlease install Chrome, or run 'npx playwright install chromium'`);
1916
+ }
1917
+ async function takeScreenshot(options) {
1918
+ const { url, output, width = 1280, height = 800, fullPage = false, browser: browserName = "chromium", waitTime = 1e3, darkMode = false, mobile = false, generateThumbnail = false, thumbnailWidth = 400, thumbnailQuality = 80, base64 = false } = options;
1919
+ let browser = null;
1920
+ try {
1921
+ if (browserName === "chromium") browser = await launchBrowserWithFallback();
1922
+ else {
1923
+ const browserType = browsers[browserName];
1924
+ if (!browserType) throw new Error(`Unsupported browser: ${browserName}`);
1925
+ browser = await browserType.launch({ headless: true });
1926
+ }
1927
+ const page = await (await browser.newContext({
1928
+ viewport: {
1929
+ width,
1930
+ height
1931
+ },
1932
+ colorScheme: darkMode ? "dark" : "light",
1933
+ isMobile: mobile
1934
+ })).newPage();
1935
+ await page.goto(url, { waitUntil: "networkidle" });
1936
+ if (waitTime > 0) await page.waitForTimeout(waitTime);
1937
+ const screenshotBuffer = await page.screenshot({
1938
+ path: output,
1939
+ fullPage
1940
+ });
1941
+ const result = { imagePath: output };
1942
+ if (generateThumbnail) {
1943
+ const ext = node_path.default.extname(output);
1944
+ const thumbnailPath = output.replace(ext, `-thumb${ext}`);
1945
+ await (0, sharp.default)(screenshotBuffer).resize(thumbnailWidth).jpeg({ quality: thumbnailQuality }).toFile(thumbnailPath.replace(ext, ".jpg"));
1946
+ result.thumbnailPath = thumbnailPath.replace(ext, ".jpg");
1947
+ }
1948
+ if (base64) result.base64 = screenshotBuffer.toString("base64");
1949
+ return result;
1950
+ } finally {
1951
+ if (browser) await browser.close();
1952
+ }
1953
+ }
1954
+
1955
+ //#endregion
1956
+ //#region src/services/ThemeService/BaseThemeService.ts
1957
+ /**
1958
+ * Abstract base class for theme listing services.
1959
+ *
1960
+ * Subclasses must implement the `listThemes` method to provide
1961
+ * source-specific theme extraction logic (CSS files, JSON configs, etc.).
1962
+ *
1963
+ * @example
1964
+ * ```typescript
1965
+ * class MyCustomThemeService extends BaseThemeService {
1966
+ * async listThemes(): Promise<AvailableThemesResult> {
1967
+ * // Custom theme extraction logic
1968
+ * }
1969
+ * }
1970
+ * ```
1971
+ */
1972
+ var BaseThemeService = class {
1973
+ config;
1974
+ /**
1975
+ * Creates a new theme service instance
1976
+ * @param config - Theme service configuration
1977
+ */
1978
+ constructor(config) {
1979
+ this.config = config;
1980
+ }
1981
+ /**
1982
+ * Validate that a file path exists and is readable.
1983
+ * Can be overridden by subclasses for custom validation.
1984
+ *
1985
+ * @param filePath - Path to validate
1986
+ * @throws Error if path is invalid or unreadable
1987
+ */
1988
+ async validatePath(filePath) {
1989
+ try {
1990
+ await node_fs.promises.access(filePath);
1991
+ } catch {
1992
+ throw new Error(`File not found or not readable: ${filePath}`);
1993
+ }
1994
+ }
1995
+ };
1996
+
1997
+ //#endregion
1998
+ //#region src/services/ThemeService/CSSThemeService.ts
1999
+ /**
2000
+ * CSS-based theme service implementation.
2001
+ *
2002
+ * Extracts themes from CSS files by parsing class selectors that contain
2003
+ * CSS custom properties (variables). Each top-level class selector with
2004
+ * color variables is treated as a theme.
2005
+ *
2006
+ * @example
2007
+ * ```typescript
2008
+ * const service = new CSSThemeService({ themePath: 'apps/my-app/src/styles/colors.css' });
2009
+ * const result = await service.listThemes();
2010
+ * console.log(result.themes); // [{ name: 'slate', ... }, { name: 'blue', ... }]
2011
+ * ```
2012
+ */
2013
+ var CSSThemeService = class extends BaseThemeService {
2014
+ monorepoRoot;
2015
+ /**
2016
+ * Creates a new CSSThemeService instance
2017
+ * @param config - Theme service configuration with themePath or cssFiles
2018
+ */
2019
+ constructor(config) {
2020
+ super(config);
2021
+ this.monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
2022
+ }
2023
+ /**
2024
+ * Get the source identifier for this service
2025
+ * @returns 'css-file' identifier
2026
+ */
2027
+ getSourceId() {
2028
+ return "css-file";
2029
+ }
2030
+ /**
2031
+ * List available themes by parsing CSS files.
2032
+ *
2033
+ * Scans the configured CSS files for class selectors that define
2034
+ * CSS custom properties (--color-*, etc.) and returns them as themes.
2035
+ *
2036
+ * @returns Promise resolving to available themes result
2037
+ * @throws Error if no theme files are configured or files cannot be read
2038
+ */
2039
+ async listThemes() {
2040
+ const cssFilePaths = this.resolveCSSFilePaths();
2041
+ if (cssFilePaths.length === 0) throw new Error("No theme CSS files configured. Set themePath or cssFiles in project.json style-system config.");
2042
+ const themes = [];
2043
+ for (const cssFilePath of cssFilePaths) try {
2044
+ await this.validatePath(cssFilePath);
2045
+ const fileThemes = await this.extractThemesFromCSS(cssFilePath);
2046
+ themes.push(...fileThemes);
2047
+ } catch (error) {
2048
+ __agiflowai_aicode_utils.log.warn(`[CSSThemeService] Could not process ${cssFilePath}:`, error);
2049
+ }
2050
+ const uniqueThemes = this.deduplicateThemes(themes);
2051
+ return {
2052
+ themes: uniqueThemes.sort((a, b) => a.name.localeCompare(b.name)),
2053
+ activeTheme: uniqueThemes.length > 0 ? uniqueThemes[0].name : void 0,
2054
+ source: "css-file"
2055
+ };
2056
+ }
2057
+ /**
2058
+ * Resolve configured CSS file paths to absolute paths.
2059
+ * @returns Array of absolute CSS file paths
2060
+ */
2061
+ resolveCSSFilePaths() {
2062
+ const paths = [];
2063
+ if (this.config.themePath) {
2064
+ const themePath = node_path.default.isAbsolute(this.config.themePath) ? this.config.themePath : node_path.default.join(this.monorepoRoot, this.config.themePath);
2065
+ paths.push(themePath);
2066
+ }
2067
+ if (this.config.cssFiles && this.config.cssFiles.length > 0) for (const cssFile of this.config.cssFiles) {
2068
+ const cssPath = node_path.default.isAbsolute(cssFile) ? cssFile : node_path.default.join(this.monorepoRoot, cssFile);
2069
+ if (!paths.includes(cssPath)) paths.push(cssPath);
2070
+ }
2071
+ return paths;
2072
+ }
2073
+ /**
2074
+ * Extract themes from a CSS file by parsing class selectors.
2075
+ *
2076
+ * Identifies class selectors that contain CSS custom property declarations
2077
+ * (--color-*, --primary-*, etc.) and treats each as a theme definition.
2078
+ *
2079
+ * @param cssFilePath - Absolute path to CSS file
2080
+ * @returns Array of ThemeInfo objects
2081
+ */
2082
+ async extractThemesFromCSS(cssFilePath) {
2083
+ const content = await node_fs.promises.readFile(cssFilePath, "utf-8");
2084
+ const fileName = node_path.default.basename(cssFilePath);
2085
+ const themes = [];
2086
+ try {
2087
+ postcss.default.parse(content).walkRules((rule) => {
2088
+ const selector = rule.selector.trim();
2089
+ if (!selector.startsWith(".") || selector.includes(" ") || selector.includes(",")) return;
2090
+ const className = selector.slice(1);
2091
+ const colorVariables = {};
2092
+ let hasColorVariables = false;
2093
+ rule.walkDecls((decl) => {
2094
+ if (decl.prop.startsWith("--color-") || decl.prop.startsWith("--primary-")) {
2095
+ colorVariables[decl.prop] = decl.value;
2096
+ hasColorVariables = true;
2097
+ }
2098
+ });
2099
+ if (hasColorVariables) themes.push({
2100
+ name: className,
2101
+ fileName,
2102
+ path: cssFilePath,
2103
+ colorVariables,
2104
+ shadeCount: Object.keys(colorVariables).length
2105
+ });
2106
+ });
2107
+ } catch (error) {
2108
+ throw new Error(`Failed to parse CSS file ${cssFilePath}: ${error instanceof Error ? error.message : String(error)}`);
2109
+ }
2110
+ __agiflowai_aicode_utils.log.info(`[CSSThemeService] Found ${themes.length} themes in ${fileName}`);
2111
+ return themes;
2112
+ }
2113
+ /**
2114
+ * Deduplicate themes by name, keeping the first occurrence.
2115
+ * @param themes - Array of themes to deduplicate
2116
+ * @returns Deduplicated array of themes
2117
+ */
2118
+ deduplicateThemes(themes) {
2119
+ const seen = /* @__PURE__ */ new Set();
2120
+ return themes.filter((theme) => {
2121
+ if (seen.has(theme.name)) return false;
2122
+ seen.add(theme.name);
2123
+ return true;
2124
+ });
2125
+ }
2126
+ };
2127
+
2128
+ //#endregion
2129
+ //#region src/services/ThemeService/ThemeService.ts
2130
+ /**
2131
+ * ThemeService handles theme configuration and CSS generation.
2132
+ *
2133
+ * Provides methods for accessing theme CSS, generating theme wrappers,
2134
+ * and listing available theme configurations.
2135
+ *
2136
+ * @example
2137
+ * ```typescript
2138
+ * const service = new ThemeService(designConfig);
2139
+ * const cssFiles = await service.getThemeCSS();
2140
+ * const themes = await service.listAvailableThemes();
2141
+ * ```
2142
+ */
2143
+ var ThemeService = class {
2144
+ monorepoRoot;
2145
+ config;
2146
+ /**
2147
+ * Creates a new ThemeService instance
2148
+ * @param config - Design system configuration
2149
+ */
2150
+ constructor(config) {
2151
+ this.monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
2152
+ this.config = config;
2153
+ __agiflowai_aicode_utils.log.info(`[ThemeService] Using theme provider: ${this.config.themeProvider}`);
2154
+ __agiflowai_aicode_utils.log.info(`[ThemeService] Design system type: ${this.config.type}`);
2155
+ }
2156
+ /**
2157
+ * Get design system configuration
2158
+ * @returns Current design system configuration
2159
+ */
2160
+ getConfig() {
2161
+ return this.config;
2162
+ }
2163
+ /**
2164
+ * Get theme CSS imports from config or common locations
2165
+ * @returns Array of absolute paths to CSS files
2166
+ */
2167
+ async getThemeCSS() {
2168
+ if (this.config.cssFiles && this.config.cssFiles.length > 0) return this.config.cssFiles.map((cssFile) => node_path.default.isAbsolute(cssFile) ? cssFile : node_path.default.join(this.monorepoRoot, cssFile));
2169
+ const cssPatterns = ["**/packages/frontend/web-theme/src/**/*.css", "**/packages/frontend/shared-theme/**/*.css"];
2170
+ const cssFiles = [];
2171
+ for (const pattern of cssPatterns) {
2172
+ const files = await (0, glob.glob)(pattern, {
2173
+ cwd: this.monorepoRoot,
2174
+ absolute: true,
2175
+ ignore: ["**/node_modules/**", "**/dist/**"]
2176
+ });
2177
+ cssFiles.push(...files);
2178
+ }
2179
+ return cssFiles;
2180
+ }
2181
+ /**
2182
+ * Generate theme provider wrapper code
2183
+ * Uses default export as specified in config
2184
+ * @param componentCode - Component code to wrap
2185
+ * @param darkMode - Whether to use dark mode theme
2186
+ * @returns Generated wrapper code string
2187
+ */
2188
+ generateThemeWrapper(componentCode, darkMode = false) {
2189
+ const { themeProvider } = this.config;
2190
+ return `
2191
+ import React from 'react';
2192
+ import ThemeProvider from '${themeProvider}';
2193
+
2194
+ const WrappedComponent = () => {
2195
+ return React.createElement(
2196
+ ThemeProvider,
2197
+ { theme: ${darkMode ? "'dark'" : "'light'"} },
2198
+ ${componentCode}
2199
+ );
2200
+ };
2201
+
2202
+ export default WrappedComponent;
2203
+ `.trim();
2204
+ }
2205
+ /**
2206
+ * Get inline theme styles for SSR
2207
+ * @param darkMode - Whether to use dark mode styles
2208
+ * @returns Combined CSS content as string
2209
+ */
2210
+ async getInlineStyles(darkMode = false) {
2211
+ const cssFiles = await this.getThemeCSS();
2212
+ let styles = "";
2213
+ for (const cssFile of cssFiles) try {
2214
+ const content = await node_fs.promises.readFile(cssFile, "utf-8");
2215
+ styles += content + "\n";
2216
+ } catch (error) {
2217
+ __agiflowai_aicode_utils.log.warn(`[ThemeService] Could not read CSS file ${cssFile}:`, error);
2218
+ }
2219
+ if (darkMode && styles) styles = `.dark { ${styles} }`;
2220
+ return styles;
2221
+ }
2222
+ /**
2223
+ * Get Tailwind CSS classes for theming
2224
+ * @param darkMode - Whether to use dark mode classes
2225
+ * @returns Array of Tailwind class names
2226
+ */
2227
+ getTailwindClasses(darkMode = false) {
2228
+ const baseClasses = ["font-sans", "antialiased"];
2229
+ if (darkMode) return [
2230
+ ...baseClasses,
2231
+ "dark",
2232
+ "bg-gray-900",
2233
+ "text-white"
2234
+ ];
2235
+ return [
2236
+ ...baseClasses,
2237
+ "bg-white",
2238
+ "text-gray-900"
2239
+ ];
2240
+ }
2241
+ /**
2242
+ * Validate that the theme provider path exists
2243
+ * @returns True if theme provider is valid
2244
+ */
2245
+ async validateThemeProvider() {
2246
+ try {
2247
+ if (this.config.themeProvider.startsWith("@") || !this.config.themeProvider.startsWith("/")) return true;
2248
+ return (await node_fs.promises.stat(this.config.themeProvider)).isFile();
2249
+ } catch {
2250
+ return false;
2251
+ }
2252
+ }
2253
+ /**
2254
+ * List all available theme configurations
2255
+ * @returns Object containing themes array and active brand
2256
+ * @throws Error if themes directory cannot be read
2257
+ */
2258
+ async listAvailableThemes() {
2259
+ const configsPath = node_path.default.join(this.monorepoRoot, "packages/frontend/shared-theme/configs");
2260
+ try {
2261
+ const themeFiles = (await node_fs.promises.readdir(configsPath)).filter((file) => file.endsWith(".json"));
2262
+ const themes = [];
2263
+ let activeBrand;
2264
+ for (const file of themeFiles) {
2265
+ const filePath = node_path.default.join(configsPath, file);
2266
+ const content = await node_fs.promises.readFile(filePath, "utf-8");
2267
+ const themeData = JSON.parse(content);
2268
+ const themeName = file.replace(".json", "");
2269
+ themes.push({
2270
+ name: themeName,
2271
+ fileName: file,
2272
+ path: filePath,
2273
+ colors: themeData.colors || themeData
2274
+ });
2275
+ if (themeName === "lightTheme" || themeName === "agimonTheme") activeBrand = themeName;
2276
+ }
2277
+ return {
2278
+ themes: themes.sort((a, b) => a.name.localeCompare(b.name)),
2279
+ activeBrand
2280
+ };
2281
+ } catch (error) {
2282
+ throw new Error(`Failed to list themes: ${error instanceof Error ? error.message : String(error)}`);
2283
+ }
2284
+ }
2285
+ };
2286
+
2287
+ //#endregion
2288
+ //#region src/services/ThemeService/types.ts
2289
+ /**
2290
+ * Default theme service configuration
2291
+ */
2292
+ const DEFAULT_THEME_SERVICE_CONFIG = {
2293
+ themePath: void 0,
2294
+ cssFiles: [],
2295
+ customServicePath: void 0
2296
+ };
2297
+
2298
+ //#endregion
2299
+ //#region src/services/ThemeService/ThemeServiceFactory.ts
2300
+ /**
2301
+ * Factory for creating theme service instances.
2302
+ *
2303
+ * Supports the built-in CSSThemeService and custom service implementations
2304
+ * loaded dynamically from user-specified paths.
2305
+ *
2306
+ * @example
2307
+ * ```typescript
2308
+ * const factory = new ThemeServiceFactory();
2309
+ *
2310
+ * // Create default CSS-based service
2311
+ * const service = await factory.createService({
2312
+ * themePath: 'apps/my-app/src/styles/colors.css'
2313
+ * });
2314
+ *
2315
+ * // Create with custom service override
2316
+ * const customService = await factory.createService({
2317
+ * customServicePath: './my-custom-theme-service.ts'
2318
+ * });
2319
+ *
2320
+ * const themes = await service.listThemes();
2321
+ * ```
2322
+ */
2323
+ var ThemeServiceFactory = class {
2324
+ /**
2325
+ * Create a theme service based on configuration.
2326
+ *
2327
+ * Resolution order:
2328
+ * 1. If customServicePath is provided, load custom service dynamically
2329
+ * 2. Otherwise, create the default CSSThemeService
2330
+ *
2331
+ * @param config - Theme service configuration
2332
+ * @returns Promise resolving to a theme service instance
2333
+ * @throws Error if custom service cannot be loaded or is invalid
2334
+ */
2335
+ async createService(config = {}) {
2336
+ const resolvedConfig = {
2337
+ ...DEFAULT_THEME_SERVICE_CONFIG,
2338
+ ...config
2339
+ };
2340
+ if (resolvedConfig.customServicePath) return this.loadCustomService(resolvedConfig);
2341
+ return new CSSThemeService(resolvedConfig);
2342
+ }
2343
+ /**
2344
+ * Load a custom theme service from user-specified path.
2345
+ *
2346
+ * The custom service must export a class that extends BaseThemeService.
2347
+ *
2348
+ * @param config - Configuration with customServicePath set
2349
+ * @returns Promise resolving to custom service instance
2350
+ * @throws Error if service cannot be loaded or is invalid
2351
+ */
2352
+ async loadCustomService(config) {
2353
+ const servicePath = config.customServicePath;
2354
+ if (!servicePath) throw new Error("customServicePath is required for custom service loading");
2355
+ try {
2356
+ const customModule = await import(servicePath);
2357
+ const ServiceClass = customModule.default || customModule.ThemeService || customModule.CustomThemeService;
2358
+ if (!ServiceClass) throw new Error(`Custom service module at ${servicePath} must export a default class, ThemeService, or CustomThemeService that extends BaseThemeService`);
2359
+ const instance = new ServiceClass(config);
2360
+ if (!(instance instanceof BaseThemeService)) throw new Error(`Custom service at ${servicePath} must extend BaseThemeService`);
2361
+ return instance;
2362
+ } catch (error) {
2363
+ if (error instanceof Error && error.message.includes("BaseThemeService")) throw error;
2364
+ throw new Error(`Failed to load custom theme service from ${servicePath}: ${error instanceof Error ? error.message : String(error)}`);
2365
+ }
2366
+ }
2367
+ };
2368
+
2369
+ //#endregion
2370
+ //#region src/services/ComponentRendererService/ComponentRendererService.ts
2371
+ /**
2372
+ * ComponentRendererService handles rendering React components to images.
2373
+ *
2374
+ * Uses BundlerService for building/serving components and ThemeService
2375
+ * for theme configuration. Supports both dev server (fast) and static
2376
+ * build (fallback) rendering modes.
2377
+ *
2378
+ * The bundler service can be customized by providing a bundlerFactory
2379
+ * to support different bundlers (Vite, Webpack) and frameworks (React, Vue).
2380
+ *
2381
+ * @example
2382
+ * ```typescript
2383
+ * // Using default bundler (Vite + React)
2384
+ * const service = new ComponentRendererService(designConfig, 'apps/my-app');
2385
+ *
2386
+ * // Using custom bundler
2387
+ * const service = new ComponentRendererService(
2388
+ * designConfig,
2389
+ * 'apps/my-app',
2390
+ * { bundlerFactory: () => new MyCustomBundlerService() }
2391
+ * );
2392
+ *
2393
+ * const result = await service.renderComponent(componentInfo, {
2394
+ * storyName: 'Primary',
2395
+ * darkMode: true,
2396
+ * width: 1280,
2397
+ * height: 800
2398
+ * });
2399
+ * console.log(result.imagePath);
2400
+ * ```
2401
+ */
2402
+ var ComponentRendererService = class ComponentRendererService {
2403
+ monorepoRoot;
2404
+ tmpDir;
2405
+ themeService;
2406
+ appPath;
2407
+ bundlerFactory;
2408
+ /**
2409
+ * Creates a new ComponentRendererService instance
2410
+ * @param designSystemConfig - Design system configuration
2411
+ * @param appPath - Path to the app directory (relative or absolute)
2412
+ * @param options - Optional configuration including custom bundler factory
2413
+ */
2414
+ constructor(designSystemConfig, appPath, options = {}) {
2415
+ if (!appPath) throw new Error("appPath is required for ComponentRendererService");
2416
+ this.monorepoRoot = __agiflowai_aicode_utils.TemplatesManagerService.getWorkspaceRootSync();
2417
+ this.appPath = appPath;
2418
+ this.tmpDir = node_path.default.join(node_os.default.tmpdir(), "style-system");
2419
+ this.themeService = new ThemeService(designSystemConfig);
2420
+ this.bundlerFactory = options.bundlerFactory ?? createDefaultBundlerService;
2421
+ }
2422
+ /**
2423
+ * Get the bundler service instance.
2424
+ * Uses the factory to create/retrieve the bundler.
2425
+ * @returns The bundler service instance
2426
+ */
2427
+ getBundlerService() {
2428
+ return this.bundlerFactory();
2429
+ }
2430
+ /**
2431
+ * Render a component to an image
2432
+ * @param componentInfo - Component metadata from StoriesIndexService
2433
+ * @param options - Render options (story name, args, dimensions, etc.)
2434
+ * @returns Rendered image path, HTML content, and component info
2435
+ * @throws Error if rendering fails
2436
+ */
2437
+ async renderComponent(componentInfo, options = {}) {
2438
+ const { storyName = componentInfo.stories[0] || "Default", args = {}, darkMode = false, width = 1280, height = 800 } = options;
2439
+ try {
2440
+ __agiflowai_aicode_utils.log.info(`[ComponentRendererService] Rendering ${componentInfo.title} - ${storyName}`);
2441
+ await node_fs.promises.mkdir(this.tmpDir, { recursive: true });
2442
+ const designSystemConfig = this.themeService.getConfig();
2443
+ __agiflowai_aicode_utils.log.info(`[ComponentRendererService] Using theme provider: ${designSystemConfig.themeProvider}`);
2444
+ __agiflowai_aicode_utils.log.info(`[ComponentRendererService] Design system type: ${designSystemConfig.type}`);
2445
+ if (!await this.themeService.validateThemeProvider()) __agiflowai_aicode_utils.log.warn(`[ComponentRendererService] Theme provider path may not exist: ${designSystemConfig.themeProvider}`);
2446
+ const bundlerService = this.getBundlerService();
2447
+ let componentUrl;
2448
+ let htmlFilePath;
2449
+ if (bundlerService.isServerRunning()) {
2450
+ __agiflowai_aicode_utils.log.info("[ComponentRendererService] Using dev server for fast rendering");
2451
+ try {
2452
+ const result = await bundlerService.serveComponent({
2453
+ componentPath: componentInfo.filePath,
2454
+ storyName,
2455
+ args,
2456
+ themePath: designSystemConfig.themeProvider,
2457
+ darkMode,
2458
+ appPath: this.appPath,
2459
+ cssFiles: designSystemConfig.cssFiles || [],
2460
+ rootComponent: designSystemConfig.rootComponent
2461
+ });
2462
+ componentUrl = result.url;
2463
+ htmlFilePath = result.htmlFilePath;
2464
+ __agiflowai_aicode_utils.log.info(`[ComponentRendererService] Component served at: ${componentUrl}`);
2465
+ } catch (devServerError) {
2466
+ __agiflowai_aicode_utils.log.warn(`[ComponentRendererService] Dev server failed, falling back to static build: ${devServerError instanceof Error ? devServerError.message : String(devServerError)}`);
2467
+ const result = await bundlerService.prerenderComponent({
2468
+ componentPath: componentInfo.filePath,
2469
+ storyName,
2470
+ args,
2471
+ themePath: designSystemConfig.themeProvider,
2472
+ darkMode,
2473
+ appPath: this.appPath,
2474
+ cssFiles: designSystemConfig.cssFiles || [],
2475
+ rootComponent: designSystemConfig.rootComponent
2476
+ });
2477
+ componentUrl = `file://${result.htmlFilePath}`;
2478
+ htmlFilePath = result.htmlFilePath;
2479
+ __agiflowai_aicode_utils.log.info(`[ComponentRendererService] HTML built to: ${htmlFilePath}`);
2480
+ }
2481
+ } else {
2482
+ __agiflowai_aicode_utils.log.info("[ComponentRendererService] No dev server running, using static build");
2483
+ const result = await bundlerService.prerenderComponent({
2484
+ componentPath: componentInfo.filePath,
2485
+ storyName,
2486
+ args,
2487
+ themePath: designSystemConfig.themeProvider,
2488
+ darkMode,
2489
+ appPath: this.appPath,
2490
+ cssFiles: designSystemConfig.cssFiles || [],
2491
+ rootComponent: designSystemConfig.rootComponent
2492
+ });
2493
+ componentUrl = `file://${result.htmlFilePath}`;
2494
+ htmlFilePath = result.htmlFilePath;
2495
+ __agiflowai_aicode_utils.log.info(`[ComponentRendererService] HTML built to: ${htmlFilePath}`);
2496
+ }
2497
+ const timestamp = Date.now();
2498
+ const imageName = `component-${componentInfo.title.split("/").pop()}-${timestamp}.png`;
2499
+ const imagePath = node_path.default.join(this.tmpDir, imageName);
2500
+ await takeScreenshot({
2501
+ url: componentUrl,
2502
+ output: imagePath,
2503
+ width,
2504
+ height,
2505
+ fullPage: true,
2506
+ browser: "chromium",
2507
+ waitTime: 2e3,
2508
+ darkMode,
2509
+ mobile: false,
2510
+ generateThumbnail: true,
2511
+ thumbnailWidth: 900,
2512
+ thumbnailQuality: 80,
2513
+ base64: false
2514
+ });
2515
+ __agiflowai_aicode_utils.log.info(`[ComponentRendererService] Screenshot saved to: ${imagePath}`);
2516
+ let html = "";
2517
+ if (htmlFilePath) {
2518
+ html = await node_fs.promises.readFile(htmlFilePath, "utf-8");
2519
+ __agiflowai_aicode_utils.log.info(`[ComponentRendererService] HTML file kept at: ${htmlFilePath}`);
2520
+ } else __agiflowai_aicode_utils.log.warn("[ComponentRendererService] No HTML file path available, returning empty HTML content");
2521
+ return {
2522
+ imagePath,
2523
+ html,
2524
+ componentInfo
2525
+ };
2526
+ } catch (error) {
2527
+ const errorMessage = error instanceof Error ? error.message : String(error);
2528
+ throw new Error(`Failed to render component ${componentInfo.title} (${storyName}): ${errorMessage}`);
2529
+ }
2530
+ }
2531
+ /**
2532
+ * Maximum number of screenshot files to keep in temp directory.
2533
+ * Prevents disk space issues on long-running servers.
2534
+ */
2535
+ static MAX_TEMP_FILES = 100;
2536
+ /**
2537
+ * Clean up old rendered files.
2538
+ * Removes files older than the specified duration and enforces a max file count.
2539
+ * @param olderThanMs - Remove files older than this duration (default: 1 hour)
2540
+ */
2541
+ async cleanup(olderThanMs = 36e5) {
2542
+ try {
2543
+ const files = await node_fs.promises.readdir(this.tmpDir);
2544
+ const now = Date.now();
2545
+ const componentFiles = [];
2546
+ for (const file of files) if (file.startsWith("component-")) {
2547
+ const filePath = node_path.default.join(this.tmpDir, file);
2548
+ try {
2549
+ const stats = await node_fs.promises.stat(filePath);
2550
+ componentFiles.push({
2551
+ name: file,
2552
+ path: filePath,
2553
+ mtime: stats.mtimeMs
2554
+ });
2555
+ } catch {}
2556
+ }
2557
+ let deletedCount = 0;
2558
+ for (const file of componentFiles) if (now - file.mtime > olderThanMs) {
2559
+ await node_fs.promises.unlink(file.path).catch((err) => __agiflowai_aicode_utils.log.warn("[ComponentRendererService] Failed to delete file:", file.path, err));
2560
+ deletedCount++;
2561
+ }
2562
+ const remainingFiles = componentFiles.filter((f) => now - f.mtime <= olderThanMs);
2563
+ if (remainingFiles.length > ComponentRendererService.MAX_TEMP_FILES) {
2564
+ remainingFiles.sort((a, b) => a.mtime - b.mtime);
2565
+ const toDelete = remainingFiles.slice(0, remainingFiles.length - ComponentRendererService.MAX_TEMP_FILES);
2566
+ for (const file of toDelete) {
2567
+ await node_fs.promises.unlink(file.path).catch((err) => __agiflowai_aicode_utils.log.warn("[ComponentRendererService] Failed to delete file:", file.path, err));
2568
+ deletedCount++;
2569
+ }
2570
+ }
2571
+ if (deletedCount > 0) __agiflowai_aicode_utils.log.info(`[ComponentRendererService] Cleaned up ${deletedCount} temp files`);
2572
+ } catch (error) {
2573
+ __agiflowai_aicode_utils.log.error("[ComponentRendererService] Cleanup error:", error);
2574
+ }
2575
+ }
2576
+ /**
2577
+ * Cleanup bundler server and temp files.
2578
+ * Called on service shutdown.
2579
+ */
2580
+ async dispose() {
2581
+ try {
2582
+ await this.cleanup(0);
2583
+ const bundlerService = this.getBundlerService();
2584
+ if (!bundlerService.isServerRunning()) await bundlerService.cleanup();
2585
+ } catch (error) {
2586
+ __agiflowai_aicode_utils.log.error("[ComponentRendererService] Dispose error:", error);
2587
+ }
2588
+ }
2589
+ };
2590
+
2591
+ //#endregion
2592
+ //#region src/services/GetUiComponentService/types.ts
2593
+ /**
2594
+ * Default configuration values.
2595
+ */
2596
+ const DEFAULT_GET_UI_COMPONENT_CONFIG = {
2597
+ defaultStoryName: "Playground",
2598
+ defaultDarkMode: true,
2599
+ defaultWidth: 1280,
2600
+ defaultHeight: 800
2601
+ };
2602
+
2603
+ //#endregion
2604
+ //#region src/services/GetUiComponentService/GetUiComponentService.ts
2605
+ /**
2606
+ * GetUiComponentService handles rendering UI component previews.
2607
+ *
2608
+ * Locates components in the stories index, renders them with app-specific
2609
+ * design system configuration, and returns screenshot results.
2610
+ *
2611
+ * @example
2612
+ * ```typescript
2613
+ * const service = new GetUiComponentService();
2614
+ * const result = await service.getComponent({
2615
+ * componentName: 'Button',
2616
+ * appPath: 'apps/my-app',
2617
+ * });
2618
+ * console.log(result.imagePath);
2619
+ * ```
2620
+ */
2621
+ var GetUiComponentService = class {
2622
+ config;
2623
+ storiesIndexFactory;
2624
+ rendererFactory;
2625
+ /**
2626
+ * Creates a new GetUiComponentService instance.
2627
+ * @param config - Service configuration options
2628
+ * @param storiesIndexFactory - Factory for creating StoriesIndexService (for DI/testing)
2629
+ * @param rendererFactory - Factory for creating ComponentRendererService (for DI/testing)
2630
+ */
2631
+ constructor(config = {}, storiesIndexFactory = () => new StoriesIndexService(), rendererFactory = (c, p) => new ComponentRendererService(c, p)) {
2632
+ this.config = {
2633
+ ...DEFAULT_GET_UI_COMPONENT_CONFIG,
2634
+ ...config
2635
+ };
2636
+ this.storiesIndexFactory = storiesIndexFactory;
2637
+ this.rendererFactory = rendererFactory;
2638
+ }
2639
+ /**
2640
+ * Get a UI component preview image.
2641
+ *
2642
+ * @param input - Component input parameters
2643
+ * @returns Result with image path and component metadata
2644
+ * @throws Error if input validation fails, component not found, or rendering fails
2645
+ */
2646
+ async getComponent(input) {
2647
+ if (!input.componentName || typeof input.componentName !== "string") throw new Error("componentName is required and must be a non-empty string");
2648
+ if (!input.appPath || typeof input.appPath !== "string") throw new Error("appPath is required and must be a non-empty string");
2649
+ if (input.storyName !== void 0 && typeof input.storyName !== "string") throw new Error("storyName must be a string");
2650
+ if (input.darkMode !== void 0 && typeof input.darkMode !== "boolean") throw new Error("darkMode must be a boolean");
2651
+ if (input.selector !== void 0) {
2652
+ if (typeof input.selector !== "string") throw new Error("selector must be a string");
2653
+ if (!/^[a-zA-Z0-9_\-#.\[\]=":' ]+$/.test(input.selector)) throw new Error("selector contains invalid characters");
2654
+ if (/javascript:|expression\(|url\(/i.test(input.selector)) throw new Error("selector contains potentially malicious content");
2655
+ }
2656
+ const { componentName, appPath, storyName = this.config.defaultStoryName, darkMode = this.config.defaultDarkMode } = input;
2657
+ __agiflowai_aicode_utils.log.info(`[GetUiComponentService] Starting for component: ${componentName}, appPath: ${appPath}, storyName: ${storyName}`);
2658
+ const storiesIndex = this.storiesIndexFactory();
2659
+ try {
2660
+ await storiesIndex.initialize();
2661
+ } catch (error) {
2662
+ throw new Error(`Failed to initialize stories index: ${error instanceof Error ? error.message : String(error)}`);
2663
+ }
2664
+ const componentInfo = storiesIndex.findComponentByName(componentName);
2665
+ if (!componentInfo) throw new Error(`Component "${componentName}" not found in stories index. Ensure the component has a .stories.tsx file and has been indexed.`);
2666
+ __agiflowai_aicode_utils.log.info(`[GetUiComponentService] Found component: ${componentInfo.title}`);
2667
+ const validStoryName = this.resolveStoryName(storyName, componentInfo.stories);
2668
+ const designSystemConfig = await getAppDesignSystemConfig(appPath);
2669
+ __agiflowai_aicode_utils.log.info(`[GetUiComponentService] Using theme provider: ${designSystemConfig.themeProvider}`);
2670
+ __agiflowai_aicode_utils.log.info(`[GetUiComponentService] Design system type: ${designSystemConfig.type}`);
2671
+ const renderer = this.rendererFactory(designSystemConfig, appPath);
2672
+ try {
2673
+ const renderResult = await renderer.renderComponent(componentInfo, {
2674
+ storyName: validStoryName,
2675
+ width: this.config.defaultWidth,
2676
+ height: this.config.defaultHeight,
2677
+ darkMode
2678
+ });
2679
+ __agiflowai_aicode_utils.log.info(`[GetUiComponentService] Component rendered to: ${renderResult.imagePath}`);
2680
+ const storyFileContent = await this.readStoryFile(componentInfo.filePath);
2681
+ __agiflowai_aicode_utils.log.info("[GetUiComponentService] Completed successfully");
2682
+ return {
2683
+ imagePath: renderResult.imagePath,
2684
+ format: "png",
2685
+ dimensions: "900px width, 80% quality",
2686
+ storyFilePath: componentInfo.filePath,
2687
+ storyFileContent,
2688
+ componentTitle: componentInfo.title,
2689
+ availableStories: componentInfo.stories,
2690
+ renderedStory: validStoryName
2691
+ };
2692
+ } finally {
2693
+ await renderer.dispose();
2694
+ }
2695
+ }
2696
+ /**
2697
+ * Resolve and validate the story name.
2698
+ *
2699
+ * @param requestedStory - The requested story name
2700
+ * @param availableStories - List of available stories for the component
2701
+ * @returns Valid story name (requested or fallback)
2702
+ */
2703
+ resolveStoryName(requestedStory, availableStories) {
2704
+ if (availableStories.includes(requestedStory)) return requestedStory;
2705
+ __agiflowai_aicode_utils.log.warn(`[GetUiComponentService] Story "${requestedStory}" not found, available stories: ${availableStories.join(", ")}`);
2706
+ const fallbackStory = availableStories[0] || "Default";
2707
+ __agiflowai_aicode_utils.log.info(`[GetUiComponentService] Using fallback story: ${fallbackStory}`);
2708
+ return fallbackStory;
2709
+ }
2710
+ /**
2711
+ * Read the story file content.
2712
+ *
2713
+ * @param filePath - Path to the story file
2714
+ * @returns File content or error message
2715
+ */
2716
+ async readStoryFile(filePath) {
2717
+ try {
2718
+ const content = await node_fs.promises.readFile(filePath, "utf-8");
2719
+ __agiflowai_aicode_utils.log.info(`[GetUiComponentService] Story file read successfully (${content.length} chars)`);
2720
+ return content;
2721
+ } catch (error) {
2722
+ __agiflowai_aicode_utils.log.warn(`[GetUiComponentService] Warning: Could not read story file: ${error instanceof Error ? error.message : String(error)}`);
2723
+ return `// Could not read file: ${filePath}\n// Error: ${error instanceof Error ? error.message : String(error)}`;
2724
+ }
2725
+ }
2726
+ };
2727
+
2728
+ //#endregion
2729
+ //#region src/tools/GetComponentVisualTool.ts
2730
+ /**
2731
+ * Template for visual review instructions returned with component screenshots.
2732
+ * Guides the LLM to verify visual correctness of the rendered component.
2733
+ * @param imagePath - Path to the component screenshot image
2734
+ * @returns Formatted markdown string with visual review instructions
2735
+ */
2736
+ const REVIEW_INSTRUCTIONS_TEMPLATE = (imagePath) => `
2737
+ ## Visual Review Instructions
2738
+
2739
+ Please review the component screenshot and story code to verify visual correctness:
2740
+
2741
+ 1. **Read the screenshot** at: ${imagePath}
2742
+ 2. **Compare with the story code** to ensure the rendered output matches expectations
2743
+ 3. **Check for visual issues**:
2744
+ - Are all variants rendering correctly?
2745
+ - Are colors, spacing, and typography as expected?
2746
+ - Are interactive states (hover, disabled, loading) properly styled?
2747
+ - Is the layout and alignment correct?
2748
+
2749
+ If you notice any visual discrepancies between the code and the rendered output, please report them.
2750
+ `;
2751
+ var GetComponentVisualTool = class GetComponentVisualTool {
2752
+ static TOOL_NAME = "get_component_visual";
2753
+ service;
2754
+ /**
2755
+ * Creates a new GetComponentVisualTool instance.
2756
+ * @param service - Optional service instance for dependency injection (useful for testing)
2757
+ */
2758
+ constructor(service) {
2759
+ this.service = service ?? new GetUiComponentService();
2760
+ }
2761
+ /**
2762
+ * Returns the tool definition including name, description, and input schema.
2763
+ * @returns Tool definition with JSON Schema for input validation
2764
+ */
2765
+ getDefinition() {
2766
+ return {
2767
+ name: GetComponentVisualTool.TOOL_NAME,
2768
+ description: "Get a image preview of a UI component with app-specific design system configuration. Useful when work on the frontend design to review the UI quickly without running the full app.",
2769
+ inputSchema: {
2770
+ type: "object",
2771
+ properties: {
2772
+ componentName: {
2773
+ type: "string",
2774
+ minLength: 1,
2775
+ description: "The name of the component to capture (e.g., \"Button\", \"Card\", etc.)"
2776
+ },
2777
+ appPath: {
2778
+ type: "string",
2779
+ minLength: 1,
2780
+ description: "The app path (relative or absolute) to load design system configuration from (e.g., \"apps/agiflow-app\"). The design system config is read from {appPath}/project.json"
2781
+ },
2782
+ storyName: {
2783
+ type: "string",
2784
+ minLength: 1,
2785
+ description: "The story name to render (e.g., \"Playground\", \"Default\"). Defaults to \"Playground\"."
2786
+ },
2787
+ darkMode: {
2788
+ type: "boolean",
2789
+ description: "Whether to render the component in dark mode. Defaults to true."
2790
+ },
2791
+ selector: {
2792
+ type: "string",
2793
+ minLength: 1,
2794
+ description: "CSS selector to target specific element for screenshot. When provided, screenshot will auto-resize to element dimensions. Defaults to \"#root\"."
2795
+ }
2796
+ },
2797
+ required: ["componentName", "appPath"],
2798
+ additionalProperties: false
2799
+ }
2800
+ };
2801
+ }
2802
+ /**
2803
+ * Executes the tool to get a UI component preview.
2804
+ * @param input - The input parameters for getting the component
2805
+ * @returns Promise resolving to CallToolResult with component info or error
2806
+ */
2807
+ async execute(input) {
2808
+ try {
2809
+ const result = await this.service.getComponent(input);
2810
+ const reviewInstructions = REVIEW_INSTRUCTIONS_TEMPLATE(result.imagePath);
2811
+ return { content: [{
2812
+ type: "text",
2813
+ text: JSON.stringify(result, null, 2)
2814
+ }, {
2815
+ type: "text",
2816
+ text: reviewInstructions
2817
+ }] };
2818
+ } catch (error) {
2819
+ return {
2820
+ content: [{
2821
+ type: "text",
2822
+ text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`
2823
+ }],
2824
+ isError: true
2825
+ };
2826
+ }
2827
+ }
2828
+ };
2829
+
2830
+ //#endregion
2831
+ //#region src/tools/ListAppComponentsTool.ts
2832
+ /**
2833
+ * Tool to list app-specific components and package components used by an app.
2834
+ *
2835
+ * Detects app components by file path (within app directory) and resolves
2836
+ * workspace dependencies to find package components from Storybook stories.
2837
+ *
2838
+ * @example
2839
+ * ```typescript
2840
+ * const tool = new ListAppComponentsTool();
2841
+ * const result = await tool.execute({ appPath: 'apps/my-app' });
2842
+ * // Returns: { app: 'my-app', appComponents: ['Button'], packageComponents: {...}, pagination: {...} }
2843
+ * ```
2844
+ */
2845
+ var ListAppComponentsTool = class ListAppComponentsTool {
2846
+ static TOOL_NAME = "list_app_components";
2847
+ static COMPONENT_REUSE_INSTRUCTION = "IMPORTANT: Before creating new components, check if a similar component already exists in the list above. Always reuse existing components to maintain consistency and reduce code duplication.";
2848
+ service;
2849
+ constructor() {
2850
+ this.service = new AppComponentsService();
2851
+ }
2852
+ /**
2853
+ * Gets the tool definition including name, description, and input schema.
2854
+ * @returns Tool definition for MCP registration
2855
+ */
2856
+ getDefinition() {
2857
+ return {
2858
+ name: ListAppComponentsTool.TOOL_NAME,
2859
+ description: "List app-specific components and package components used by an app. Call this tool BEFORE creating new components to check if a similar component already exists. Reads the app's package.json to find workspace dependencies and returns components from both the app and its dependent packages.",
2860
+ inputSchema: {
2861
+ type: "object",
2862
+ properties: {
2863
+ appPath: {
2864
+ type: "string",
2865
+ description: "The app path (relative or absolute) to list components for (e.g., \"apps/my-app\")",
2866
+ minLength: 1
2867
+ },
2868
+ cursor: {
2869
+ type: "string",
2870
+ description: "Optional pagination cursor to fetch the next page of results. Omit to fetch the first page."
2871
+ }
2872
+ },
2873
+ required: ["appPath"],
2874
+ additionalProperties: false
2875
+ }
2876
+ };
2877
+ }
2878
+ /**
2879
+ * Lists app-specific and package components for a given application.
2880
+ * @param input - Object containing appPath and optional cursor for pagination
2881
+ * @returns CallToolResult with component list or error
2882
+ */
2883
+ async execute(input) {
2884
+ try {
2885
+ const result = await this.service.listComponents({
2886
+ appPath: input.appPath,
2887
+ cursor: input.cursor
2888
+ });
2889
+ return { content: [{
2890
+ type: "text",
2891
+ text: `${ListAppComponentsTool.COMPONENT_REUSE_INSTRUCTION}\n\n${JSON.stringify(result, null, 2)}`
2892
+ }] };
2893
+ } catch (error) {
2894
+ return {
2895
+ content: [{
2896
+ type: "text",
2897
+ text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`
2898
+ }],
2899
+ isError: true
2900
+ };
2901
+ }
2902
+ }
2903
+ };
2904
+
2905
+ //#endregion
2906
+ //#region src/tools/ListThemesTool.ts
2907
+ /**
2908
+ * Tool to list all available theme configurations.
2909
+ *
2910
+ * Reads themes from CSS files configured in the app's project.json
2911
+ * style-system config. Themes are extracted by parsing CSS class selectors
2912
+ * that contain color variable definitions.
2913
+ *
2914
+ * @example
2915
+ * ```typescript
2916
+ * const tool = new ListThemesTool();
2917
+ * const result = await tool.execute({ appPath: 'apps/my-app' });
2918
+ * // Returns: { themes: [{ name: 'slate', ... }, { name: 'blue', ... }], source: 'css-file' }
2919
+ * ```
2920
+ */
2921
+ var ListThemesTool = class ListThemesTool {
2922
+ static TOOL_NAME = "list_themes";
2923
+ serviceFactory;
2924
+ constructor() {
2925
+ this.serviceFactory = new ThemeServiceFactory();
2926
+ }
2927
+ getDefinition() {
2928
+ return {
2929
+ name: ListThemesTool.TOOL_NAME,
2930
+ description: "List all available theme configurations. Reads themes from CSS files configured in the app's project.json style-system config.",
2931
+ inputSchema: {
2932
+ type: "object",
2933
+ properties: { appPath: {
2934
+ type: "string",
2935
+ description: "App path (relative or absolute) to read theme config from project.json (e.g., \"apps/my-app\")"
2936
+ } },
2937
+ additionalProperties: false
2938
+ }
2939
+ };
2940
+ }
2941
+ async execute(input) {
2942
+ try {
2943
+ let themePath;
2944
+ let cssFiles;
2945
+ if (input.appPath) {
2946
+ const appConfig = await getAppDesignSystemConfig(input.appPath);
2947
+ themePath = appConfig.themePath;
2948
+ cssFiles = appConfig.cssFiles;
2949
+ }
2950
+ const result = await (await this.serviceFactory.createService({
2951
+ themePath,
2952
+ cssFiles
2953
+ })).listThemes();
2954
+ return { content: [{
2955
+ type: "text",
2956
+ text: JSON.stringify(result, null, 2)
2957
+ }] };
2958
+ } catch (error) {
2959
+ return {
2960
+ content: [{
2961
+ type: "text",
2962
+ text: `Error: ${error instanceof Error ? error.message : "Unknown error"}`
2963
+ }],
2964
+ isError: true
2965
+ };
2966
+ }
2967
+ }
2968
+ };
2969
+
2970
+ //#endregion
2971
+ //#region src/tools/ListSharedComponentsTool.ts
2972
+ var ListSharedComponentsTool = class ListSharedComponentsTool {
2973
+ static TOOL_NAME = "list_shared_components";
2974
+ static PAGE_SIZE = 50;
2975
+ static COMPONENT_REUSE_INSTRUCTION = "IMPORTANT: Before creating new components, check if a similar component already exists in the list above. Always reuse existing shared components to maintain consistency and reduce code duplication.";
2976
+ /**
2977
+ * Encode pagination state into an opaque cursor string
2978
+ * @param offset - The current offset in the component list
2979
+ * @returns Base64 encoded cursor string
2980
+ */
2981
+ encodeCursor(offset) {
2982
+ return Buffer.from(JSON.stringify({ offset })).toString("base64");
2983
+ }
2984
+ /**
2985
+ * Decode cursor string into pagination state
2986
+ * @param cursor - Base64 encoded cursor string
2987
+ * @returns Object containing the offset
2988
+ */
2989
+ decodeCursor(cursor) {
2990
+ try {
2991
+ const decoded = Buffer.from(cursor, "base64").toString("utf-8");
2992
+ return { offset: JSON.parse(decoded).offset || 0 };
2993
+ } catch {
2994
+ return { offset: 0 };
2995
+ }
2996
+ }
2997
+ /**
2998
+ * Gets the tool definition including name, description, and input schema.
2999
+ * @returns Tool definition for MCP registration
3000
+ */
3001
+ getDefinition() {
3002
+ return {
3003
+ name: ListSharedComponentsTool.TOOL_NAME,
3004
+ description: "List shared UI components from the design system. Call this tool BEFORE creating new components to check if a similar component already exists. Use the \"tags\" parameter to filter by specific tags. If no tags provided, uses configured sharedComponentTags from toolkit.yaml (default: style-system). The response includes \"availableTags\" showing all tags in the codebase for filtering.",
3005
+ inputSchema: {
3006
+ type: "object",
3007
+ properties: {
3008
+ tags: {
3009
+ type: "array",
3010
+ items: { type: "string" },
3011
+ description: "Optional array of tags to filter components by. If not provided, uses configured sharedComponentTags from toolkit.yaml. Pass an empty array [] to list ALL components."
3012
+ },
3013
+ cursor: {
3014
+ type: "string",
3015
+ description: "Optional pagination cursor to fetch the next page of results. Omit to fetch the first page."
3016
+ }
3017
+ },
3018
+ additionalProperties: false
3019
+ }
3020
+ };
3021
+ }
3022
+ /**
3023
+ * Lists shared UI components from the design system.
3024
+ * @param input - Object containing optional tags filter and cursor for pagination
3025
+ * @returns CallToolResult with component list or error
3026
+ */
3027
+ async execute(input) {
3028
+ try {
3029
+ const { cursor, tags: inputTags } = input;
3030
+ const { offset } = cursor ? this.decodeCursor(cursor) : { offset: 0 };
3031
+ const storiesIndex = new StoriesIndexService();
3032
+ await storiesIndex.initialize();
3033
+ const availableTags = storiesIndex.getAllTags();
3034
+ let filterTags;
3035
+ if (inputTags !== void 0) filterTags = inputTags;
3036
+ else filterTags = await getSharedComponentTags();
3037
+ const allComponentNames = storiesIndex.getComponentsByTags(filterTags.length > 0 ? filterTags : void 0).map((component) => component.title.split("/").pop() || component.title).filter((name, index, self) => self.indexOf(name) === index).sort();
3038
+ const totalComponents = allComponentNames.length;
3039
+ const paginatedComponents = allComponentNames.slice(offset, offset + ListSharedComponentsTool.PAGE_SIZE);
3040
+ const hasMore = offset + paginatedComponents.length < totalComponents;
3041
+ const result = {
3042
+ filteredByTags: filterTags,
3043
+ availableTags,
3044
+ components: paginatedComponents,
3045
+ pagination: {
3046
+ offset,
3047
+ pageSize: ListSharedComponentsTool.PAGE_SIZE,
3048
+ totalComponents,
3049
+ hasMore
3050
+ }
3051
+ };
3052
+ if (hasMore) result.nextCursor = this.encodeCursor(offset + paginatedComponents.length);
3053
+ __agiflowai_aicode_utils.log.info(`[ListSharedComponentsTool] Tags: [${filterTags.join(", ")}], Page ${Math.floor(offset / ListSharedComponentsTool.PAGE_SIZE) + 1}: Returned ${paginatedComponents.length} of ${totalComponents} total components (hasMore: ${hasMore})`);
3054
+ return { content: [{
3055
+ type: "text",
3056
+ text: `${ListSharedComponentsTool.COMPONENT_REUSE_INSTRUCTION}\n\n${JSON.stringify(result, null, 2)}`
3057
+ }] };
3058
+ } catch (error) {
3059
+ return {
3060
+ content: [{
3061
+ type: "text",
3062
+ text: `Failed to list shared components: ${error instanceof Error ? error.message : "Unknown error"}`
3063
+ }],
3064
+ isError: true
3065
+ };
3066
+ }
3067
+ }
3068
+ };
3069
+
3070
+ //#endregion
3071
+ //#region src/server/index.ts
3072
+ function createServer(themePath = "packages/frontend/web-theme/src/agimon-theme.css") {
3073
+ const server = new __modelcontextprotocol_sdk_server_index_js.Server({
3074
+ name: "style-system-mcp",
3075
+ version: "0.1.0"
3076
+ }, {
3077
+ capabilities: { tools: {} },
3078
+ instructions: "Use this MCP when you work on the frontend or design. You can use this to list supported themes, get the correct tailwind classes supported by the repo, list design system components, and get visual + detailed implementation of components."
3079
+ });
3080
+ const listThemesTool = new ListThemesTool();
3081
+ const getCSSClassesTool = new GetCSSClassesTool(themePath);
3082
+ const getComponentVisualTool = new GetComponentVisualTool();
3083
+ const listSharedComponentsTool = new ListSharedComponentsTool();
3084
+ const listAppComponentsTool = new ListAppComponentsTool();
3085
+ server.setRequestHandler(__modelcontextprotocol_sdk_types_js.ListToolsRequestSchema, async () => {
3086
+ return { tools: [
3087
+ listThemesTool.getDefinition(),
3088
+ getCSSClassesTool.getDefinition(),
3089
+ getComponentVisualTool.getDefinition(),
3090
+ listSharedComponentsTool.getDefinition(),
3091
+ listAppComponentsTool.getDefinition()
3092
+ ] };
3093
+ });
3094
+ server.setRequestHandler(__modelcontextprotocol_sdk_types_js.CallToolRequestSchema, async (request) => {
3095
+ const { name, arguments: args } = request.params;
3096
+ if (name === ListThemesTool.TOOL_NAME) return await listThemesTool.execute({});
3097
+ if (name === GetCSSClassesTool.TOOL_NAME) return await getCSSClassesTool.execute(args);
3098
+ if (name === GetComponentVisualTool.TOOL_NAME) return await getComponentVisualTool.execute(args);
3099
+ if (name === ListSharedComponentsTool.TOOL_NAME) return await listSharedComponentsTool.execute({});
3100
+ if (name === ListAppComponentsTool.TOOL_NAME) return await listAppComponentsTool.execute(args);
3101
+ return {
3102
+ content: [{
3103
+ type: "text",
3104
+ text: `Error: Unknown tool: ${name}`
3105
+ }],
3106
+ isError: true
3107
+ };
3108
+ });
3109
+ return server;
3110
+ }
3111
+
3112
+ //#endregion
3113
+ //#region src/transports/stdio.ts
3114
+ /**
3115
+ * Stdio transport handler for MCP server
3116
+ * Used for command-line and direct integrations
3117
+ */
3118
+ var StdioTransportHandler = class {
3119
+ server;
3120
+ transport = null;
3121
+ constructor(server) {
3122
+ this.server = server;
3123
+ }
3124
+ async start() {
3125
+ this.transport = new __modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
3126
+ await this.server.connect(this.transport);
3127
+ __agiflowai_aicode_utils.log.info("style-system-mcp MCP server started on stdio");
3128
+ }
3129
+ async stop() {
3130
+ if (this.transport) {
3131
+ await this.transport.close();
3132
+ this.transport = null;
3133
+ }
3134
+ }
3135
+ };
3136
+
3137
+ //#endregion
3138
+ Object.defineProperty(exports, 'BaseBundlerService', {
3139
+ enumerable: true,
3140
+ get: function () {
3141
+ return BaseBundlerService;
3142
+ }
3143
+ });
3144
+ Object.defineProperty(exports, 'BaseCSSClassesService', {
3145
+ enumerable: true,
3146
+ get: function () {
3147
+ return BaseCSSClassesService;
3148
+ }
3149
+ });
3150
+ Object.defineProperty(exports, 'CSSClassesServiceFactory', {
3151
+ enumerable: true,
3152
+ get: function () {
3153
+ return CSSClassesServiceFactory;
3154
+ }
3155
+ });
3156
+ Object.defineProperty(exports, 'ComponentRendererService', {
3157
+ enumerable: true,
3158
+ get: function () {
3159
+ return ComponentRendererService;
3160
+ }
3161
+ });
3162
+ Object.defineProperty(exports, 'DEFAULT_STYLE_SYSTEM_CONFIG', {
3163
+ enumerable: true,
3164
+ get: function () {
3165
+ return DEFAULT_STYLE_SYSTEM_CONFIG;
3166
+ }
3167
+ });
3168
+ Object.defineProperty(exports, 'GetCSSClassesTool', {
3169
+ enumerable: true,
3170
+ get: function () {
3171
+ return GetCSSClassesTool;
3172
+ }
3173
+ });
3174
+ Object.defineProperty(exports, 'GetComponentVisualTool', {
3175
+ enumerable: true,
3176
+ get: function () {
3177
+ return GetComponentVisualTool;
3178
+ }
3179
+ });
3180
+ Object.defineProperty(exports, 'ListAppComponentsTool', {
3181
+ enumerable: true,
3182
+ get: function () {
3183
+ return ListAppComponentsTool;
3184
+ }
3185
+ });
3186
+ Object.defineProperty(exports, 'ListSharedComponentsTool', {
3187
+ enumerable: true,
3188
+ get: function () {
3189
+ return ListSharedComponentsTool;
3190
+ }
3191
+ });
3192
+ Object.defineProperty(exports, 'ListThemesTool', {
3193
+ enumerable: true,
3194
+ get: function () {
3195
+ return ListThemesTool;
3196
+ }
3197
+ });
3198
+ Object.defineProperty(exports, 'StdioTransportHandler', {
3199
+ enumerable: true,
3200
+ get: function () {
3201
+ return StdioTransportHandler;
3202
+ }
3203
+ });
3204
+ Object.defineProperty(exports, 'StoriesIndexService', {
3205
+ enumerable: true,
3206
+ get: function () {
3207
+ return StoriesIndexService;
3208
+ }
3209
+ });
3210
+ Object.defineProperty(exports, 'TailwindCSSClassesService', {
3211
+ enumerable: true,
3212
+ get: function () {
3213
+ return TailwindCSSClassesService;
3214
+ }
3215
+ });
3216
+ Object.defineProperty(exports, 'ThemeService', {
3217
+ enumerable: true,
3218
+ get: function () {
3219
+ return ThemeService;
3220
+ }
3221
+ });
3222
+ Object.defineProperty(exports, 'ViteReactBundlerService', {
3223
+ enumerable: true,
3224
+ get: function () {
3225
+ return ViteReactBundlerService;
3226
+ }
3227
+ });
3228
+ Object.defineProperty(exports, '__toESM', {
3229
+ enumerable: true,
3230
+ get: function () {
3231
+ return __toESM;
3232
+ }
3233
+ });
3234
+ Object.defineProperty(exports, 'createDefaultBundlerService', {
3235
+ enumerable: true,
3236
+ get: function () {
3237
+ return createDefaultBundlerService;
3238
+ }
3239
+ });
3240
+ Object.defineProperty(exports, 'createServer', {
3241
+ enumerable: true,
3242
+ get: function () {
3243
+ return createServer;
3244
+ }
3245
+ });
3246
+ Object.defineProperty(exports, 'getBundlerServiceFromConfig', {
3247
+ enumerable: true,
3248
+ get: function () {
3249
+ return getBundlerServiceFromConfig;
3250
+ }
3251
+ });