@tpitre/story-ui 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,250 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { spawn } from 'child_process';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+ import { setupProductionGitignore } from '../story-generator/productionGitignoreManager.js';
8
+ import { createStoryUIConfig } from '../story-ui.config.js';
9
+ import { setupCommand } from './setup.js';
10
+ const __filename = fileURLToPath(import.meta.url);
11
+ const __dirname = path.dirname(__filename);
12
+ const program = new Command();
13
+ program
14
+ .name('story-ui')
15
+ .description('AI-powered Storybook story generator for React component libraries')
16
+ .version('1.0.0');
17
+ program
18
+ .command('init')
19
+ .description('Initialize Story UI configuration with interactive setup')
20
+ .action(async () => {
21
+ await setupCommand();
22
+ });
23
+ program
24
+ .command('start')
25
+ .description('Start the Story UI server')
26
+ .option('-p, --port <port>', 'Port to run the server on', '4001')
27
+ .option('-c, --config <config>', 'Path to configuration file')
28
+ .action((options) => {
29
+ console.log('🚀 Starting Story UI server...');
30
+ const serverPath = path.resolve(__dirname, '../dist/mcp-server/index.js');
31
+ const env = { ...process.env };
32
+ if (options.port) {
33
+ env.PORT = options.port;
34
+ }
35
+ if (options.config) {
36
+ env.STORY_UI_CONFIG_PATH = options.config;
37
+ }
38
+ const server = spawn('node', [serverPath], {
39
+ stdio: 'inherit',
40
+ env
41
+ });
42
+ server.on('close', (code) => {
43
+ console.log(`Server exited with code ${code}`);
44
+ });
45
+ process.on('SIGINT', () => {
46
+ server.kill('SIGINT');
47
+ });
48
+ });
49
+ program
50
+ .command('config')
51
+ .description('Configuration utilities')
52
+ .option('--generate', 'Generate a sample configuration file')
53
+ .option('--type <type>', 'Configuration file type (json|js)', 'js')
54
+ .action((options) => {
55
+ if (options.generate) {
56
+ const filename = `story-ui.config.${options.type}`;
57
+ generateSampleConfig(filename, options.type);
58
+ console.log(`✅ Sample configuration generated: ${filename}`);
59
+ }
60
+ });
61
+ program
62
+ .command('setup-gitignore')
63
+ .description('Set up .gitignore for Story UI generated stories')
64
+ .option('-c, --config <path>', 'Path to Story UI config file', 'story-ui.config.js')
65
+ .action(async (options) => {
66
+ console.log('🔧 Setting up .gitignore for Story UI...');
67
+ try {
68
+ const configPath = path.resolve(process.cwd(), options.config);
69
+ const configModule = await import(configPath);
70
+ const userConfig = configModule.default;
71
+ const fullConfig = createStoryUIConfig(userConfig);
72
+ const manager = setupProductionGitignore(fullConfig);
73
+ if (manager.isProductionMode()) {
74
+ console.log('🌐 Production environment detected');
75
+ console.log('✅ Gitignore validation completed');
76
+ }
77
+ else {
78
+ console.log('🔧 Development environment - gitignore updated');
79
+ }
80
+ console.log('✅ Gitignore setup completed successfully!');
81
+ }
82
+ catch (error) {
83
+ console.error('❌ Failed to set up gitignore:', error);
84
+ console.log('💡 Make sure you have a valid story-ui.config.js file');
85
+ process.exit(1);
86
+ }
87
+ });
88
+ async function autoDetectAndCreateConfig() {
89
+ const cwd = process.cwd();
90
+ const config = {
91
+ generatedStoriesPath: './src/stories/generated',
92
+ storyPrefix: 'Generated/',
93
+ defaultAuthor: 'Story UI AI',
94
+ componentPrefix: '',
95
+ layoutRules: {
96
+ multiColumnWrapper: 'div',
97
+ columnComponent: 'div',
98
+ layoutExamples: {
99
+ twoColumn: `<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem'}}>
100
+ <div>Column 1 content</div>
101
+ <div>Column 2 content</div>
102
+ </div>`
103
+ },
104
+ prohibitedElements: []
105
+ }
106
+ };
107
+ // Try to detect package.json
108
+ const packageJsonPath = path.join(cwd, 'package.json');
109
+ if (fs.existsSync(packageJsonPath)) {
110
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
111
+ config.importPath = packageJson.name || 'your-component-library';
112
+ }
113
+ // Try to detect components directory
114
+ const possibleComponentPaths = [
115
+ './src/components',
116
+ './lib/components',
117
+ './components',
118
+ './src'
119
+ ];
120
+ for (const possiblePath of possibleComponentPaths) {
121
+ if (fs.existsSync(path.join(cwd, possiblePath))) {
122
+ config.componentsPath = possiblePath;
123
+ break;
124
+ }
125
+ }
126
+ writeConfig(config, 'js');
127
+ }
128
+ async function createTemplateConfig(template) {
129
+ let config;
130
+ switch (template) {
131
+ case 'material-ui':
132
+ config = {
133
+ importPath: '@mui/material',
134
+ componentPrefix: '',
135
+ layoutRules: {
136
+ multiColumnWrapper: 'Grid',
137
+ columnComponent: 'Grid',
138
+ layoutExamples: {
139
+ twoColumn: `<Grid container spacing={2}>
140
+ <Grid item xs={6}>
141
+ <Card>Left content</Card>
142
+ </Grid>
143
+ <Grid item xs={6}>
144
+ <Card>Right content</Card>
145
+ </Grid>
146
+ </Grid>`
147
+ }
148
+ }
149
+ };
150
+ break;
151
+ case 'chakra-ui':
152
+ config = {
153
+ importPath: '@chakra-ui/react',
154
+ componentPrefix: '',
155
+ layoutRules: {
156
+ multiColumnWrapper: 'SimpleGrid',
157
+ columnComponent: 'Box',
158
+ layoutExamples: {
159
+ twoColumn: `<SimpleGrid columns={2} spacing={4}>
160
+ <Box><Card>Left content</Card></Box>
161
+ <Box><Card>Right content</Card></Box>
162
+ </SimpleGrid>`
163
+ }
164
+ }
165
+ };
166
+ break;
167
+ case 'ant-design':
168
+ config = {
169
+ importPath: 'antd',
170
+ componentPrefix: '',
171
+ layoutRules: {
172
+ multiColumnWrapper: 'Row',
173
+ columnComponent: 'Col',
174
+ layoutExamples: {
175
+ twoColumn: `<Row gutter={16}>
176
+ <Col span={12}><Card>Left content</Card></Col>
177
+ <Col span={12}><Card>Right content</Card></Col>
178
+ </Row>`
179
+ }
180
+ }
181
+ };
182
+ break;
183
+ default:
184
+ throw new Error(`Unknown template: ${template}`);
185
+ }
186
+ // Add common defaults
187
+ config = {
188
+ generatedStoriesPath: './src/stories/generated',
189
+ componentsPath: './src/components',
190
+ storyPrefix: 'Generated/',
191
+ defaultAuthor: 'Story UI AI',
192
+ ...config
193
+ };
194
+ writeConfig(config, 'js');
195
+ }
196
+ async function createBasicConfig() {
197
+ const config = {
198
+ generatedStoriesPath: './src/stories/generated',
199
+ componentsPath: './src/components',
200
+ storyPrefix: 'Generated/',
201
+ defaultAuthor: 'Story UI AI',
202
+ importPath: 'your-component-library',
203
+ componentPrefix: '',
204
+ layoutRules: {
205
+ multiColumnWrapper: 'div',
206
+ columnComponent: 'div',
207
+ layoutExamples: {
208
+ twoColumn: `<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem'}}>
209
+ <div>Column 1 content</div>
210
+ <div>Column 2 content</div>
211
+ </div>`
212
+ },
213
+ prohibitedElements: []
214
+ }
215
+ };
216
+ writeConfig(config, 'js');
217
+ }
218
+ function generateSampleConfig(filename, type) {
219
+ const config = {
220
+ generatedStoriesPath: './src/stories/generated',
221
+ componentsPath: './src/components',
222
+ storyPrefix: 'Generated/',
223
+ defaultAuthor: 'Story UI AI',
224
+ importPath: 'your-component-library',
225
+ componentPrefix: 'UI',
226
+ layoutRules: {
227
+ multiColumnWrapper: 'UIGrid',
228
+ columnComponent: 'UIColumn',
229
+ layoutExamples: {
230
+ twoColumn: `<UIGrid columns={2}>
231
+ <UIColumn><UICard>Left content</UICard></UIColumn>
232
+ <UIColumn><UICard>Right content</UICard></UIColumn>
233
+ </UIGrid>`
234
+ },
235
+ prohibitedElements: []
236
+ }
237
+ };
238
+ writeConfig(config, type, filename);
239
+ }
240
+ function writeConfig(config, type, filename) {
241
+ const outputFile = filename || `story-ui.config.${type}`;
242
+ if (type === 'json') {
243
+ fs.writeFileSync(outputFile, JSON.stringify(config, null, 2));
244
+ }
245
+ else {
246
+ const jsContent = `export default ${JSON.stringify(config, null, 2)};`;
247
+ fs.writeFileSync(outputFile, jsContent);
248
+ }
249
+ }
250
+ program.parse(process.argv);
@@ -0,0 +1,289 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import inquirer from 'inquirer';
4
+ import chalk from 'chalk';
5
+ import { autoDetectDesignSystem } from '../story-generator/configLoader.js';
6
+ import { fileURLToPath } from 'url';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ export async function setupCommand() {
10
+ console.log(chalk.blue.bold('\n🎨 Story UI Setup\n'));
11
+ console.log('This will help you configure Story UI for your design system.\n');
12
+ // Check if we're in a valid project
13
+ const packageJsonPath = path.join(process.cwd(), 'package.json');
14
+ if (!fs.existsSync(packageJsonPath)) {
15
+ console.error(chalk.red('❌ No package.json found. Please run this command in your project root.'));
16
+ process.exit(1);
17
+ }
18
+ // Check if Storybook is installed
19
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
20
+ const hasStorybook = packageJson.devDependencies?.['@storybook/react'] ||
21
+ packageJson.dependencies?.['@storybook/react'] ||
22
+ fs.existsSync(path.join(process.cwd(), '.storybook'));
23
+ if (!hasStorybook) {
24
+ console.warn(chalk.yellow('⚠️ Storybook not detected. Story UI works best with Storybook installed.'));
25
+ console.log('Install Storybook first: npx storybook@latest init\n');
26
+ }
27
+ // Auto-detect design system
28
+ const autoDetected = autoDetectDesignSystem();
29
+ if (autoDetected) {
30
+ console.log(chalk.green(`✅ Auto-detected design system:`));
31
+ console.log(` 📦 Import path: ${autoDetected.importPath}`);
32
+ if (autoDetected.componentPrefix) {
33
+ console.log(` 🏷️ Component prefix: ${autoDetected.componentPrefix}`);
34
+ }
35
+ if (autoDetected.componentsPath) {
36
+ console.log(` 📁 Components path: ${autoDetected.componentsPath}`);
37
+ }
38
+ }
39
+ const answers = await inquirer.prompt([
40
+ {
41
+ type: 'list',
42
+ name: 'designSystem',
43
+ message: 'Which design system are you using?',
44
+ choices: [
45
+ { name: '🤖 Auto-detect from package.json', value: 'auto' },
46
+ { name: '🎨 Material-UI (@mui/material)', value: 'mui' },
47
+ { name: '⚡ Chakra UI (@chakra-ui/react)', value: 'chakra' },
48
+ { name: '🐜 Ant Design (antd)', value: 'antd' },
49
+ { name: '🎯 Mantine (@mantine/core)', value: 'mantine' },
50
+ { name: '🔧 Custom/Other', value: 'custom' }
51
+ ],
52
+ default: autoDetected ? 'auto' : 'custom'
53
+ },
54
+ {
55
+ type: 'input',
56
+ name: 'importPath',
57
+ message: 'What is the import path for your components?',
58
+ when: (answers) => answers.designSystem === 'custom',
59
+ validate: (input) => input.trim() ? true : 'Import path is required'
60
+ },
61
+ {
62
+ type: 'input',
63
+ name: 'componentPrefix',
64
+ message: 'Do your components have a prefix? (e.g., "AL" for ALButton)',
65
+ when: (answers) => answers.designSystem === 'custom',
66
+ default: ''
67
+ },
68
+ {
69
+ type: 'input',
70
+ name: 'generatedStoriesPath',
71
+ message: 'Where should generated stories be saved?',
72
+ default: './src/stories/generated/',
73
+ validate: (input) => input.trim() ? true : 'Path is required'
74
+ },
75
+ {
76
+ type: 'input',
77
+ name: 'componentsPath',
78
+ message: 'Where are your component files located?',
79
+ default: './src/components',
80
+ when: (answers) => answers.designSystem === 'custom'
81
+ },
82
+ {
83
+ type: 'confirm',
84
+ name: 'hasApiKey',
85
+ message: 'Do you have a Claude API key? (You can add it later)',
86
+ default: false
87
+ },
88
+ {
89
+ type: 'password',
90
+ name: 'apiKey',
91
+ message: 'Enter your Claude API key:',
92
+ when: (answers) => answers.hasApiKey,
93
+ validate: (input) => input.trim() ? true : 'API key is required'
94
+ }
95
+ ]);
96
+ // Generate configuration
97
+ let config = {};
98
+ if (answers.designSystem === 'auto' && autoDetected) {
99
+ config = autoDetected;
100
+ }
101
+ else if (answers.designSystem === 'mui') {
102
+ config = {
103
+ importPath: '@mui/material',
104
+ componentPrefix: '',
105
+ layoutRules: {
106
+ multiColumnWrapper: 'Grid',
107
+ columnComponent: 'Grid',
108
+ containerComponent: 'Container',
109
+ layoutExamples: {
110
+ twoColumn: `<Grid container spacing={2}>
111
+ <Grid item xs={6}>
112
+ <Card>
113
+ <CardContent>
114
+ <Typography variant="h5">Left Card</Typography>
115
+ <Typography>Left content</Typography>
116
+ </CardContent>
117
+ </Card>
118
+ </Grid>
119
+ <Grid item xs={6}>
120
+ <Card>
121
+ <CardContent>
122
+ <Typography variant="h5">Right Card</Typography>
123
+ <Typography>Right content</Typography>
124
+ </CardContent>
125
+ </Card>
126
+ </Grid>
127
+ </Grid>`
128
+ }
129
+ }
130
+ };
131
+ }
132
+ else if (answers.designSystem === 'chakra') {
133
+ config = {
134
+ importPath: '@chakra-ui/react',
135
+ componentPrefix: '',
136
+ layoutRules: {
137
+ multiColumnWrapper: 'SimpleGrid',
138
+ columnComponent: 'Box',
139
+ containerComponent: 'Container'
140
+ }
141
+ };
142
+ }
143
+ else if (answers.designSystem === 'antd') {
144
+ config = {
145
+ importPath: 'antd',
146
+ componentPrefix: '',
147
+ layoutRules: {
148
+ multiColumnWrapper: 'Row',
149
+ columnComponent: 'Col',
150
+ containerComponent: 'div'
151
+ }
152
+ };
153
+ }
154
+ else if (answers.designSystem === 'mantine') {
155
+ config = {
156
+ importPath: '@mantine/core',
157
+ componentPrefix: '',
158
+ layoutRules: {
159
+ multiColumnWrapper: 'SimpleGrid',
160
+ columnComponent: 'div',
161
+ containerComponent: 'Container'
162
+ }
163
+ };
164
+ }
165
+ else {
166
+ // Custom configuration
167
+ config = {
168
+ importPath: answers.importPath,
169
+ componentPrefix: answers.componentPrefix || '',
170
+ componentsPath: answers.componentsPath ? path.resolve(answers.componentsPath) : undefined,
171
+ layoutRules: {
172
+ multiColumnWrapper: 'div',
173
+ columnComponent: 'div',
174
+ containerComponent: 'div',
175
+ layoutExamples: {
176
+ twoColumn: `<div style={{display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem'}}>
177
+ <div>Column 1 content</div>
178
+ <div>Column 2 content</div>
179
+ </div>`
180
+ }
181
+ }
182
+ };
183
+ }
184
+ // Add common configuration
185
+ config.generatedStoriesPath = path.resolve(answers.generatedStoriesPath || './src/stories/generated/');
186
+ config.storyPrefix = 'Generated/';
187
+ config.defaultAuthor = 'Story UI AI';
188
+ // Create configuration file
189
+ const configContent = `module.exports = ${JSON.stringify(config, null, 2)};`;
190
+ const configPath = path.join(process.cwd(), 'story-ui.config.js');
191
+ fs.writeFileSync(configPath, configContent);
192
+ // Create generated stories directory
193
+ const storiesDir = path.dirname(config.generatedStoriesPath);
194
+ if (!fs.existsSync(storiesDir)) {
195
+ fs.mkdirSync(storiesDir, { recursive: true });
196
+ }
197
+ // Copy StoryUI component to the project
198
+ const storyUITargetDir = path.join(storiesDir, 'StoryUI');
199
+ if (!fs.existsSync(storyUITargetDir)) {
200
+ fs.mkdirSync(storyUITargetDir, { recursive: true });
201
+ }
202
+ // Copy component files
203
+ const templatesDir = path.resolve(__dirname, '../../templates/StoryUI');
204
+ const componentFiles = ['StoryUIPanel.tsx', 'StoryUIPanel.stories.tsx'];
205
+ console.log(chalk.blue('\n📦 Installing Story UI component...'));
206
+ for (const file of componentFiles) {
207
+ const sourcePath = path.join(templatesDir, file);
208
+ const targetPath = path.join(storyUITargetDir, file);
209
+ if (fs.existsSync(sourcePath)) {
210
+ fs.copyFileSync(sourcePath, targetPath);
211
+ console.log(chalk.green(`✅ Copied ${file}`));
212
+ }
213
+ else {
214
+ console.warn(chalk.yellow(`⚠️ Template file not found: ${file}`));
215
+ }
216
+ }
217
+ // Create .env file from template
218
+ const envSamplePath = path.resolve(__dirname, '../../.env.sample');
219
+ const envPath = path.join(process.cwd(), '.env');
220
+ if (!fs.existsSync(envPath)) {
221
+ if (fs.existsSync(envSamplePath)) {
222
+ let envContent = fs.readFileSync(envSamplePath, 'utf-8');
223
+ // If user provided API key, update the template
224
+ if (answers.apiKey) {
225
+ envContent = envContent.replace('your-claude-api-key-here', answers.apiKey);
226
+ }
227
+ fs.writeFileSync(envPath, envContent);
228
+ console.log(chalk.green(`✅ Created .env file${answers.apiKey ? ' with your API key' : ''}`));
229
+ }
230
+ }
231
+ else {
232
+ console.log(chalk.yellow('⚠️ .env file already exists, skipping'));
233
+ }
234
+ // Add .env to .gitignore if not already there
235
+ const gitignorePath = path.join(process.cwd(), '.gitignore');
236
+ if (fs.existsSync(gitignorePath)) {
237
+ const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
238
+ const patterns = [
239
+ '.env',
240
+ path.relative(process.cwd(), config.generatedStoriesPath),
241
+ `${path.relative(process.cwd(), storiesDir)}/StoryUI/`
242
+ ];
243
+ let gitignoreUpdated = false;
244
+ for (const pattern of patterns) {
245
+ if (!gitignoreContent.includes(pattern)) {
246
+ fs.appendFileSync(gitignorePath, `\n${pattern}`);
247
+ gitignoreUpdated = true;
248
+ }
249
+ }
250
+ if (gitignoreUpdated) {
251
+ fs.appendFileSync(gitignorePath, '\n');
252
+ console.log(chalk.green(`✅ Updated .gitignore with Story UI patterns`));
253
+ }
254
+ }
255
+ // Update package.json with convenience scripts
256
+ if (packageJson) {
257
+ const scripts = packageJson.scripts || {};
258
+ if (!scripts['story-ui']) {
259
+ scripts['story-ui'] = 'story-ui start';
260
+ }
261
+ if (!scripts['storybook-with-ui'] && scripts['storybook']) {
262
+ scripts['storybook-with-ui'] = 'concurrently "npm run storybook" "npm run story-ui"';
263
+ }
264
+ packageJson.scripts = scripts;
265
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
266
+ console.log(chalk.green('✅ Added convenience scripts to package.json'));
267
+ }
268
+ console.log(chalk.green.bold('\n🎉 Setup complete!\n'));
269
+ console.log(`📁 Configuration saved to: ${chalk.cyan(configPath)}`);
270
+ console.log(`📁 Generated stories will be saved to: ${chalk.cyan(config.generatedStoriesPath)}`);
271
+ console.log(`📁 Story UI component installed to: ${chalk.cyan(path.relative(process.cwd(), storyUITargetDir))}`);
272
+ if (config.importPath) {
273
+ console.log(`📦 Import path: ${chalk.cyan(config.importPath)}`);
274
+ }
275
+ if (!answers.apiKey) {
276
+ console.log(chalk.yellow('\n⚠️ Don\'t forget to add your Claude API key to .env file!'));
277
+ console.log(' Get your key from: https://console.anthropic.com/');
278
+ }
279
+ console.log('\n🚀 Next steps:');
280
+ console.log('1. ' + (answers.apiKey ? 'Start' : 'Add your Claude API key to .env, then start') + ' Story UI: npm run story-ui');
281
+ console.log('2. Start Storybook: npm run storybook');
282
+ console.log('3. Navigate to "Story UI > Story Generator" in your Storybook sidebar');
283
+ console.log('4. Start generating UI with natural language prompts!');
284
+ console.log('\n💡 Tips:');
285
+ console.log('- Run both together: npm run storybook-with-ui');
286
+ console.log('- Generated stories are automatically excluded from git');
287
+ console.log('- The Story UI panel is in your stories under "Story UI/Story Generator"');
288
+ console.log('- You can modify story-ui.config.js to customize the configuration');
289
+ }
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ // Main exports for Story UI package
2
+ export * from './story-ui.config.js';
3
+ export * from './story-ui.config.loader.js';
4
+ export * from './story-generator/componentDiscovery.js';
5
+ export * from './story-generator/promptGenerator.js';
6
+ export { createStoryUIConfig, DEFAULT_CONFIG, GENERIC_CONFIG_TEMPLATE } from './story-ui.config.js';
7
+ export { configLoader, loadStoryUIConfig } from './story-ui.config.loader.js';
8
+ export { discoverComponents, discoverComponentsFromDirectory, discoverComponentsFromCustomElements, discoverComponentsFromPackage } from './story-generator/componentDiscovery.js';
9
+ export { generatePrompt, buildClaudePrompt } from './story-generator/promptGenerator.js';
10
+ export { ProductionGitignoreManager, setupProductionGitignore } from './story-generator/productionGitignoreManager.js';
11
+ export { InMemoryStoryService, getInMemoryStoryService } from './story-generator/inMemoryStoryService.js';
12
+ export { StorySyncService, getStorySyncService } from './story-generator/storySync.js';
@@ -0,0 +1,64 @@
1
+ import dotenv from 'dotenv';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ // Get __dirname equivalent for ES modules
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = path.dirname(__filename);
7
+ // Load .env from current working directory (where the MCP server is run from)
8
+ // This allows each environment to have its own API key configuration
9
+ dotenv.config({ path: path.resolve(process.cwd(), '.env') });
10
+ import express from 'express';
11
+ import cors from 'cors';
12
+ import { getComponents, getProps } from './routes/components.js';
13
+ import { claudeProxy } from './routes/claude.js';
14
+ import { generateStoryFromPrompt } from './routes/generateStory.js';
15
+ import { getStoriesMetadata, getStoryById, getStoryContent, deleteStory, clearAllStories, getMemoryStats } from './routes/memoryStories.js';
16
+ import { getSyncedStories, deleteSyncedStory, clearAllSyncedStories, syncChatHistory, validateChatSession, getSyncedStoryById } from './routes/storySync.js';
17
+ import { setupProductionGitignore } from '../story-generator/productionGitignoreManager.js';
18
+ import { getInMemoryStoryService } from '../story-generator/inMemoryStoryService.js';
19
+ import { loadUserConfig } from '../story-generator/configLoader.js';
20
+ const app = express();
21
+ app.use(cors());
22
+ app.use(express.json());
23
+ // Component discovery routes
24
+ app.get('/mcp/components', getComponents);
25
+ app.get('/mcp/props', getProps);
26
+ // AI generation routes
27
+ app.post('/mcp/claude', claudeProxy);
28
+ app.post('/mcp/generate-story', generateStoryFromPrompt);
29
+ // In-memory story management routes (production)
30
+ app.get('/mcp/stories', getStoriesMetadata);
31
+ app.get('/mcp/stories/:id', getStoryById);
32
+ app.get('/mcp/stories/:id/content', getStoryContent);
33
+ app.delete('/mcp/stories/:id', deleteStory);
34
+ app.delete('/mcp/stories', clearAllStories);
35
+ app.get('/mcp/memory-stats', getMemoryStats);
36
+ // Synchronized story management routes (works in both dev and production)
37
+ app.get('/mcp/sync/stories', getSyncedStories);
38
+ app.get('/mcp/sync/stories/:id', getSyncedStoryById);
39
+ app.delete('/mcp/sync/stories/:id', deleteSyncedStory);
40
+ app.delete('/mcp/sync/stories', clearAllSyncedStories);
41
+ app.get('/mcp/sync/chat-history', syncChatHistory);
42
+ app.get('/mcp/sync/validate/:id', validateChatSession);
43
+ const PORT = process.env.PORT || 4001;
44
+ // Set up production-ready gitignore and directory structure on startup
45
+ const config = loadUserConfig();
46
+ const gitignoreManager = setupProductionGitignore(config);
47
+ const storyService = getInMemoryStoryService(config);
48
+ // Add memory stats endpoint for production monitoring
49
+ app.get('/mcp/stats', (req, res) => {
50
+ const memoryStats = storyService.getMemoryStats();
51
+ const isProduction = gitignoreManager.isProductionMode();
52
+ res.json({
53
+ environment: isProduction ? 'production' : 'development',
54
+ storyGeneration: isProduction ? 'in-memory' : 'file-system',
55
+ memoryStats,
56
+ uptime: process.uptime(),
57
+ nodeVersion: process.version
58
+ });
59
+ });
60
+ app.listen(PORT, () => {
61
+ console.log(`MCP server running on port ${PORT}`);
62
+ console.log(`Environment: ${gitignoreManager.isProductionMode() ? 'Production' : 'Development'}`);
63
+ console.log(`Story generation: ${gitignoreManager.isProductionMode() ? 'In-memory' : 'File-system'}`);
64
+ });
@@ -0,0 +1,30 @@
1
+ import fetch from 'node-fetch';
2
+ export async function claudeProxy(req, res) {
3
+ const { prompt } = req.body;
4
+ if (!prompt)
5
+ return res.status(400).json({ error: 'Missing prompt' });
6
+ const apiKey = process.env.CLAUDE_API_KEY;
7
+ if (!apiKey)
8
+ return res.status(500).json({ error: 'Claude API key not set' });
9
+ try {
10
+ // Example Claude API call (adjust endpoint/model as needed)
11
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
12
+ method: 'POST',
13
+ headers: {
14
+ 'x-api-key': apiKey,
15
+ 'content-type': 'application/json',
16
+ 'anthropic-version': '2023-06-01',
17
+ },
18
+ body: JSON.stringify({
19
+ model: process.env.CLAUDE_MODEL || 'claude-3-opus-20240229',
20
+ max_tokens: 1024,
21
+ messages: [{ role: 'user', content: prompt }],
22
+ }),
23
+ });
24
+ const data = await response.json();
25
+ res.json(data);
26
+ }
27
+ catch (err) {
28
+ res.status(500).json({ error: 'Claude API error', details: err });
29
+ }
30
+ }
@@ -0,0 +1,26 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { STORY_UI_CONFIG } from '../../story-ui.config.js';
4
+ const metadataPath = STORY_UI_CONFIG.componentsMetadataPath ?
5
+ path.resolve(process.cwd(), STORY_UI_CONFIG.componentsMetadataPath) :
6
+ null;
7
+ export function getComponents(req, res) {
8
+ if (!metadataPath || !fs.existsSync(metadataPath)) {
9
+ return res.json([]);
10
+ }
11
+ const data = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
12
+ const components = data?.tags?.map((tag) => ({
13
+ name: tag.name,
14
+ description: tag.description,
15
+ })) || [];
16
+ res.json(components);
17
+ }
18
+ export function getProps(req, res) {
19
+ if (!metadataPath || !fs.existsSync(metadataPath)) {
20
+ return res.json([]);
21
+ }
22
+ const { component } = req.query;
23
+ const data = JSON.parse(fs.readFileSync(metadataPath, 'utf-8'));
24
+ const tag = data?.tags?.find((t) => t.name === component);
25
+ res.json(tag?.attributes || []);
26
+ }