@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.
- package/.env.sample +17 -0
- package/LICENSE +21 -0
- package/README.md +531 -0
- package/dist/cli/index.js +250 -0
- package/dist/cli/setup.js +289 -0
- package/dist/index.js +12 -0
- package/dist/mcp-server/index.js +64 -0
- package/dist/mcp-server/routes/claude.js +30 -0
- package/dist/mcp-server/routes/components.js +26 -0
- package/dist/mcp-server/routes/generateStory.js +289 -0
- package/dist/mcp-server/routes/memoryStories.js +141 -0
- package/dist/mcp-server/routes/storySync.js +147 -0
- package/dist/story-generator/componentDiscovery.js +222 -0
- package/dist/story-generator/configLoader.js +482 -0
- package/dist/story-generator/generateStory.js +19 -0
- package/dist/story-generator/gitignoreManager.js +182 -0
- package/dist/story-generator/inMemoryStoryService.js +128 -0
- package/dist/story-generator/productionGitignoreManager.js +333 -0
- package/dist/story-generator/promptGenerator.js +201 -0
- package/dist/story-generator/storySync.js +201 -0
- package/dist/story-ui.config.js +114 -0
- package/dist/story-ui.config.loader.js +205 -0
- package/package.json +80 -0
- package/templates/README.md +32 -0
- package/templates/StoryUI/StoryUIPanel.stories.tsx +28 -0
- package/templates/StoryUI/StoryUIPanel.tsx +870 -0
- package/templates/StoryUI/index.tsx +2 -0
|
@@ -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
|
+
}
|