@opensaas/stack-cli 0.5.0 → 0.6.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.
- package/README.md +76 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +94 -268
- package/dist/commands/migrate.js.map +1 -1
- package/package.json +7 -2
- package/.turbo/turbo-build.log +0 -4
- package/CHANGELOG.md +0 -462
- package/CLAUDE.md +0 -298
- package/src/commands/__snapshots__/generate.test.ts.snap +0 -413
- package/src/commands/dev.test.ts +0 -215
- package/src/commands/dev.ts +0 -48
- package/src/commands/generate.test.ts +0 -282
- package/src/commands/generate.ts +0 -182
- package/src/commands/init.ts +0 -34
- package/src/commands/mcp.ts +0 -135
- package/src/commands/migrate.ts +0 -534
- package/src/generator/__snapshots__/context.test.ts.snap +0 -361
- package/src/generator/__snapshots__/prisma.test.ts.snap +0 -174
- package/src/generator/__snapshots__/types.test.ts.snap +0 -1702
- package/src/generator/context.test.ts +0 -139
- package/src/generator/context.ts +0 -227
- package/src/generator/index.ts +0 -7
- package/src/generator/lists.test.ts +0 -335
- package/src/generator/lists.ts +0 -140
- package/src/generator/plugin-types.ts +0 -147
- package/src/generator/prisma-config.ts +0 -46
- package/src/generator/prisma-extensions.ts +0 -159
- package/src/generator/prisma.test.ts +0 -211
- package/src/generator/prisma.ts +0 -161
- package/src/generator/types.test.ts +0 -268
- package/src/generator/types.ts +0 -537
- package/src/index.ts +0 -46
- package/src/mcp/lib/documentation-provider.ts +0 -710
- package/src/mcp/lib/features/catalog.ts +0 -301
- package/src/mcp/lib/generators/feature-generator.ts +0 -598
- package/src/mcp/lib/types.ts +0 -89
- package/src/mcp/lib/wizards/migration-wizard.ts +0 -584
- package/src/mcp/lib/wizards/wizard-engine.ts +0 -427
- package/src/mcp/server/index.ts +0 -361
- package/src/mcp/server/stack-mcp-server.ts +0 -544
- package/src/migration/generators/migration-generator.ts +0 -675
- package/src/migration/introspectors/index.ts +0 -12
- package/src/migration/introspectors/keystone-introspector.ts +0 -296
- package/src/migration/introspectors/nextjs-introspector.ts +0 -209
- package/src/migration/introspectors/prisma-introspector.ts +0 -233
- package/src/migration/types.ts +0 -92
- package/tests/introspectors/keystone-introspector.test.ts +0 -255
- package/tests/introspectors/nextjs-introspector.test.ts +0 -302
- package/tests/introspectors/prisma-introspector.test.ts +0 -268
- package/tests/migration-generator.test.ts +0 -592
- package/tests/migration-wizard.test.ts +0 -442
- package/tsconfig.json +0 -13
- package/tsconfig.tsbuildinfo +0 -1
- package/vitest.config.ts +0 -26
|
@@ -1,544 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OpenSaaS Stack MCP Server - Business logic for MCP tools
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { WizardEngine } from '../lib/wizards/wizard-engine.js'
|
|
6
|
-
import { MigrationWizard } from '../lib/wizards/migration-wizard.js'
|
|
7
|
-
import { OpenSaasDocumentationProvider } from '../lib/documentation-provider.js'
|
|
8
|
-
import { getAllFeatures, getFeature } from '../lib/features/catalog.js'
|
|
9
|
-
import { PrismaIntrospector } from '../../migration/introspectors/prisma-introspector.js'
|
|
10
|
-
import { KeystoneIntrospector } from '../../migration/introspectors/keystone-introspector.js'
|
|
11
|
-
import type { ProjectType } from '../../migration/types.js'
|
|
12
|
-
|
|
13
|
-
export class StackMCPServer {
|
|
14
|
-
private wizardEngine: WizardEngine
|
|
15
|
-
private migrationWizard: MigrationWizard
|
|
16
|
-
private docsProvider: OpenSaasDocumentationProvider
|
|
17
|
-
private prismaIntrospector: PrismaIntrospector
|
|
18
|
-
private keystoneIntrospector: KeystoneIntrospector
|
|
19
|
-
|
|
20
|
-
constructor() {
|
|
21
|
-
this.wizardEngine = new WizardEngine()
|
|
22
|
-
this.migrationWizard = new MigrationWizard()
|
|
23
|
-
this.docsProvider = new OpenSaasDocumentationProvider()
|
|
24
|
-
this.prismaIntrospector = new PrismaIntrospector()
|
|
25
|
-
this.keystoneIntrospector = new KeystoneIntrospector()
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Implement a feature - starts the wizard flow
|
|
30
|
-
*/
|
|
31
|
-
async implementFeature({ feature, description }: { feature: string; description?: string }) {
|
|
32
|
-
if (feature === 'custom' && !description) {
|
|
33
|
-
return {
|
|
34
|
-
content: [
|
|
35
|
-
{
|
|
36
|
-
type: 'text' as const,
|
|
37
|
-
text: '❌ For custom features, please provide a description of what you want to build.',
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (feature === 'custom') {
|
|
44
|
-
return {
|
|
45
|
-
content: [
|
|
46
|
-
{
|
|
47
|
-
type: 'text' as const,
|
|
48
|
-
text: `🔧 **Custom Feature**: ${description}
|
|
49
|
-
|
|
50
|
-
I'll help you build this custom feature. Let me search the docs for relevant patterns...
|
|
51
|
-
|
|
52
|
-
${await this.searchFeatureDocs({ topic: description! })}
|
|
53
|
-
|
|
54
|
-
Based on your description, consider using these OpenSaaS patterns:
|
|
55
|
-
- Define your data model with \`list()\` and field types
|
|
56
|
-
- Add access control with \`access.operation\` and \`access.field\`
|
|
57
|
-
- Use hooks for data transformation and side effects
|
|
58
|
-
- Consider if any plugins would help (auth, storage, RAG)
|
|
59
|
-
|
|
60
|
-
Would you like me to help you design the config for this feature?`,
|
|
61
|
-
},
|
|
62
|
-
],
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return this.wizardEngine.startFeature(feature)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Answer a wizard question
|
|
71
|
-
*/
|
|
72
|
-
async answerFeatureQuestion({
|
|
73
|
-
sessionId,
|
|
74
|
-
answer,
|
|
75
|
-
}: {
|
|
76
|
-
sessionId: string
|
|
77
|
-
answer: string | boolean | string[]
|
|
78
|
-
}) {
|
|
79
|
-
return this.wizardEngine.answerQuestion(sessionId, answer)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Answer a follow-up question
|
|
84
|
-
*/
|
|
85
|
-
async answerFollowUpQuestion({ sessionId, answer }: { sessionId: string; answer: string }) {
|
|
86
|
-
return this.wizardEngine.answerFollowUp(sessionId, answer)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Search documentation
|
|
91
|
-
*/
|
|
92
|
-
async searchFeatureDocs({ topic }: { topic: string }) {
|
|
93
|
-
const docs = await this.docsProvider.getTopicDocs(topic)
|
|
94
|
-
|
|
95
|
-
return {
|
|
96
|
-
content: [
|
|
97
|
-
{
|
|
98
|
-
type: 'text' as const,
|
|
99
|
-
text: `# ${docs.topic}
|
|
100
|
-
|
|
101
|
-
${docs.content}
|
|
102
|
-
|
|
103
|
-
${docs.codeExamples.length > 0 ? `\n## Code Examples\n\n${docs.codeExamples.join('\n\n')}` : ''}
|
|
104
|
-
|
|
105
|
-
${docs.relatedTopics.length > 0 ? `\n## Related Topics\n\n${docs.relatedTopics.map((t) => `- ${t}`).join('\n')}` : ''}
|
|
106
|
-
|
|
107
|
-
---
|
|
108
|
-
|
|
109
|
-
📚 **Full documentation**: ${docs.url}`,
|
|
110
|
-
},
|
|
111
|
-
],
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* List all available features
|
|
117
|
-
*/
|
|
118
|
-
async listFeatures() {
|
|
119
|
-
const features = getAllFeatures()
|
|
120
|
-
const categories = {
|
|
121
|
-
authentication: [] as typeof features,
|
|
122
|
-
content: [] as typeof features,
|
|
123
|
-
storage: [] as typeof features,
|
|
124
|
-
search: [] as typeof features,
|
|
125
|
-
custom: [] as typeof features,
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
features.forEach((feature) => {
|
|
129
|
-
categories[feature.category].push(feature)
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
const formatFeatureList = (featureList: typeof features) =>
|
|
133
|
-
featureList
|
|
134
|
-
.map(
|
|
135
|
-
(f) =>
|
|
136
|
-
`### ${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(', ')}` : ''}`,
|
|
137
|
-
)
|
|
138
|
-
.join('\n\n')
|
|
139
|
-
|
|
140
|
-
return {
|
|
141
|
-
content: [
|
|
142
|
-
{
|
|
143
|
-
type: 'text' as const,
|
|
144
|
-
text: `# Available OpenSaaS Stack Features
|
|
145
|
-
|
|
146
|
-
Use \`opensaas_implement_feature\` to start implementing any of these:
|
|
147
|
-
|
|
148
|
-
## 🔐 Authentication
|
|
149
|
-
|
|
150
|
-
${formatFeatureList(categories.authentication)}
|
|
151
|
-
|
|
152
|
-
## 📝 Content Management
|
|
153
|
-
|
|
154
|
-
${formatFeatureList(categories.content)}
|
|
155
|
-
|
|
156
|
-
## 📦 Storage
|
|
157
|
-
|
|
158
|
-
${formatFeatureList(categories.storage)}
|
|
159
|
-
|
|
160
|
-
## 🔍 Search
|
|
161
|
-
|
|
162
|
-
${formatFeatureList(categories.search)}
|
|
163
|
-
|
|
164
|
-
---
|
|
165
|
-
|
|
166
|
-
**To implement a feature**, call:
|
|
167
|
-
\`\`\`
|
|
168
|
-
opensaas_implement_feature({ feature: "authentication" })
|
|
169
|
-
\`\`\`
|
|
170
|
-
|
|
171
|
-
**For custom features**, call:
|
|
172
|
-
\`\`\`
|
|
173
|
-
opensaas_implement_feature({
|
|
174
|
-
feature: "custom",
|
|
175
|
-
description: "what you want to build"
|
|
176
|
-
})
|
|
177
|
-
\`\`\``,
|
|
178
|
-
},
|
|
179
|
-
],
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Suggest complementary features based on described features
|
|
185
|
-
*/
|
|
186
|
-
async suggestFeatures({ currentFeatures }: { currentFeatures?: string[] }) {
|
|
187
|
-
const allFeatures = getAllFeatures()
|
|
188
|
-
const implemented = new Set(currentFeatures || [])
|
|
189
|
-
|
|
190
|
-
const suggestions = allFeatures
|
|
191
|
-
.filter((f) => !implemented.has(f.id))
|
|
192
|
-
.map((f) => {
|
|
193
|
-
let reasoning = ''
|
|
194
|
-
|
|
195
|
-
// Add context-aware suggestions
|
|
196
|
-
if (f.id === 'authentication' && !implemented.has('authentication')) {
|
|
197
|
-
reasoning = 'Essential for user management and access control'
|
|
198
|
-
} else if (f.id === 'blog' && implemented.has('authentication')) {
|
|
199
|
-
reasoning = 'You have auth - add content creation capabilities'
|
|
200
|
-
} else if (
|
|
201
|
-
f.id === 'comments' &&
|
|
202
|
-
(implemented.has('blog') || implemented.has('authentication'))
|
|
203
|
-
) {
|
|
204
|
-
reasoning = 'Enhance engagement with user comments'
|
|
205
|
-
} else if (
|
|
206
|
-
f.id === 'file-upload' &&
|
|
207
|
-
(implemented.has('blog') || implemented.has('authentication'))
|
|
208
|
-
) {
|
|
209
|
-
reasoning = 'Add media support for richer content'
|
|
210
|
-
} else if (f.id === 'semantic-search' && implemented.has('blog')) {
|
|
211
|
-
reasoning = 'Make your content more discoverable'
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return { feature: f, reasoning }
|
|
215
|
-
})
|
|
216
|
-
.filter((s) => s.reasoning)
|
|
217
|
-
|
|
218
|
-
if (suggestions.length === 0) {
|
|
219
|
-
return {
|
|
220
|
-
content: [
|
|
221
|
-
{
|
|
222
|
-
type: 'text' as const,
|
|
223
|
-
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: '...' })`",
|
|
224
|
-
},
|
|
225
|
-
],
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return {
|
|
230
|
-
content: [
|
|
231
|
-
{
|
|
232
|
-
type: 'text' as const,
|
|
233
|
-
text: `# Feature Suggestions
|
|
234
|
-
|
|
235
|
-
Based on ${currentFeatures && currentFeatures.length > 0 ? `your current features (${currentFeatures.join(', ')})` : 'common patterns'}, consider adding:
|
|
236
|
-
|
|
237
|
-
${suggestions
|
|
238
|
-
.map(
|
|
239
|
-
(s, i) => `## ${i + 1}. ${s.feature.name}
|
|
240
|
-
|
|
241
|
-
${s.reasoning}
|
|
242
|
-
|
|
243
|
-
**What you'll get**:
|
|
244
|
-
${s.feature.includes.map((inc) => `- ${inc}`).join('\n')}
|
|
245
|
-
|
|
246
|
-
**To implement**: \`opensaas_implement_feature({ feature: "${s.feature.id}" })\`
|
|
247
|
-
`,
|
|
248
|
-
)
|
|
249
|
-
.join('\n---\n\n')}`,
|
|
250
|
-
},
|
|
251
|
-
],
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Validate a feature implementation
|
|
257
|
-
*/
|
|
258
|
-
async validateFeature({ feature }: { feature: string; configPath?: string }) {
|
|
259
|
-
const featureDefinition = getFeature(feature)
|
|
260
|
-
|
|
261
|
-
if (!featureDefinition) {
|
|
262
|
-
return {
|
|
263
|
-
content: [
|
|
264
|
-
{
|
|
265
|
-
type: 'text' as const,
|
|
266
|
-
text: `❌ Unknown feature: ${feature}\n\nUse \`opensaas_list_features\` to see available features.`,
|
|
267
|
-
},
|
|
268
|
-
],
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// TODO: Implement actual validation by reading the config file
|
|
273
|
-
// For now, return validation checklist
|
|
274
|
-
|
|
275
|
-
return {
|
|
276
|
-
content: [
|
|
277
|
-
{
|
|
278
|
-
type: 'text' as const,
|
|
279
|
-
text: `# ${featureDefinition.name} Validation
|
|
280
|
-
|
|
281
|
-
Checking your implementation...
|
|
282
|
-
|
|
283
|
-
## Checklist
|
|
284
|
-
|
|
285
|
-
${featureDefinition.includes.map((item) => `- [ ] ${item}`).join('\n')}
|
|
286
|
-
|
|
287
|
-
${featureDefinition.dependsOn && featureDefinition.dependsOn.length > 0 ? `\n## Dependencies\n\nEnsure these features are implemented:\n${featureDefinition.dependsOn.map((dep) => `- [ ] ${dep}`).join('\n')}\n` : ''}
|
|
288
|
-
|
|
289
|
-
---
|
|
290
|
-
|
|
291
|
-
**Manual validation steps**:
|
|
292
|
-
|
|
293
|
-
1. Check that all required lists are defined in your config
|
|
294
|
-
2. Verify access control is properly configured
|
|
295
|
-
3. Test the feature in your development environment
|
|
296
|
-
4. Run \`pnpm generate\` and \`pnpm db:push\` successfully
|
|
297
|
-
|
|
298
|
-
**Note**: Full automatic validation coming soon! For now, use this checklist to verify your implementation.`,
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Start a migration wizard session
|
|
306
|
-
*/
|
|
307
|
-
async startMigration({ projectType }: { projectType: ProjectType }) {
|
|
308
|
-
return this.migrationWizard.startMigration(projectType)
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Answer a migration wizard question
|
|
313
|
-
*/
|
|
314
|
-
async answerMigration({
|
|
315
|
-
sessionId,
|
|
316
|
-
answer,
|
|
317
|
-
}: {
|
|
318
|
-
sessionId: string
|
|
319
|
-
answer: string | boolean | string[]
|
|
320
|
-
}) {
|
|
321
|
-
return this.migrationWizard.answerQuestion(sessionId, answer)
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Introspect a Prisma schema
|
|
326
|
-
*/
|
|
327
|
-
async introspectPrisma({ schemaPath }: { schemaPath?: string }) {
|
|
328
|
-
const cwd = process.cwd()
|
|
329
|
-
const path = schemaPath || 'prisma/schema.prisma'
|
|
330
|
-
|
|
331
|
-
try {
|
|
332
|
-
const schema = await this.prismaIntrospector.introspect(cwd, path)
|
|
333
|
-
|
|
334
|
-
const modelList = schema.models
|
|
335
|
-
.map((m) => {
|
|
336
|
-
const fields = m.fields
|
|
337
|
-
.map((f) => {
|
|
338
|
-
let type = f.type
|
|
339
|
-
if (f.relation) type = `→ ${f.relation.model}`
|
|
340
|
-
if (f.isList) type = `${type}[]`
|
|
341
|
-
if (!f.isRequired) type = `${type}?`
|
|
342
|
-
return ` - ${f.name}: ${type}`
|
|
343
|
-
})
|
|
344
|
-
.join('\n')
|
|
345
|
-
return `### ${m.name}\n${fields}`
|
|
346
|
-
})
|
|
347
|
-
.join('\n\n')
|
|
348
|
-
|
|
349
|
-
const enumList =
|
|
350
|
-
schema.enums.length > 0
|
|
351
|
-
? `\n## Enums\n\n${schema.enums.map((e) => `- **${e.name}**: ${e.values.join(', ')}`).join('\n')}`
|
|
352
|
-
: ''
|
|
353
|
-
|
|
354
|
-
return {
|
|
355
|
-
content: [
|
|
356
|
-
{
|
|
357
|
-
type: 'text' as const,
|
|
358
|
-
text: `# Prisma Schema Analysis
|
|
359
|
-
|
|
360
|
-
**Provider:** ${schema.provider}
|
|
361
|
-
**Models:** ${schema.models.length}
|
|
362
|
-
**Enums:** ${schema.enums.length}
|
|
363
|
-
|
|
364
|
-
## Models
|
|
365
|
-
|
|
366
|
-
${modelList}
|
|
367
|
-
${enumList}
|
|
368
|
-
|
|
369
|
-
---
|
|
370
|
-
|
|
371
|
-
**Ready to migrate?** Use \`opensaas_start_migration({ projectType: "prisma" })\` to begin the wizard.`,
|
|
372
|
-
},
|
|
373
|
-
],
|
|
374
|
-
}
|
|
375
|
-
} catch (error) {
|
|
376
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
377
|
-
return {
|
|
378
|
-
content: [
|
|
379
|
-
{
|
|
380
|
-
type: 'text' as const,
|
|
381
|
-
text: `❌ Failed to introspect Prisma schema: ${message}\n\nMake sure the file exists at: ${path}`,
|
|
382
|
-
},
|
|
383
|
-
],
|
|
384
|
-
isError: true,
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Introspect a KeystoneJS config
|
|
391
|
-
*/
|
|
392
|
-
async introspectKeystone({ configPath }: { configPath?: string }) {
|
|
393
|
-
const cwd = process.cwd()
|
|
394
|
-
const path = configPath || 'keystone.config.ts'
|
|
395
|
-
|
|
396
|
-
try {
|
|
397
|
-
const config = await this.keystoneIntrospector.introspect(cwd, path)
|
|
398
|
-
|
|
399
|
-
const listInfo = config.models
|
|
400
|
-
.map((m) => {
|
|
401
|
-
const fields = m.fields.map((f) => ` - ${f.name}: ${f.type}`).join('\n')
|
|
402
|
-
return `### ${m.name}\n${fields}`
|
|
403
|
-
})
|
|
404
|
-
.join('\n\n')
|
|
405
|
-
|
|
406
|
-
return {
|
|
407
|
-
content: [
|
|
408
|
-
{
|
|
409
|
-
type: 'text' as const,
|
|
410
|
-
text: `# KeystoneJS Config Analysis
|
|
411
|
-
|
|
412
|
-
**Lists:** ${config.models.length}
|
|
413
|
-
|
|
414
|
-
## Lists
|
|
415
|
-
|
|
416
|
-
${listInfo}
|
|
417
|
-
|
|
418
|
-
---
|
|
419
|
-
|
|
420
|
-
**Note:** KeystoneJS → OpenSaaS migration is mostly 1:1. Field types and access control patterns map directly.
|
|
421
|
-
|
|
422
|
-
**Ready to migrate?** Use \`opensaas_start_migration({ projectType: "keystone" })\` to begin.`,
|
|
423
|
-
},
|
|
424
|
-
],
|
|
425
|
-
}
|
|
426
|
-
} catch (error) {
|
|
427
|
-
const message = error instanceof Error ? error.message : String(error)
|
|
428
|
-
return {
|
|
429
|
-
content: [
|
|
430
|
-
{
|
|
431
|
-
type: 'text' as const,
|
|
432
|
-
text: `❌ Failed to introspect KeystoneJS config: ${message}\n\nMake sure the file exists at: ${path}`,
|
|
433
|
-
},
|
|
434
|
-
],
|
|
435
|
-
isError: true,
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Search migration documentation
|
|
442
|
-
*/
|
|
443
|
-
async searchMigrationDocs({ query }: { query: string }) {
|
|
444
|
-
// First try local CLAUDE.md files
|
|
445
|
-
const localDocs = await this.docsProvider.searchLocalDocs(query)
|
|
446
|
-
|
|
447
|
-
// Then try hosted docs
|
|
448
|
-
const hostedDocs = await this.docsProvider.searchDocs(query)
|
|
449
|
-
|
|
450
|
-
const sections: string[] = []
|
|
451
|
-
|
|
452
|
-
if (localDocs.content) {
|
|
453
|
-
sections.push(`## Local Documentation\n\n${localDocs.content}`)
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (hostedDocs.content && hostedDocs.content !== 'No documentation found for this query.') {
|
|
457
|
-
sections.push(`## Online Documentation\n\n${hostedDocs.content}`)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
if (sections.length === 0) {
|
|
461
|
-
return {
|
|
462
|
-
content: [
|
|
463
|
-
{
|
|
464
|
-
type: 'text' as const,
|
|
465
|
-
text: `No documentation found for "${query}".
|
|
466
|
-
|
|
467
|
-
Try these searches:
|
|
468
|
-
- "access control" - How to restrict access to data
|
|
469
|
-
- "field types" - Available field types in OpenSaaS
|
|
470
|
-
- "authentication" - Setting up auth with Better-auth
|
|
471
|
-
- "hooks" - Data transformation and side effects
|
|
472
|
-
|
|
473
|
-
Or visit: https://stack.opensaas.au/`,
|
|
474
|
-
},
|
|
475
|
-
],
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
return {
|
|
480
|
-
content: [
|
|
481
|
-
{
|
|
482
|
-
type: 'text' as const,
|
|
483
|
-
text: `# Documentation: ${query}\n\n${sections.join('\n\n---\n\n')}`,
|
|
484
|
-
},
|
|
485
|
-
],
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
/**
|
|
490
|
-
* Get example code for a feature
|
|
491
|
-
*/
|
|
492
|
-
async getExample({ feature }: { feature: string }) {
|
|
493
|
-
const example = await this.docsProvider.getExampleConfig(feature)
|
|
494
|
-
|
|
495
|
-
if (!example) {
|
|
496
|
-
return {
|
|
497
|
-
content: [
|
|
498
|
-
{
|
|
499
|
-
type: 'text' as const,
|
|
500
|
-
text: `No example found for "${feature}".
|
|
501
|
-
|
|
502
|
-
Available examples:
|
|
503
|
-
- **blog-with-auth** - Blog with user authentication
|
|
504
|
-
- **access-control** - Access control patterns
|
|
505
|
-
- **relationships** - Model relationships
|
|
506
|
-
- **hooks** - Data transformation hooks
|
|
507
|
-
- **custom-fields** - Custom field types
|
|
508
|
-
|
|
509
|
-
Use: \`opensaas_get_example({ feature: "example-name" })\``,
|
|
510
|
-
},
|
|
511
|
-
],
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
return {
|
|
516
|
-
content: [
|
|
517
|
-
{
|
|
518
|
-
type: 'text' as const,
|
|
519
|
-
text: `# Example: ${feature}
|
|
520
|
-
|
|
521
|
-
${example.description}
|
|
522
|
-
|
|
523
|
-
\`\`\`typescript
|
|
524
|
-
${example.code}
|
|
525
|
-
\`\`\`
|
|
526
|
-
|
|
527
|
-
${example.notes ? `\n## Notes\n\n${example.notes}` : ''}
|
|
528
|
-
|
|
529
|
-
---
|
|
530
|
-
|
|
531
|
-
📚 Full example at: ${example.sourcePath}`,
|
|
532
|
-
},
|
|
533
|
-
],
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Cleanup - clear wizard sessions and caches
|
|
539
|
-
*/
|
|
540
|
-
cleanup() {
|
|
541
|
-
this.wizardEngine.clearCompletedSessions()
|
|
542
|
-
this.docsProvider.clearExpiredCache()
|
|
543
|
-
}
|
|
544
|
-
}
|