@tpitre/story-ui 1.7.1 → 2.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.
Files changed (35) hide show
  1. package/.env.sample +3 -1
  2. package/README.md +160 -606
  3. package/dist/cli/index.js +23 -24
  4. package/dist/cli/setup.js +295 -36
  5. package/dist/mcp-server/index.js +67 -0
  6. package/dist/mcp-server/routes/generateStory.js +323 -56
  7. package/dist/story-generator/componentBlacklist.js +181 -0
  8. package/dist/story-generator/componentDiscovery.js +9 -2
  9. package/dist/story-generator/configLoader.js +109 -39
  10. package/dist/story-generator/considerationsLoader.js +204 -0
  11. package/dist/story-generator/documentation-sources.js +36 -0
  12. package/dist/story-generator/documentationLoader.js +214 -0
  13. package/dist/story-generator/dynamicPackageDiscovery.js +527 -0
  14. package/dist/story-generator/enhancedComponentDiscovery.js +369 -118
  15. package/dist/story-generator/generateStory.js +7 -3
  16. package/dist/story-generator/postProcessStory.js +71 -0
  17. package/dist/story-generator/promptGenerator.js +286 -37
  18. package/dist/story-generator/storyHistory.js +118 -0
  19. package/dist/story-generator/storyTracker.js +33 -18
  20. package/dist/story-generator/storyValidator.js +39 -0
  21. package/dist/story-generator/universalDesignSystemAdapter.js +209 -0
  22. package/dist/story-generator/validateStory.js +82 -7
  23. package/dist/story-ui.config.js +12 -5
  24. package/package.json +11 -6
  25. package/templates/StoryUI/StoryUIPanel.stories.tsx +29 -13
  26. package/templates/StoryUI/StoryUIPanel.tsx +489 -359
  27. package/templates/react-import-rule.json +36 -0
  28. package/templates/story-generation-rules.json +29 -0
  29. package/templates/story-ui-considerations.json +156 -0
  30. package/templates/story-ui-considerations.md +109 -0
  31. package/templates/story-ui-docs-README.md +55 -0
  32. package/dist/scripts/test-validation.js +0 -81
  33. package/dist/test-storybooks/chakra-test/src/components/index.js +0 -3
  34. package/dist/test-storybooks/custom-design-test/src/components/index.js +0 -3
  35. package/dist/tsconfig.tsbuildinfo +0 -1
package/dist/cli/index.js CHANGED
@@ -7,6 +7,7 @@ import { fileURLToPath } from 'url';
7
7
  import { setupProductionGitignore } from '../story-generator/productionGitignoreManager.js';
8
8
  import { createStoryUIConfig } from '../story-ui.config.js';
9
9
  import { setupCommand } from './setup.js';
10
+ import net from 'net';
10
11
  const __filename = fileURLToPath(import.meta.url);
11
12
  const __dirname = path.dirname(__filename);
12
13
  const program = new Command();
@@ -25,16 +26,34 @@ program
25
26
  .description('Start the Story UI server')
26
27
  .option('-p, --port <port>', 'Port to run the server on', '4001')
27
28
  .option('-c, --config <config>', 'Path to configuration file')
28
- .action((options) => {
29
+ .action(async (options) => {
29
30
  console.log('šŸš€ Starting Story UI server...');
30
31
  // Use absolute path to avoid dist/dist issue when package is linked
31
32
  const pkgRoot = path.resolve(__dirname, '..');
32
33
  const serverPath = path.join(pkgRoot, 'mcp-server/index.js');
33
34
  console.log(`āœ… Using MCP server at: ${serverPath}`);
34
- const env = { ...process.env };
35
- if (options.port) {
36
- env.PORT = options.port;
35
+ // FIRST_EDIT: determine an available port
36
+ const requestedPort = parseInt(options.port || '4001', 10);
37
+ const isPortFree = (port) => {
38
+ return new Promise((resolve) => {
39
+ const tester = net.createServer()
40
+ .once('error', () => resolve(false))
41
+ .once('listening', () => {
42
+ tester.close();
43
+ resolve(true);
44
+ })
45
+ .listen(port);
46
+ });
47
+ };
48
+ let finalPort = requestedPort;
49
+ // eslint-disable-next-line no-await-in-loop
50
+ while (!(await isPortFree(finalPort))) {
51
+ finalPort += 1;
37
52
  }
53
+ if (finalPort !== requestedPort) {
54
+ console.log(`āš ļø Port ${requestedPort} is in use. Using ${finalPort} instead.`);
55
+ }
56
+ const env = { ...process.env, PORT: String(finalPort) };
38
57
  if (options.config) {
39
58
  env.STORY_UI_CONFIG_PATH = options.config;
40
59
  }
@@ -131,26 +150,6 @@ async function autoDetectAndCreateConfig() {
131
150
  async function createTemplateConfig(template) {
132
151
  let config;
133
152
  switch (template) {
134
- case 'material-ui':
135
- config = {
136
- importPath: '@mui/material',
137
- componentPrefix: '',
138
- layoutRules: {
139
- multiColumnWrapper: 'Grid',
140
- columnComponent: 'Grid',
141
- layoutExamples: {
142
- twoColumn: `<Grid container spacing={2}>
143
- <Grid item xs={6}>
144
- <Card>Left content</Card>
145
- </Grid>
146
- <Grid item xs={6}>
147
- <Card>Right content</Card>
148
- </Grid>
149
- </Grid>`
150
- }
151
- }
152
- };
153
- break;
154
153
  case 'chakra-ui':
155
154
  config = {
156
155
  importPath: '@chakra-ui/react',
package/dist/cli/setup.js CHANGED
@@ -4,8 +4,128 @@ import inquirer from 'inquirer';
4
4
  import chalk from 'chalk';
5
5
  import { autoDetectDesignSystem } from '../story-generator/configLoader.js';
6
6
  import { fileURLToPath } from 'url';
7
+ import net from 'net';
8
+ import { execSync } from 'child_process';
7
9
  const __filename = fileURLToPath(import.meta.url);
8
10
  const __dirname = path.dirname(__filename);
11
+ // FIRST_EDIT: helper functions to check for free ports
12
+ async function isPortAvailable(port) {
13
+ return new Promise((resolve) => {
14
+ const tester = net.createServer()
15
+ .once('error', () => resolve(false))
16
+ .once('listening', () => {
17
+ tester.close();
18
+ resolve(true);
19
+ })
20
+ .listen(port);
21
+ });
22
+ }
23
+ async function findAvailablePort(startPort) {
24
+ let port = startPort;
25
+ // eslint-disable-next-line no-await-in-loop
26
+ while (!(await isPortAvailable(port))) {
27
+ port += 1;
28
+ }
29
+ return port;
30
+ }
31
+ /**
32
+ * Clean up default Storybook template components that could conflict with design system discovery
33
+ */
34
+ function cleanupDefaultStorybookComponents() {
35
+ const storiesDir = path.join(process.cwd(), 'src', 'stories');
36
+ // Common default Storybook files that cause conflicts
37
+ const defaultFiles = [
38
+ 'Button.stories.ts',
39
+ 'Button.stories.tsx',
40
+ 'Header.stories.ts',
41
+ 'Header.stories.tsx',
42
+ 'Page.stories.ts',
43
+ 'Page.stories.tsx',
44
+ 'button.css',
45
+ 'header.css',
46
+ 'page.css',
47
+ 'Button.tsx',
48
+ 'Header.tsx',
49
+ 'Page.tsx'
50
+ ];
51
+ let cleanedFiles = 0;
52
+ for (const fileName of defaultFiles) {
53
+ const filePath = path.join(storiesDir, fileName);
54
+ if (fs.existsSync(filePath)) {
55
+ try {
56
+ fs.unlinkSync(filePath);
57
+ cleanedFiles++;
58
+ }
59
+ catch (error) {
60
+ console.warn(`Could not remove ${fileName}: ${error}`);
61
+ }
62
+ }
63
+ }
64
+ if (cleanedFiles > 0) {
65
+ console.log(chalk.green(`āœ… Cleaned up ${cleanedFiles} default Storybook template files to prevent conflicts`));
66
+ }
67
+ }
68
+ // Design system installation configurations
69
+ const DESIGN_SYSTEM_CONFIGS = {
70
+ antd: {
71
+ packages: ['antd'],
72
+ name: 'Ant Design',
73
+ importPath: 'antd',
74
+ additionalSetup: 'import "antd/dist/reset.css";'
75
+ },
76
+ mantine: {
77
+ packages: ['@mantine/core', '@mantine/hooks', '@mantine/notifications'],
78
+ name: 'Mantine',
79
+ importPath: '@mantine/core',
80
+ additionalSetup: 'import "@mantine/core/styles.css";'
81
+ },
82
+ chakra: {
83
+ packages: ['@chakra-ui/react', '@emotion/react', '@emotion/styled', 'framer-motion'],
84
+ name: 'Chakra UI',
85
+ importPath: '@chakra-ui/react',
86
+ additionalSetup: 'import { ChakraProvider } from "@chakra-ui/react";'
87
+ }
88
+ };
89
+ async function installDesignSystem(systemKey) {
90
+ const config = DESIGN_SYSTEM_CONFIGS[systemKey];
91
+ const packageJson = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf-8'));
92
+ // Check if packages are already installed
93
+ const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies };
94
+ const missingPackages = config.packages.filter(pkg => !dependencies[pkg]);
95
+ if (missingPackages.length === 0) {
96
+ console.log(chalk.green(`āœ… ${config.name} packages already installed`));
97
+ return true;
98
+ }
99
+ console.log(chalk.blue(`\nšŸ“¦ Installing ${config.name} packages...`));
100
+ console.log(chalk.gray(`Packages: ${missingPackages.join(', ')}`));
101
+ // Detect package manager
102
+ const npmLock = fs.existsSync(path.join(process.cwd(), 'package-lock.json'));
103
+ const yarnLock = fs.existsSync(path.join(process.cwd(), 'yarn.lock'));
104
+ const pnpmLock = fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'));
105
+ let installCommand = `npm install ${missingPackages.join(' ')}`;
106
+ if (yarnLock) {
107
+ installCommand = `yarn add ${missingPackages.join(' ')}`;
108
+ }
109
+ else if (pnpmLock) {
110
+ installCommand = `pnpm add ${missingPackages.join(' ')}`;
111
+ }
112
+ try {
113
+ console.log(chalk.gray(`Running: ${installCommand}`));
114
+ execSync(installCommand, { stdio: 'inherit' });
115
+ console.log(chalk.green(`āœ… ${config.name} installed successfully!`));
116
+ if (config.additionalSetup) {
117
+ console.log(chalk.blue('\nšŸ“‹ Additional setup required:'));
118
+ console.log(chalk.gray(`Add this import to your main CSS/index file:`));
119
+ console.log(chalk.cyan(`${config.additionalSetup}`));
120
+ }
121
+ return true;
122
+ }
123
+ catch (error) {
124
+ console.error(chalk.red(`āŒ Failed to install ${config.name}:`), error);
125
+ console.log(chalk.yellow(`\nšŸ’” You can install manually with: ${installCommand}`));
126
+ return false;
127
+ }
128
+ }
9
129
  export async function setupCommand() {
10
130
  console.log(chalk.blue.bold('\nšŸŽØ Story UI Setup\n'));
11
131
  console.log('This will help you configure Story UI for your design system.\n');
@@ -60,14 +180,25 @@ export async function setupCommand() {
60
180
  message: 'Which design system are you using?',
61
181
  choices: [
62
182
  { name: 'šŸ¤– Auto-detect from package.json', value: 'auto' },
63
- { name: 'šŸŽØ Material-UI (@mui/material)', value: 'mui' },
64
- { name: '⚔ Chakra UI (@chakra-ui/react)', value: 'chakra' },
65
- { name: '🐜 Ant Design (antd)', value: 'antd' },
66
- { name: 'šŸŽÆ Mantine (@mantine/core)', value: 'mantine' },
183
+ { name: '🐜 Ant Design (antd) - Install & Configure', value: 'antd' },
184
+ { name: 'šŸŽÆ Mantine (@mantine/core) - Install & Configure', value: 'mantine' },
185
+ { name: '⚔ Chakra UI (@chakra-ui/react) - Install & Configure', value: 'chakra' },
67
186
  { name: 'šŸ”§ Custom/Other', value: 'custom' }
68
187
  ],
69
188
  default: autoDetected ? 'auto' : 'custom'
70
189
  },
190
+ {
191
+ type: 'confirm',
192
+ name: 'installDesignSystem',
193
+ message: (answers) => {
194
+ const systemName = answers.designSystem === 'antd' ? 'Ant Design' :
195
+ answers.designSystem === 'mantine' ? 'Mantine' :
196
+ answers.designSystem === 'chakra' ? 'Chakra UI' : 'the design system';
197
+ return `Would you like to install ${systemName} and its dependencies now?`;
198
+ },
199
+ when: (answers) => ['antd', 'mantine', 'chakra'].includes(answers.designSystem),
200
+ default: true
201
+ },
71
202
  {
72
203
  type: 'input',
73
204
  name: 'importPath',
@@ -96,6 +227,22 @@ export async function setupCommand() {
96
227
  default: './src/components',
97
228
  when: (answers) => answers.designSystem === 'custom'
98
229
  },
230
+ {
231
+ type: 'input',
232
+ name: 'mcpPort',
233
+ message: 'Port for the Story UI MCP server',
234
+ default: async () => {
235
+ const port = await findAvailablePort(4001);
236
+ return String(port);
237
+ },
238
+ validate: async (input) => {
239
+ const value = parseInt(input, 10);
240
+ if (isNaN(value) || value <= 0)
241
+ return 'Enter a valid port number';
242
+ const available = await isPortAvailable(value);
243
+ return available ? true : `Port ${value} is already in use`;
244
+ }
245
+ },
99
246
  {
100
247
  type: 'confirm',
101
248
  name: 'hasApiKey',
@@ -110,53 +257,53 @@ export async function setupCommand() {
110
257
  validate: (input) => input.trim() ? true : 'API key is required'
111
258
  }
112
259
  ]);
260
+ // Install design system if requested
261
+ if (answers.installDesignSystem && ['antd', 'mantine', 'chakra'].includes(answers.designSystem)) {
262
+ const installSuccess = await installDesignSystem(answers.designSystem);
263
+ if (!installSuccess) {
264
+ console.log(chalk.yellow('āš ļø Installation failed but continuing with configuration...'));
265
+ }
266
+ }
113
267
  // Generate configuration
114
268
  let config = {};
115
269
  if (answers.designSystem === 'auto' && autoDetected) {
116
270
  config = autoDetected;
117
271
  }
118
- else if (answers.designSystem === 'mui') {
272
+ else if (answers.designSystem === 'chakra') {
119
273
  config = {
120
- importPath: '@mui/material',
274
+ importPath: '@chakra-ui/react',
121
275
  componentPrefix: '',
122
276
  layoutRules: {
123
- multiColumnWrapper: 'Grid',
124
- columnComponent: 'Grid',
277
+ multiColumnWrapper: 'SimpleGrid',
278
+ columnComponent: 'Box',
125
279
  containerComponent: 'Container',
126
280
  layoutExamples: {
127
- twoColumn: `<Grid container spacing={2}>
128
- <Grid item xs={6}>
281
+ twoColumn: `<SimpleGrid columns={2} spacing={6}>
282
+ <Box>
129
283
  <Card>
130
- <CardContent>
131
- <Typography variant="h5">Left Card</Typography>
132
- <Typography>Left content</Typography>
133
- </CardContent>
284
+ <CardHeader>
285
+ <Heading size="md">Left Card</Heading>
286
+ </CardHeader>
287
+ <CardBody>
288
+ <Text>Left content goes here</Text>
289
+ </CardBody>
134
290
  </Card>
135
- </Grid>
136
- <Grid item xs={6}>
291
+ </Box>
292
+ <Box>
137
293
  <Card>
138
- <CardContent>
139
- <Typography variant="h5">Right Card</Typography>
140
- <Typography>Right content</Typography>
141
- </CardContent>
294
+ <CardHeader>
295
+ <Heading size="md">Right Card</Heading>
296
+ </CardHeader>
297
+ <CardBody>
298
+ <Text>Right content goes here</Text>
299
+ </CardBody>
142
300
  </Card>
143
- </Grid>
144
- </Grid>`
301
+ </Box>
302
+ </SimpleGrid>`
145
303
  }
146
304
  }
147
305
  };
148
306
  }
149
- else if (answers.designSystem === 'chakra') {
150
- config = {
151
- importPath: '@chakra-ui/react',
152
- componentPrefix: '',
153
- layoutRules: {
154
- multiColumnWrapper: 'SimpleGrid',
155
- columnComponent: 'Box',
156
- containerComponent: 'Container'
157
- }
158
- };
159
- }
160
307
  else if (answers.designSystem === 'antd') {
161
308
  config = {
162
309
  importPath: 'antd',
@@ -164,7 +311,21 @@ export async function setupCommand() {
164
311
  layoutRules: {
165
312
  multiColumnWrapper: 'Row',
166
313
  columnComponent: 'Col',
167
- containerComponent: 'div'
314
+ containerComponent: 'div',
315
+ layoutExamples: {
316
+ twoColumn: `<Row gutter={16}>
317
+ <Col span={12}>
318
+ <Card title="Left Card" bordered={false}>
319
+ <p>Left content goes here</p>
320
+ </Card>
321
+ </Col>
322
+ <Col span={12}>
323
+ <Card title="Right Card" bordered={false}>
324
+ <p>Right content goes here</p>
325
+ </Card>
326
+ </Col>
327
+ </Row>`
328
+ }
168
329
  }
169
330
  };
170
331
  }
@@ -175,7 +336,27 @@ export async function setupCommand() {
175
336
  layoutRules: {
176
337
  multiColumnWrapper: 'SimpleGrid',
177
338
  columnComponent: 'div',
178
- containerComponent: 'Container'
339
+ containerComponent: 'Container',
340
+ layoutExamples: {
341
+ twoColumn: `<SimpleGrid cols={2} spacing="md">
342
+ <div>
343
+ <Card shadow="sm" padding="lg" radius="md" withBorder>
344
+ <Text fw={500} size="lg" mb="xs">Left Card</Text>
345
+ <Text size="sm" c="dimmed">
346
+ Left content goes here
347
+ </Text>
348
+ </Card>
349
+ </div>
350
+ <div>
351
+ <Card shadow="sm" padding="lg" radius="md" withBorder>
352
+ <Text fw={500} size="lg" mb="xs">Right Card</Text>
353
+ <Text size="sm" c="dimmed">
354
+ Right content goes here
355
+ </Text>
356
+ </Card>
357
+ </div>
358
+ </SimpleGrid>`
359
+ }
179
360
  }
180
361
  };
181
362
  }
@@ -236,6 +417,38 @@ export async function setupCommand() {
236
417
  console.warn(chalk.yellow(`āš ļø Template file not found: ${file}`));
237
418
  }
238
419
  }
420
+ // Create considerations file
421
+ const considerationsTemplatePath = path.resolve(__dirname, '../../templates/story-ui-considerations.md');
422
+ const considerationsPath = path.join(process.cwd(), 'story-ui-considerations.md');
423
+ if (!fs.existsSync(considerationsPath) && fs.existsSync(considerationsTemplatePath)) {
424
+ let considerationsContent = fs.readFileSync(considerationsTemplatePath, 'utf-8');
425
+ // Customize based on selected design system
426
+ if (config.importPath) {
427
+ considerationsContent = considerationsContent.replace('[Your Component Library]', config.importPath);
428
+ considerationsContent = considerationsContent.replace('[your-import-path]', config.importPath);
429
+ }
430
+ fs.writeFileSync(considerationsPath, considerationsContent);
431
+ console.log(chalk.green('āœ… Created story-ui-considerations.md for AI customization'));
432
+ }
433
+ // Create documentation directory structure
434
+ const docsDir = path.join(process.cwd(), 'story-ui-docs');
435
+ if (!fs.existsSync(docsDir)) {
436
+ console.log(chalk.blue('\nšŸ“š Creating documentation directory structure...'));
437
+ // Create main directory and subdirectories
438
+ const subdirs = ['guidelines', 'tokens', 'components', 'patterns'];
439
+ fs.mkdirSync(docsDir, { recursive: true });
440
+ for (const subdir of subdirs) {
441
+ fs.mkdirSync(path.join(docsDir, subdir), { recursive: true });
442
+ }
443
+ // Copy README template
444
+ const docsReadmeTemplatePath = path.resolve(__dirname, '../../templates/story-ui-docs-README.md');
445
+ const docsReadmePath = path.join(docsDir, 'README.md');
446
+ if (fs.existsSync(docsReadmeTemplatePath)) {
447
+ fs.writeFileSync(docsReadmePath, fs.readFileSync(docsReadmeTemplatePath, 'utf-8'));
448
+ }
449
+ console.log(chalk.green('āœ… Created story-ui-docs/ directory structure'));
450
+ console.log(chalk.gray(' Add your design system documentation to enhance AI story generation'));
451
+ }
239
452
  // Create .env file from template
240
453
  const envSamplePath = path.resolve(__dirname, '../../.env.sample');
241
454
  const envPath = path.join(process.cwd(), '.env');
@@ -246,6 +459,10 @@ export async function setupCommand() {
246
459
  if (answers.apiKey) {
247
460
  envContent = envContent.replace('your-claude-api-key-here', answers.apiKey);
248
461
  }
462
+ // Update the VITE_STORY_UI_PORT with the chosen port
463
+ if (answers.mcpPort) {
464
+ envContent = envContent.replace('VITE_STORY_UI_PORT=4001', `VITE_STORY_UI_PORT=${answers.mcpPort}`);
465
+ }
249
466
  fs.writeFileSync(envPath, envContent);
250
467
  console.log(chalk.green(`āœ… Created .env file${answers.apiKey ? ' with your API key' : ''}`));
251
468
  }
@@ -274,18 +491,60 @@ export async function setupCommand() {
274
491
  console.log(chalk.green(`āœ… Updated .gitignore with Story UI patterns`));
275
492
  }
276
493
  }
494
+ // Clean up default Storybook template components to prevent conflicts
495
+ cleanupDefaultStorybookComponents();
277
496
  // Update package.json with convenience scripts
278
497
  if (packageJson) {
279
498
  const scripts = packageJson.scripts || {};
499
+ // FIRST_EDIT: include chosen port in script
500
+ const portFlag = `--port ${answers.mcpPort || '4001'}`;
280
501
  if (!scripts['story-ui']) {
281
- scripts['story-ui'] = 'story-ui start';
502
+ scripts['story-ui'] = `story-ui start ${portFlag}`;
503
+ }
504
+ else if (!scripts['story-ui'].includes('--port')) {
505
+ scripts['story-ui'] += ` ${portFlag}`;
282
506
  }
283
507
  if (!scripts['storybook-with-ui'] && scripts['storybook']) {
284
508
  scripts['storybook-with-ui'] = 'concurrently "npm run storybook" "npm run story-ui"';
285
509
  }
286
510
  packageJson.scripts = scripts;
511
+ // Check and add required dependencies
512
+ const dependencies = packageJson.dependencies || {};
513
+ const devDependencies = packageJson.devDependencies || {};
514
+ let needsInstall = false;
515
+ // Check for concurrently (needed for storybook-with-ui script)
516
+ if (!dependencies['concurrently'] && !devDependencies['concurrently']) {
517
+ console.log(chalk.blue('šŸ“¦ Adding concurrently dependency...'));
518
+ devDependencies['concurrently'] = '^8.2.0';
519
+ needsInstall = true;
520
+ }
521
+ packageJson.dependencies = dependencies;
522
+ packageJson.devDependencies = devDependencies;
287
523
  fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
288
524
  console.log(chalk.green('āœ… Added convenience scripts to package.json'));
525
+ if (needsInstall) {
526
+ console.log(chalk.blue('\nšŸ“¦ Installing required dependencies...'));
527
+ console.log(chalk.gray('This may take a moment...\n'));
528
+ // Detect package manager
529
+ const npmLock = fs.existsSync(path.join(process.cwd(), 'package-lock.json'));
530
+ const yarnLock = fs.existsSync(path.join(process.cwd(), 'yarn.lock'));
531
+ const pnpmLock = fs.existsSync(path.join(process.cwd(), 'pnpm-lock.yaml'));
532
+ let installCommand = 'npm install';
533
+ if (yarnLock) {
534
+ installCommand = 'yarn install';
535
+ }
536
+ else if (pnpmLock) {
537
+ installCommand = 'pnpm install';
538
+ }
539
+ try {
540
+ execSync(installCommand, { stdio: 'inherit' });
541
+ console.log(chalk.green('āœ… Dependencies installed successfully'));
542
+ }
543
+ catch (error) {
544
+ console.log(chalk.yellow('āš ļø Failed to install dependencies automatically.'));
545
+ console.log(chalk.yellow(` Please run "${installCommand}" manually to complete the setup.`));
546
+ }
547
+ }
289
548
  }
290
549
  console.log(chalk.green.bold('\nšŸŽ‰ Setup complete!\n'));
291
550
  console.log(`šŸ“ Configuration saved to: ${chalk.cyan(configPath)}`);
@@ -17,6 +17,7 @@ import { getSyncedStories, deleteSyncedStory, clearAllSyncedStories, syncChatHis
17
17
  import { setupProductionGitignore } from '../story-generator/productionGitignoreManager.js';
18
18
  import { getInMemoryStoryService } from '../story-generator/inMemoryStoryService.js';
19
19
  import { loadUserConfig } from '../story-generator/configLoader.js';
20
+ import fs from 'fs';
20
21
  const app = express();
21
22
  app.use(cors());
22
23
  app.use(express.json());
@@ -40,6 +41,72 @@ app.delete('/mcp/sync/stories/:id', deleteSyncedStory);
40
41
  app.delete('/mcp/sync/stories', clearAllSyncedStories);
41
42
  app.get('/mcp/sync/chat-history', syncChatHistory);
42
43
  app.get('/mcp/sync/validate/:id', validateChatSession);
44
+ // Proxy routes for frontend compatibility (maps /story-ui/ to /mcp/)
45
+ app.get('/story-ui/stories', getStoriesMetadata);
46
+ app.get('/story-ui/stories/:id', getStoryById);
47
+ app.get('/story-ui/stories/:id/content', getStoryContent);
48
+ app.delete('/story-ui/stories/:id', deleteStory);
49
+ app.delete('/story-ui/stories', clearAllStories);
50
+ app.post('/story-ui/generate', generateStoryFromPrompt);
51
+ app.post('/story-ui/claude', claudeProxy);
52
+ app.get('/story-ui/components', getComponents);
53
+ app.get('/story-ui/props', getProps);
54
+ app.get('/story-ui/memory-stats', getMemoryStats);
55
+ // Legacy delete route for backwards compatibility
56
+ app.post('/story-ui/delete', async (req, res) => {
57
+ try {
58
+ const { chatId, storyId } = req.body;
59
+ const id = chatId || storyId; // Support both parameter names
60
+ if (!id) {
61
+ return res.status(400).json({ error: 'chatId or storyId is required' });
62
+ }
63
+ console.log(`šŸ—‘ļø Attempting to delete story: ${id}`);
64
+ // First try in-memory deletion (production mode)
65
+ const storyService = getInMemoryStoryService(config);
66
+ const inMemoryDeleted = storyService.deleteStory(id);
67
+ if (inMemoryDeleted) {
68
+ console.log(`āœ… Deleted story from memory: ${id}`);
69
+ return res.json({
70
+ success: true,
71
+ message: 'Story deleted successfully from memory'
72
+ });
73
+ }
74
+ // If not found in memory, try file-system deletion (development mode)
75
+ if (gitignoreManager && !gitignoreManager.isProductionMode()) {
76
+ const storiesPath = config.generatedStoriesPath;
77
+ console.log(`šŸ” Searching for file-system story in: ${storiesPath}`);
78
+ if (fs.existsSync(storiesPath)) {
79
+ const files = fs.readdirSync(storiesPath);
80
+ const matchingFile = files.find(file => file.includes(id) || file.replace('.stories.tsx', '') === id);
81
+ if (matchingFile) {
82
+ const filePath = path.join(storiesPath, matchingFile);
83
+ fs.unlinkSync(filePath);
84
+ console.log(`āœ… Deleted story file: ${filePath}`);
85
+ return res.json({
86
+ success: true,
87
+ message: 'Story deleted successfully from file system'
88
+ });
89
+ }
90
+ }
91
+ }
92
+ console.log(`āŒ Story not found: ${id}`);
93
+ return res.status(404).json({
94
+ success: false,
95
+ error: 'Story not found'
96
+ });
97
+ }
98
+ catch (error) {
99
+ console.error('Error in legacy delete route:', error);
100
+ res.status(500).json({ error: 'Failed to delete story' });
101
+ }
102
+ });
103
+ // Synchronized story proxy routes
104
+ app.get('/story-ui/sync/stories', getSyncedStories);
105
+ app.get('/story-ui/sync/stories/:id', getSyncedStoryById);
106
+ app.delete('/story-ui/sync/stories/:id', deleteSyncedStory);
107
+ app.delete('/story-ui/sync/stories', clearAllSyncedStories);
108
+ app.get('/story-ui/sync/chat-history', syncChatHistory);
109
+ app.get('/story-ui/sync/validate/:id', validateChatSession);
43
110
  // Set up production-ready gitignore and directory structure on startup
44
111
  const config = loadUserConfig();
45
112
  const gitignoreManager = setupProductionGitignore(config);