create-chaaskit 0.1.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 (122) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +25 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/add-infra.d.ts +6 -0
  6. package/dist/commands/add-infra.d.ts.map +1 -0
  7. package/dist/commands/add-infra.js +160 -0
  8. package/dist/commands/add-infra.js.map +1 -0
  9. package/dist/commands/build.d.ts +2 -0
  10. package/dist/commands/build.d.ts.map +1 -0
  11. package/dist/commands/build.js +63 -0
  12. package/dist/commands/build.js.map +1 -0
  13. package/dist/commands/db-sync.d.ts +13 -0
  14. package/dist/commands/db-sync.d.ts.map +1 -0
  15. package/dist/commands/db-sync.js +108 -0
  16. package/dist/commands/db-sync.js.map +1 -0
  17. package/dist/commands/dev.d.ts +7 -0
  18. package/dist/commands/dev.d.ts.map +1 -0
  19. package/dist/commands/dev.js +61 -0
  20. package/dist/commands/dev.js.map +1 -0
  21. package/dist/commands/init.d.ts +9 -0
  22. package/dist/commands/init.d.ts.map +1 -0
  23. package/dist/commands/init.js +214 -0
  24. package/dist/commands/init.js.map +1 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +57 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/templates/.env.example +24 -0
  30. package/dist/templates/README.md +81 -0
  31. package/dist/templates/app/components/AcceptInviteClient.tsx +10 -0
  32. package/dist/templates/app/components/AdminDashboardClient.tsx +10 -0
  33. package/dist/templates/app/components/AdminTeamClient.tsx +10 -0
  34. package/dist/templates/app/components/AdminTeamsClient.tsx +10 -0
  35. package/dist/templates/app/components/AdminUsersClient.tsx +10 -0
  36. package/dist/templates/app/components/ApiKeysClient.tsx +10 -0
  37. package/dist/templates/app/components/AutomationsClient.tsx +10 -0
  38. package/dist/templates/app/components/ChatClient.tsx +13 -0
  39. package/dist/templates/app/components/ClientOnly.tsx +6 -0
  40. package/dist/templates/app/components/DocumentsClient.tsx +10 -0
  41. package/dist/templates/app/components/OAuthConsentClient.tsx +10 -0
  42. package/dist/templates/app/components/PricingClient.tsx +10 -0
  43. package/dist/templates/app/components/TeamSettingsClient.tsx +10 -0
  44. package/dist/templates/app/components/VerifyEmailClient.tsx +10 -0
  45. package/dist/templates/app/entry.client.tsx +12 -0
  46. package/dist/templates/app/entry.server.tsx +67 -0
  47. package/dist/templates/app/root.tsx +91 -0
  48. package/dist/templates/app/routes/_index.tsx +82 -0
  49. package/dist/templates/app/routes/admin._index.tsx +57 -0
  50. package/dist/templates/app/routes/admin.teams.$teamId.tsx +57 -0
  51. package/dist/templates/app/routes/admin.teams._index.tsx +57 -0
  52. package/dist/templates/app/routes/admin.users.tsx +57 -0
  53. package/dist/templates/app/routes/api-keys.tsx +57 -0
  54. package/dist/templates/app/routes/automations.tsx +57 -0
  55. package/dist/templates/app/routes/chat._index.tsx +11 -0
  56. package/dist/templates/app/routes/chat.admin._index.tsx +10 -0
  57. package/dist/templates/app/routes/chat.admin.teams.$teamId.tsx +10 -0
  58. package/dist/templates/app/routes/chat.admin.teams._index.tsx +10 -0
  59. package/dist/templates/app/routes/chat.admin.users.tsx +10 -0
  60. package/dist/templates/app/routes/chat.api-keys.tsx +10 -0
  61. package/dist/templates/app/routes/chat.automations.tsx +10 -0
  62. package/dist/templates/app/routes/chat.documents.tsx +10 -0
  63. package/dist/templates/app/routes/chat.team.$teamId.settings.tsx +10 -0
  64. package/dist/templates/app/routes/chat.thread.$threadId.tsx +11 -0
  65. package/dist/templates/app/routes/chat.tsx +39 -0
  66. package/dist/templates/app/routes/documents.tsx +57 -0
  67. package/dist/templates/app/routes/invite.$token.tsx +10 -0
  68. package/dist/templates/app/routes/login.tsx +334 -0
  69. package/dist/templates/app/routes/oauth.consent.tsx +10 -0
  70. package/dist/templates/app/routes/pricing.tsx +10 -0
  71. package/dist/templates/app/routes/privacy.tsx +197 -0
  72. package/dist/templates/app/routes/register.tsx +398 -0
  73. package/dist/templates/app/routes/shared.$shareId.tsx +226 -0
  74. package/dist/templates/app/routes/team.$teamId.settings.tsx +57 -0
  75. package/dist/templates/app/routes/terms.tsx +173 -0
  76. package/dist/templates/app/routes/thread.$threadId.tsx +102 -0
  77. package/dist/templates/app/routes/verify-email.tsx +10 -0
  78. package/dist/templates/app/routes.ts +47 -0
  79. package/dist/templates/config/app.config.ts +216 -0
  80. package/dist/templates/docs/admin.md +257 -0
  81. package/dist/templates/docs/api-keys.md +403 -0
  82. package/dist/templates/docs/authentication.md +247 -0
  83. package/dist/templates/docs/configuration.md +1212 -0
  84. package/dist/templates/docs/custom-pages.md +466 -0
  85. package/dist/templates/docs/deployment.md +362 -0
  86. package/dist/templates/docs/development.md +411 -0
  87. package/dist/templates/docs/documents.md +293 -0
  88. package/dist/templates/docs/extensions.md +639 -0
  89. package/dist/templates/docs/index.md +139 -0
  90. package/dist/templates/docs/installation.md +286 -0
  91. package/dist/templates/docs/mcp.md +952 -0
  92. package/dist/templates/docs/native-tools.md +688 -0
  93. package/dist/templates/docs/queue.md +514 -0
  94. package/dist/templates/docs/scheduled-prompts.md +279 -0
  95. package/dist/templates/docs/settings.md +415 -0
  96. package/dist/templates/docs/slack.md +318 -0
  97. package/dist/templates/docs/styling.md +288 -0
  98. package/dist/templates/extensions/agents/.gitkeep +0 -0
  99. package/dist/templates/extensions/pages/.gitkeep +0 -0
  100. package/dist/templates/extensions/payment-plans/.gitkeep +0 -0
  101. package/dist/templates/index.html +16 -0
  102. package/dist/templates/infra-aws/.github/workflows/deploy.yml +95 -0
  103. package/dist/templates/infra-aws/README.md +207 -0
  104. package/dist/templates/infra-aws/bin/cdk.ts +18 -0
  105. package/dist/templates/infra-aws/cdk.json +43 -0
  106. package/dist/templates/infra-aws/config/deployment.ts +156 -0
  107. package/dist/templates/infra-aws/lib/chaaskit-stack.ts +419 -0
  108. package/dist/templates/infra-aws/package.json +27 -0
  109. package/dist/templates/infra-aws/scripts/build-app.sh +63 -0
  110. package/dist/templates/infra-aws/tsconfig.json +25 -0
  111. package/dist/templates/package.json +46 -0
  112. package/dist/templates/prisma/schema/base.prisma +584 -0
  113. package/dist/templates/prisma/schema/custom.prisma +24 -0
  114. package/dist/templates/prisma/schema.prisma +271 -0
  115. package/dist/templates/public/favicon.svg +4 -0
  116. package/dist/templates/public/logo.svg +4 -0
  117. package/dist/templates/react-router.config.ts +11 -0
  118. package/dist/templates/server.js +52 -0
  119. package/dist/templates/src/main.tsx +8 -0
  120. package/dist/templates/tsconfig.json +26 -0
  121. package/dist/templates/vite.config.ts +26 -0
  122. package/package.json +46 -0
@@ -0,0 +1,639 @@
1
+ # Extensions
2
+
3
+ ChaasKit supports extensions for customizing behavior without modifying core code. Extensions live in the `extensions/` directory of your project and are automatically loaded.
4
+
5
+ ## Extension Types
6
+
7
+ ### Server Extensions
8
+
9
+ | Type | Directory | Purpose |
10
+ |------|-----------|---------|
11
+ | Agents | `extensions/agents/` | Custom AI agent implementations |
12
+ | Payment Plans | `extensions/payment-plans/` | Custom pricing and billing logic |
13
+ | Auth Providers | `extensions/auth-providers/` | Additional OAuth providers |
14
+ | MCP Resources | `extensions/mcp-resources/` | Custom resources for MCP server |
15
+
16
+ ### Client Extensions
17
+
18
+ | Type | Directory | Purpose |
19
+ |------|-----------|---------|
20
+ | Pages | `extensions/pages/` | Custom frontend pages |
21
+ | Tools | `extensions/tools/` | Custom tool result renderers |
22
+
23
+ ## Directory Structure
24
+
25
+ ```
26
+ my-chat-app/
27
+ └── extensions/
28
+ ├── agents/
29
+ │ └── my-custom-agent.ts # Custom agent implementation
30
+ ├── payment-plans/
31
+ │ └── enterprise-plan.ts # Custom pricing plan
32
+ ├── auth-providers/
33
+ │ └── slack-auth.ts # Custom OAuth provider
34
+ ├── mcp-resources/
35
+ │ └── user-profile.ts # Custom MCP resource
36
+ ├── pages/
37
+ │ └── analytics.tsx # Custom frontend page
38
+ └── tools/
39
+ └── chart-renderer.tsx # Custom tool result renderer
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Server Extensions
45
+
46
+ Server extensions are automatically discovered and loaded from your `extensions/` directory when the server starts.
47
+
48
+ ### Registry System
49
+
50
+ Extensions register themselves with the global registry:
51
+
52
+ ```typescript
53
+ import { registry } from '@chaaskit/server';
54
+
55
+ // Register an extension
56
+ registry.register('category', 'name', Implementation);
57
+
58
+ // Categories: 'agent', 'payment-plan', 'auth-provider', 'mcp-resource'
59
+ ```
60
+
61
+ ### Custom Agents
62
+
63
+ Create custom agent implementations that wrap AI providers or connect to external services.
64
+
65
+ #### Base Agent Class
66
+
67
+ ```typescript
68
+ export abstract class BaseAgent {
69
+ abstract chat(
70
+ messages: ChatMessage[],
71
+ options?: ChatOptions
72
+ ): AsyncGenerator<ChatEvent>;
73
+ }
74
+ ```
75
+
76
+ #### Example: Custom Agent with Pre/Post Processing
77
+
78
+ ```typescript
79
+ // extensions/agents/moderated-agent.ts
80
+ import { BaseAgent, registry } from '@chaaskit/server';
81
+ import type { ChatMessage, ChatOptions, ChatEvent } from '@chaaskit/shared';
82
+
83
+ export class ModeratedAgent extends BaseAgent {
84
+ async *chat(
85
+ messages: ChatMessage[],
86
+ options?: ChatOptions
87
+ ): AsyncGenerator<ChatEvent> {
88
+ // Pre-processing: Content moderation
89
+ const lastMessage = messages[messages.length - 1];
90
+ if (lastMessage?.role === 'user') {
91
+ const isAllowed = await this.moderateContent(lastMessage.content);
92
+ if (!isAllowed) {
93
+ yield { type: 'text', content: "I can't help with that request." };
94
+ yield { type: 'done' };
95
+ return;
96
+ }
97
+ }
98
+
99
+ // Pass through to inner agent...
100
+ yield { type: 'done' };
101
+ }
102
+
103
+ private async moderateContent(content: string): Promise<boolean> {
104
+ const blockedTerms = ['harmful', 'illegal'];
105
+ return !blockedTerms.some(term => content.toLowerCase().includes(term));
106
+ }
107
+ }
108
+
109
+ // Register the agent
110
+ registry.register('agent', 'moderated', ModeratedAgent);
111
+ ```
112
+
113
+ #### Using Custom Agents in Config
114
+
115
+ ```typescript
116
+ // config/app.config.ts
117
+ agent: {
118
+ agents: [
119
+ {
120
+ id: 'moderated-assistant',
121
+ name: 'Moderated Assistant',
122
+ type: 'custom',
123
+ customType: 'moderated', // Matches registry name
124
+ config: {
125
+ provider: 'openai',
126
+ model: 'gpt-4o-mini',
127
+ },
128
+ },
129
+ ],
130
+ }
131
+ ```
132
+
133
+ ### Custom Payment Plans
134
+
135
+ Create custom pricing logic for enterprise plans, usage-based billing, or special promotions.
136
+
137
+ #### Base Pricing Plan Class
138
+
139
+ ```typescript
140
+ export abstract class BasePricingPlan {
141
+ abstract id: string;
142
+ abstract name: string;
143
+
144
+ abstract canSendMessage(user: User): Promise<boolean>;
145
+ abstract onMessageSent(user: User): Promise<void>;
146
+ abstract getUsageDisplay(user: User): Promise<UsageDisplay>;
147
+ }
148
+ ```
149
+
150
+ #### Example: Enterprise Plan
151
+
152
+ ```typescript
153
+ // extensions/payment-plans/enterprise-plan.ts
154
+ import { BasePricingPlan, registry } from '@chaaskit/server';
155
+ import type { User, UsageDisplay } from '@chaaskit/shared';
156
+ import { db } from '@chaaskit/db';
157
+
158
+ export class EnterprisePlan extends BasePricingPlan {
159
+ id = 'enterprise';
160
+ name = 'Enterprise';
161
+
162
+ private limits = {
163
+ messagesPerDay: 1000,
164
+ messagesPerMonth: 50000,
165
+ };
166
+
167
+ async canSendMessage(user: User): Promise<boolean> {
168
+ const today = new Date();
169
+ today.setHours(0, 0, 0, 0);
170
+
171
+ const dailyCount = await db.message.count({
172
+ where: {
173
+ thread: { userId: user.id },
174
+ role: 'user',
175
+ createdAt: { gte: today },
176
+ },
177
+ });
178
+
179
+ return dailyCount < this.limits.messagesPerDay;
180
+ }
181
+
182
+ async onMessageSent(user: User): Promise<void> {
183
+ // Track usage, send alerts, etc.
184
+ }
185
+
186
+ async getUsageDisplay(user: User): Promise<UsageDisplay> {
187
+ // Return usage info
188
+ return {
189
+ used: 0,
190
+ limit: this.limits.messagesPerMonth,
191
+ label: '0 / 50,000 messages',
192
+ percentage: 0,
193
+ };
194
+ }
195
+ }
196
+
197
+ registry.register('payment-plan', 'enterprise', EnterprisePlan);
198
+ ```
199
+
200
+ ### Custom Auth Providers
201
+
202
+ Add additional OAuth providers beyond the built-in Google and GitHub.
203
+
204
+ ```typescript
205
+ // extensions/auth-providers/slack-auth.ts
206
+ import { BaseAuthProvider, registry } from '@chaaskit/server';
207
+ import { Strategy as SlackStrategy } from 'passport-slack-oauth2';
208
+
209
+ export class SlackAuthProvider extends BaseAuthProvider {
210
+ name = 'slack';
211
+ displayName = 'Slack';
212
+
213
+ getStrategy() {
214
+ return new SlackStrategy({
215
+ clientID: process.env.SLACK_CLIENT_ID!,
216
+ clientSecret: process.env.SLACK_CLIENT_SECRET!,
217
+ callbackURL: `${process.env.API_URL}/api/auth/callback/slack`,
218
+ }, async (accessToken, refreshToken, profile, done) => {
219
+ // Handle user creation/lookup
220
+ });
221
+ }
222
+ }
223
+
224
+ registry.register('auth-provider', 'slack', SlackAuthProvider);
225
+ ```
226
+
227
+ ### MCP Resources
228
+
229
+ When the MCP server export feature is enabled, you can expose custom resources that external MCP clients can read. Resources provide read-only data access via the MCP protocol.
230
+
231
+ #### Base MCP Resource Class
232
+
233
+ ```typescript
234
+ export abstract class BaseMCPResource {
235
+ /** URI for the resource (e.g., "myapp://users/profile") */
236
+ abstract uri: string;
237
+
238
+ /** Human-readable name for the resource */
239
+ abstract name: string;
240
+
241
+ /** Optional description */
242
+ abstract description?: string;
243
+
244
+ /** MIME type of the resource content */
245
+ abstract mimeType?: string;
246
+
247
+ /**
248
+ * Read the resource content.
249
+ * @param context - Context including the requesting user's ID
250
+ * @returns Resource content as text or base64-encoded blob
251
+ */
252
+ abstract read(context: { userId?: string }): Promise<{ text?: string; blob?: string }>;
253
+ }
254
+ ```
255
+
256
+ #### Example: User Profile Resource
257
+
258
+ ```typescript
259
+ // extensions/mcp-resources/user-profile.ts
260
+ import { BaseMCPResource, registry } from '@chaaskit/server';
261
+ import { db } from '@chaaskit/db';
262
+
263
+ class UserProfileResource extends BaseMCPResource {
264
+ uri = 'chatapp://user/profile';
265
+ name = 'User Profile';
266
+ description = 'Current user profile information';
267
+ mimeType = 'application/json';
268
+
269
+ async read(context: { userId?: string }): Promise<{ text?: string }> {
270
+ if (!context.userId) {
271
+ return { text: JSON.stringify({ error: 'Not authenticated' }) };
272
+ }
273
+
274
+ const user = await db.user.findUnique({
275
+ where: { id: context.userId },
276
+ select: {
277
+ id: true,
278
+ email: true,
279
+ name: true,
280
+ createdAt: true,
281
+ },
282
+ });
283
+
284
+ return {
285
+ text: JSON.stringify(user, null, 2),
286
+ };
287
+ }
288
+ }
289
+
290
+ // Register the resource
291
+ registry.register('mcp-resource', 'user-profile', new UserProfileResource());
292
+ ```
293
+
294
+ #### Example: Thread Summary Resource
295
+
296
+ ```typescript
297
+ // extensions/mcp-resources/thread-summary.ts
298
+ import { BaseMCPResource, registry } from '@chaaskit/server';
299
+ import { db } from '@chaaskit/db';
300
+
301
+ class ThreadSummaryResource extends BaseMCPResource {
302
+ uri = 'chatapp://threads/summary';
303
+ name = 'Thread Summary';
304
+ description = 'Summary of user conversation threads';
305
+ mimeType = 'application/json';
306
+
307
+ async read(context: { userId?: string }): Promise<{ text?: string }> {
308
+ if (!context.userId) {
309
+ return { text: JSON.stringify({ error: 'Not authenticated' }) };
310
+ }
311
+
312
+ const threads = await db.thread.findMany({
313
+ where: { userId: context.userId },
314
+ select: {
315
+ id: true,
316
+ title: true,
317
+ createdAt: true,
318
+ updatedAt: true,
319
+ _count: { select: { messages: true } },
320
+ },
321
+ orderBy: { updatedAt: 'desc' },
322
+ take: 10,
323
+ });
324
+
325
+ const summary = threads.map(t => ({
326
+ id: t.id,
327
+ title: t.title,
328
+ messageCount: t._count.messages,
329
+ lastUpdated: t.updatedAt,
330
+ }));
331
+
332
+ return {
333
+ text: JSON.stringify(summary, null, 2),
334
+ };
335
+ }
336
+ }
337
+
338
+ registry.register('mcp-resource', 'thread-summary', new ThreadSummaryResource());
339
+ ```
340
+
341
+ #### Example: Binary Resource (Image)
342
+
343
+ ```typescript
344
+ // extensions/mcp-resources/avatar.ts
345
+ import { BaseMCPResource, registry } from '@chaaskit/server';
346
+ import fs from 'fs/promises';
347
+
348
+ class AvatarResource extends BaseMCPResource {
349
+ uri = 'chatapp://user/avatar';
350
+ name = 'User Avatar';
351
+ description = 'User profile avatar image';
352
+ mimeType = 'image/png';
353
+
354
+ async read(context: { userId?: string }): Promise<{ blob?: string }> {
355
+ if (!context.userId) {
356
+ // Return a default avatar
357
+ const defaultAvatar = await fs.readFile('./public/default-avatar.png');
358
+ return { blob: defaultAvatar.toString('base64') };
359
+ }
360
+
361
+ // Load user-specific avatar
362
+ const avatarPath = `./uploads/avatars/${context.userId}.png`;
363
+ try {
364
+ const avatar = await fs.readFile(avatarPath);
365
+ return { blob: avatar.toString('base64') };
366
+ } catch {
367
+ // Return default if not found
368
+ const defaultAvatar = await fs.readFile('./public/default-avatar.png');
369
+ return { blob: defaultAvatar.toString('base64') };
370
+ }
371
+ }
372
+ }
373
+
374
+ registry.register('mcp-resource', 'avatar', new AvatarResource());
375
+ ```
376
+
377
+ #### MCP Resource URIs
378
+
379
+ Use descriptive URI schemes for your resources:
380
+
381
+ | Pattern | Example | Description |
382
+ |---------|---------|-------------|
383
+ | `appname://category/item` | `chatapp://user/profile` | User-specific data |
384
+ | `appname://data/collection` | `chatapp://threads/recent` | Collections |
385
+ | `appname://config/settings` | `chatapp://config/theme` | Configuration |
386
+
387
+ #### Security Considerations
388
+
389
+ 1. **User Context**: Always check `context.userId` before returning user-specific data
390
+ 2. **Data Filtering**: Only expose data the user is authorized to see
391
+ 3. **Error Handling**: Return safe error messages, don't leak internal details
392
+ 4. **Rate Limiting**: Consider caching for expensive resource reads
393
+
394
+ ---
395
+
396
+ ## Client Extensions
397
+
398
+ ### Custom Pages
399
+
400
+ With React Router v7, custom pages are created as route files in `app/routes/`. See the [Custom Pages](./custom-pages.md) documentation for details.
401
+
402
+ For pages within the authenticated chat app area, create files following the naming convention:
403
+
404
+ ```tsx
405
+ // app/routes/chat.analytics.tsx
406
+ // Creates a route at /chat/analytics
407
+
408
+ import { ChatProviders } from '@chaaskit/client';
409
+
410
+ export default function AnalyticsPage() {
411
+ return (
412
+ <ChatProviders>
413
+ <div className="p-6">
414
+ <h1 className="text-2xl font-bold text-text-primary">Analytics</h1>
415
+ <p className="text-text-secondary">Your usage statistics</p>
416
+ {/* Your analytics content */}
417
+ </div>
418
+ </ChatProviders>
419
+ );
420
+ }
421
+ ```
422
+
423
+ ### Custom Tool Renderers
424
+
425
+ Create custom renderers for specific tool outputs. Tool renderers are registered with the client registry and used when displaying tool results in chat.
426
+
427
+ ```tsx
428
+ // extensions/tools/chart-renderer.tsx
429
+ import { clientRegistry } from '@chaaskit/client/extensions';
430
+
431
+ interface ChartResult {
432
+ type: 'chart';
433
+ data: Array<{ name: string; value: number }>;
434
+ title?: string;
435
+ }
436
+
437
+ function ChartRenderer({ result }: { result: ChartResult }) {
438
+ return (
439
+ <div className="rounded-lg border border-border p-4">
440
+ {result.title && (
441
+ <h3 className="mb-4 font-semibold">{result.title}</h3>
442
+ )}
443
+ {/* Render chart */}
444
+ </div>
445
+ );
446
+ }
447
+
448
+ clientRegistry.registerTool({
449
+ name: 'chart',
450
+ description: 'Renders chart data',
451
+ resultRenderer: ChartRenderer,
452
+ });
453
+
454
+ export default ChartRenderer;
455
+ ```
456
+
457
+ ### Using Extension Hooks
458
+
459
+ ```tsx
460
+ import { useToolRenderer } from '@chaaskit/client/extensions';
461
+
462
+ function MyComponent() {
463
+ // Get a specific tool renderer
464
+ const chartRenderer = useToolRenderer('chart');
465
+
466
+ return (
467
+ // ...
468
+ );
469
+ }
470
+ ```
471
+
472
+ ---
473
+
474
+ ## Loading Extensions
475
+
476
+ ### Server Extensions
477
+
478
+ Server extensions are automatically loaded from `extensions/` when the server starts. The loader scans:
479
+
480
+ - `extensions/agents/*.{ts,js}`
481
+ - `extensions/payment-plans/*.{ts,js}`
482
+ - `extensions/auth-providers/*.{ts,js}`
483
+ - `extensions/mcp-resources/*.{ts,js}`
484
+
485
+ Each file should register itself with the registry when imported.
486
+
487
+ ### Client Extensions
488
+
489
+ Custom tool renderers need to be imported in your app. Create an entry point and import it in your `app/root.tsx`:
490
+
491
+ ```typescript
492
+ // extensions/tools/index.ts
493
+ import './chart-renderer';
494
+ ```
495
+
496
+ ```tsx
497
+ // app/root.tsx
498
+ import '../extensions/tools';
499
+ ```
500
+
501
+ ---
502
+
503
+ ## Best Practices
504
+
505
+ 1. **Keep extensions isolated** - Don't modify core packages; use the registry pattern
506
+ 2. **Type safety** - Extend base classes and implement required interfaces
507
+ 3. **Error handling** - Always handle errors gracefully in extensions
508
+ 4. **Environment variables** - Use env vars for secrets, don't hardcode
509
+ 5. **Testing** - Test extensions independently before integrating
510
+ 6. **Documentation** - Document your extensions for team members
511
+
512
+ ## Troubleshooting
513
+
514
+ ### Extension not loading
515
+
516
+ 1. Check that the extension file is in the correct directory
517
+ 2. Verify the extension calls `registry.register()`
518
+ 3. Check server/browser logs for import errors
519
+
520
+ ### Type errors
521
+
522
+ 1. Ensure you're extending the correct base class
523
+ 2. Import types from `@chaaskit/shared`
524
+ 3. Run `pnpm typecheck` to catch issues
525
+
526
+ ### Client extension not appearing
527
+
528
+ 1. Ensure you're importing the extension file
529
+ 2. Check that `showInSidebar: true` is set for pages
530
+ 3. Verify `requiresAuth` matches the user's auth state
531
+
532
+ ---
533
+
534
+ ## Custom Server Routes
535
+
536
+ For full control over your server, you can create a custom server entry point that adds your own routes alongside the ChaasKit API.
537
+
538
+ ### Custom Server Entry Point
539
+
540
+ Create `src/server.ts`:
541
+
542
+ ```typescript
543
+ // src/server.ts
544
+ import { createApp, requireAuth, optionalAuth } from '@chaaskit/server';
545
+ import { db } from '@chaaskit/db';
546
+ import { Router } from 'express';
547
+ import { config } from '../config/app.config.js';
548
+
549
+ async function start() {
550
+ // Create the base app with all ChaasKit functionality
551
+ const app = await createApp({ config });
552
+
553
+ // Add your custom routes
554
+ const customRouter = Router();
555
+
556
+ // Public route
557
+ customRouter.get('/api/custom/hello', (req, res) => {
558
+ res.json({ message: 'Hello from custom route!' });
559
+ });
560
+
561
+ // Protected route - requires authentication
562
+ customRouter.get('/api/custom/profile', requireAuth, (req, res) => {
563
+ res.json({
564
+ userId: req.user!.id,
565
+ email: req.user!.email,
566
+ });
567
+ });
568
+
569
+ // Route with database access
570
+ customRouter.get('/api/custom/stats', requireAuth, async (req, res) => {
571
+ const threadCount = await db.thread.count({
572
+ where: { userId: req.user!.id },
573
+ });
574
+ res.json({ threads: threadCount });
575
+ });
576
+
577
+ // Webhook handler
578
+ customRouter.post('/api/custom/webhook', (req, res) => {
579
+ // Handle webhooks
580
+ res.json({ received: true });
581
+ });
582
+
583
+ // Mount your custom router
584
+ app.use(customRouter);
585
+
586
+ // Start the server
587
+ const port = process.env.PORT || 3000;
588
+ app.listen(port, () => {
589
+ console.log(`Server running on port ${port}`);
590
+ });
591
+ }
592
+
593
+ start().catch(console.error);
594
+ ```
595
+
596
+ ### Update package.json
597
+
598
+ ```json
599
+ {
600
+ "scripts": {
601
+ "dev:server": "tsx watch src/server.ts",
602
+ "dev:client": "vite",
603
+ "dev": "concurrently \"pnpm dev:server\" \"pnpm dev:client\"",
604
+ "start": "node dist/server.js"
605
+ }
606
+ }
607
+ ```
608
+
609
+ ### Available Middleware
610
+
611
+ | Middleware | Purpose |
612
+ |------------|---------|
613
+ | `requireAuth` | Requires authentication, adds `req.user` |
614
+ | `optionalAuth` | Adds `req.user` if authenticated, allows anonymous |
615
+
616
+ ### Adding Marketing Pages (with basePath)
617
+
618
+ When using `basePath` to run the chat app under a sub-path (e.g., `/app`), you can serve marketing pages from the root:
619
+
620
+ ```typescript
621
+ import express from 'express';
622
+ import path from 'path';
623
+
624
+ // In your server setup after createApp()
625
+
626
+ // Serve marketing pages
627
+ app.get('/', (req, res) => {
628
+ res.sendFile(path.join(__dirname, '../public/marketing/index.html'));
629
+ });
630
+
631
+ app.get('/pricing', (req, res) => {
632
+ res.sendFile(path.join(__dirname, '../public/marketing/pricing.html'));
633
+ });
634
+
635
+ // Or serve static marketing site
636
+ app.use(express.static(path.join(__dirname, '../public/marketing')));
637
+
638
+ // Chat app is served under /app/* (via basePath config)
639
+ ```