@opensaas/stack-cli 0.1.6 → 0.3.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.
Files changed (94) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +215 -0
  3. package/CLAUDE.md +60 -12
  4. package/dist/commands/generate.d.ts.map +1 -1
  5. package/dist/commands/generate.js +10 -1
  6. package/dist/commands/generate.js.map +1 -1
  7. package/dist/commands/mcp.d.ts +6 -0
  8. package/dist/commands/mcp.d.ts.map +1 -0
  9. package/dist/commands/mcp.js +116 -0
  10. package/dist/commands/mcp.js.map +1 -0
  11. package/dist/generator/context.d.ts.map +1 -1
  12. package/dist/generator/context.js +21 -3
  13. package/dist/generator/context.js.map +1 -1
  14. package/dist/generator/index.d.ts +3 -0
  15. package/dist/generator/index.d.ts.map +1 -1
  16. package/dist/generator/index.js +3 -0
  17. package/dist/generator/index.js.map +1 -1
  18. package/dist/generator/lists.d.ts +31 -0
  19. package/dist/generator/lists.d.ts.map +1 -0
  20. package/dist/generator/lists.js +91 -0
  21. package/dist/generator/lists.js.map +1 -0
  22. package/dist/generator/plugin-types.d.ts +10 -0
  23. package/dist/generator/plugin-types.d.ts.map +1 -0
  24. package/dist/generator/plugin-types.js +122 -0
  25. package/dist/generator/plugin-types.js.map +1 -0
  26. package/dist/generator/prisma-config.d.ts +17 -0
  27. package/dist/generator/prisma-config.d.ts.map +1 -0
  28. package/dist/generator/prisma-config.js +40 -0
  29. package/dist/generator/prisma-config.js.map +1 -0
  30. package/dist/generator/prisma.d.ts.map +1 -1
  31. package/dist/generator/prisma.js +1 -2
  32. package/dist/generator/prisma.js.map +1 -1
  33. package/dist/generator/types.d.ts.map +1 -1
  34. package/dist/generator/types.js +53 -1
  35. package/dist/generator/types.js.map +1 -1
  36. package/dist/index.js +3 -0
  37. package/dist/index.js.map +1 -1
  38. package/dist/mcp/lib/documentation-provider.d.ts +43 -0
  39. package/dist/mcp/lib/documentation-provider.d.ts.map +1 -0
  40. package/dist/mcp/lib/documentation-provider.js +163 -0
  41. package/dist/mcp/lib/documentation-provider.js.map +1 -0
  42. package/dist/mcp/lib/features/catalog.d.ts +26 -0
  43. package/dist/mcp/lib/features/catalog.d.ts.map +1 -0
  44. package/dist/mcp/lib/features/catalog.js +291 -0
  45. package/dist/mcp/lib/features/catalog.js.map +1 -0
  46. package/dist/mcp/lib/generators/feature-generator.d.ts +35 -0
  47. package/dist/mcp/lib/generators/feature-generator.d.ts.map +1 -0
  48. package/dist/mcp/lib/generators/feature-generator.js +546 -0
  49. package/dist/mcp/lib/generators/feature-generator.js.map +1 -0
  50. package/dist/mcp/lib/types.d.ts +80 -0
  51. package/dist/mcp/lib/types.d.ts.map +1 -0
  52. package/dist/mcp/lib/types.js +5 -0
  53. package/dist/mcp/lib/types.js.map +1 -0
  54. package/dist/mcp/lib/wizards/wizard-engine.d.ts +71 -0
  55. package/dist/mcp/lib/wizards/wizard-engine.d.ts.map +1 -0
  56. package/dist/mcp/lib/wizards/wizard-engine.js +356 -0
  57. package/dist/mcp/lib/wizards/wizard-engine.js.map +1 -0
  58. package/dist/mcp/server/index.d.ts +8 -0
  59. package/dist/mcp/server/index.d.ts.map +1 -0
  60. package/dist/mcp/server/index.js +202 -0
  61. package/dist/mcp/server/index.js.map +1 -0
  62. package/dist/mcp/server/stack-mcp-server.d.ts +92 -0
  63. package/dist/mcp/server/stack-mcp-server.d.ts.map +1 -0
  64. package/dist/mcp/server/stack-mcp-server.js +265 -0
  65. package/dist/mcp/server/stack-mcp-server.js.map +1 -0
  66. package/package.json +9 -7
  67. package/src/commands/__snapshots__/generate.test.ts.snap +61 -21
  68. package/src/commands/dev.test.ts +0 -1
  69. package/src/commands/generate.test.ts +18 -8
  70. package/src/commands/generate.ts +12 -0
  71. package/src/commands/mcp.ts +135 -0
  72. package/src/generator/__snapshots__/context.test.ts.snap +8 -8
  73. package/src/generator/__snapshots__/prisma.test.ts.snap +8 -16
  74. package/src/generator/__snapshots__/types.test.ts.snap +605 -9
  75. package/src/generator/context.test.ts +13 -8
  76. package/src/generator/context.ts +21 -3
  77. package/src/generator/index.ts +3 -0
  78. package/src/generator/lists.test.ts +335 -0
  79. package/src/generator/lists.ts +102 -0
  80. package/src/generator/plugin-types.ts +147 -0
  81. package/src/generator/prisma-config.ts +46 -0
  82. package/src/generator/prisma.test.ts +0 -10
  83. package/src/generator/prisma.ts +1 -2
  84. package/src/generator/types.test.ts +0 -12
  85. package/src/generator/types.ts +56 -1
  86. package/src/index.ts +4 -0
  87. package/src/mcp/lib/documentation-provider.ts +203 -0
  88. package/src/mcp/lib/features/catalog.ts +301 -0
  89. package/src/mcp/lib/generators/feature-generator.ts +598 -0
  90. package/src/mcp/lib/types.ts +89 -0
  91. package/src/mcp/lib/wizards/wizard-engine.ts +427 -0
  92. package/src/mcp/server/index.ts +240 -0
  93. package/src/mcp/server/stack-mcp-server.ts +301 -0
  94. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,301 @@
1
+ /**
2
+ * OpenSaaS Stack MCP Server - Business logic for MCP tools
3
+ */
4
+
5
+ import { WizardEngine } from '../lib/wizards/wizard-engine.js'
6
+ import { OpenSaasDocumentationProvider } from '../lib/documentation-provider.js'
7
+ import { getAllFeatures, getFeature } from '../lib/features/catalog.js'
8
+
9
+ export class StackMCPServer {
10
+ private wizardEngine: WizardEngine
11
+ private docsProvider: OpenSaasDocumentationProvider
12
+
13
+ constructor() {
14
+ this.wizardEngine = new WizardEngine()
15
+ this.docsProvider = new OpenSaasDocumentationProvider()
16
+ }
17
+
18
+ /**
19
+ * Implement a feature - starts the wizard flow
20
+ */
21
+ async implementFeature({ feature, description }: { feature: string; description?: string }) {
22
+ if (feature === 'custom' && !description) {
23
+ return {
24
+ content: [
25
+ {
26
+ type: 'text' as const,
27
+ text: '❌ For custom features, please provide a description of what you want to build.',
28
+ },
29
+ ],
30
+ }
31
+ }
32
+
33
+ if (feature === 'custom') {
34
+ return {
35
+ content: [
36
+ {
37
+ type: 'text' as const,
38
+ text: `🔧 **Custom Feature**: ${description}
39
+
40
+ I'll help you build this custom feature. Let me search the docs for relevant patterns...
41
+
42
+ ${await this.searchFeatureDocs({ topic: description! })}
43
+
44
+ Based on your description, consider using these OpenSaaS patterns:
45
+ - Define your data model with \`list()\` and field types
46
+ - Add access control with \`access.operation\` and \`access.field\`
47
+ - Use hooks for data transformation and side effects
48
+ - Consider if any plugins would help (auth, storage, RAG)
49
+
50
+ Would you like me to help you design the config for this feature?`,
51
+ },
52
+ ],
53
+ }
54
+ }
55
+
56
+ return this.wizardEngine.startFeature(feature)
57
+ }
58
+
59
+ /**
60
+ * Answer a wizard question
61
+ */
62
+ async answerFeatureQuestion({
63
+ sessionId,
64
+ answer,
65
+ }: {
66
+ sessionId: string
67
+ answer: string | boolean | string[]
68
+ }) {
69
+ return this.wizardEngine.answerQuestion(sessionId, answer)
70
+ }
71
+
72
+ /**
73
+ * Answer a follow-up question
74
+ */
75
+ async answerFollowUpQuestion({ sessionId, answer }: { sessionId: string; answer: string }) {
76
+ return this.wizardEngine.answerFollowUp(sessionId, answer)
77
+ }
78
+
79
+ /**
80
+ * Search documentation
81
+ */
82
+ async searchFeatureDocs({ topic }: { topic: string }) {
83
+ const docs = await this.docsProvider.getTopicDocs(topic)
84
+
85
+ return {
86
+ content: [
87
+ {
88
+ type: 'text' as const,
89
+ text: `# ${docs.topic}
90
+
91
+ ${docs.content}
92
+
93
+ ${docs.codeExamples.length > 0 ? `\n## Code Examples\n\n${docs.codeExamples.join('\n\n')}` : ''}
94
+
95
+ ${docs.relatedTopics.length > 0 ? `\n## Related Topics\n\n${docs.relatedTopics.map((t) => `- ${t}`).join('\n')}` : ''}
96
+
97
+ ---
98
+
99
+ 📚 **Full documentation**: ${docs.url}`,
100
+ },
101
+ ],
102
+ }
103
+ }
104
+
105
+ /**
106
+ * List all available features
107
+ */
108
+ async listFeatures() {
109
+ const features = getAllFeatures()
110
+ const categories = {
111
+ authentication: [] as typeof features,
112
+ content: [] as typeof features,
113
+ storage: [] as typeof features,
114
+ search: [] as typeof features,
115
+ custom: [] as typeof features,
116
+ }
117
+
118
+ features.forEach((feature) => {
119
+ categories[feature.category].push(feature)
120
+ })
121
+
122
+ const formatFeatureList = (featureList: typeof features) =>
123
+ featureList
124
+ .map(
125
+ (f) =>
126
+ `### ${f.name}\n**ID**: \`${f.id}\`\n${f.description}\n\n**Includes**:\n${f.includes.map((i) => `- ${i}`).join('\n')}\n${f.dependsOn && f.dependsOn.length > 0 ? `\n**Requires**: ${f.dependsOn.join(', ')}` : ''}`,
127
+ )
128
+ .join('\n\n')
129
+
130
+ return {
131
+ content: [
132
+ {
133
+ type: 'text' as const,
134
+ text: `# Available OpenSaaS Stack Features
135
+
136
+ Use \`opensaas_implement_feature\` to start implementing any of these:
137
+
138
+ ## 🔐 Authentication
139
+
140
+ ${formatFeatureList(categories.authentication)}
141
+
142
+ ## 📝 Content Management
143
+
144
+ ${formatFeatureList(categories.content)}
145
+
146
+ ## 📦 Storage
147
+
148
+ ${formatFeatureList(categories.storage)}
149
+
150
+ ## 🔍 Search
151
+
152
+ ${formatFeatureList(categories.search)}
153
+
154
+ ---
155
+
156
+ **To implement a feature**, call:
157
+ \`\`\`
158
+ opensaas_implement_feature({ feature: "authentication" })
159
+ \`\`\`
160
+
161
+ **For custom features**, call:
162
+ \`\`\`
163
+ opensaas_implement_feature({
164
+ feature: "custom",
165
+ description: "what you want to build"
166
+ })
167
+ \`\`\``,
168
+ },
169
+ ],
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Suggest complementary features based on described features
175
+ */
176
+ async suggestFeatures({ currentFeatures }: { currentFeatures?: string[] }) {
177
+ const allFeatures = getAllFeatures()
178
+ const implemented = new Set(currentFeatures || [])
179
+
180
+ const suggestions = allFeatures
181
+ .filter((f) => !implemented.has(f.id))
182
+ .map((f) => {
183
+ let reasoning = ''
184
+
185
+ // Add context-aware suggestions
186
+ if (f.id === 'authentication' && !implemented.has('authentication')) {
187
+ reasoning = 'Essential for user management and access control'
188
+ } else if (f.id === 'blog' && implemented.has('authentication')) {
189
+ reasoning = 'You have auth - add content creation capabilities'
190
+ } else if (
191
+ f.id === 'comments' &&
192
+ (implemented.has('blog') || implemented.has('authentication'))
193
+ ) {
194
+ reasoning = 'Enhance engagement with user comments'
195
+ } else if (
196
+ f.id === 'file-upload' &&
197
+ (implemented.has('blog') || implemented.has('authentication'))
198
+ ) {
199
+ reasoning = 'Add media support for richer content'
200
+ } else if (f.id === 'semantic-search' && implemented.has('blog')) {
201
+ reasoning = 'Make your content more discoverable'
202
+ }
203
+
204
+ return { feature: f, reasoning }
205
+ })
206
+ .filter((s) => s.reasoning)
207
+
208
+ if (suggestions.length === 0) {
209
+ return {
210
+ content: [
211
+ {
212
+ type: 'text' as const,
213
+ text: "🎉 You've implemented all our built-in features!\n\nConsider building custom features tailored to your specific needs using `opensaas_implement_feature({ feature: 'custom', description: '...' })`",
214
+ },
215
+ ],
216
+ }
217
+ }
218
+
219
+ return {
220
+ content: [
221
+ {
222
+ type: 'text' as const,
223
+ text: `# Feature Suggestions
224
+
225
+ Based on ${currentFeatures && currentFeatures.length > 0 ? `your current features (${currentFeatures.join(', ')})` : 'common patterns'}, consider adding:
226
+
227
+ ${suggestions
228
+ .map(
229
+ (s, i) => `## ${i + 1}. ${s.feature.name}
230
+
231
+ ${s.reasoning}
232
+
233
+ **What you'll get**:
234
+ ${s.feature.includes.map((inc) => `- ${inc}`).join('\n')}
235
+
236
+ **To implement**: \`opensaas_implement_feature({ feature: "${s.feature.id}" })\`
237
+ `,
238
+ )
239
+ .join('\n---\n\n')}`,
240
+ },
241
+ ],
242
+ }
243
+ }
244
+
245
+ /**
246
+ * Validate a feature implementation
247
+ */
248
+ async validateFeature({ feature }: { feature: string; configPath?: string }) {
249
+ const featureDefinition = getFeature(feature)
250
+
251
+ if (!featureDefinition) {
252
+ return {
253
+ content: [
254
+ {
255
+ type: 'text' as const,
256
+ text: `❌ Unknown feature: ${feature}\n\nUse \`opensaas_list_features\` to see available features.`,
257
+ },
258
+ ],
259
+ }
260
+ }
261
+
262
+ // TODO: Implement actual validation by reading the config file
263
+ // For now, return validation checklist
264
+
265
+ return {
266
+ content: [
267
+ {
268
+ type: 'text' as const,
269
+ text: `# ${featureDefinition.name} Validation
270
+
271
+ Checking your implementation...
272
+
273
+ ## Checklist
274
+
275
+ ${featureDefinition.includes.map((item) => `- [ ] ${item}`).join('\n')}
276
+
277
+ ${featureDefinition.dependsOn && featureDefinition.dependsOn.length > 0 ? `\n## Dependencies\n\nEnsure these features are implemented:\n${featureDefinition.dependsOn.map((dep) => `- [ ] ${dep}`).join('\n')}\n` : ''}
278
+
279
+ ---
280
+
281
+ **Manual validation steps**:
282
+
283
+ 1. Check that all required lists are defined in your config
284
+ 2. Verify access control is properly configured
285
+ 3. Test the feature in your development environment
286
+ 4. Run \`pnpm generate\` and \`pnpm db:push\` successfully
287
+
288
+ **Note**: Full automatic validation coming soon! For now, use this checklist to verify your implementation.`,
289
+ },
290
+ ],
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Cleanup - clear wizard sessions and caches
296
+ */
297
+ cleanup() {
298
+ this.wizardEngine.clearCompletedSessions()
299
+ this.docsProvider.clearExpiredCache()
300
+ }
301
+ }