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.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +25 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/add-infra.d.ts +6 -0
- package/dist/commands/add-infra.d.ts.map +1 -0
- package/dist/commands/add-infra.js +160 -0
- package/dist/commands/add-infra.js.map +1 -0
- package/dist/commands/build.d.ts +2 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +63 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/db-sync.d.ts +13 -0
- package/dist/commands/db-sync.d.ts.map +1 -0
- package/dist/commands/db-sync.js +108 -0
- package/dist/commands/db-sync.js.map +1 -0
- package/dist/commands/dev.d.ts +7 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +61 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/init.d.ts +9 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +214 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/.env.example +24 -0
- package/dist/templates/README.md +81 -0
- package/dist/templates/app/components/AcceptInviteClient.tsx +10 -0
- package/dist/templates/app/components/AdminDashboardClient.tsx +10 -0
- package/dist/templates/app/components/AdminTeamClient.tsx +10 -0
- package/dist/templates/app/components/AdminTeamsClient.tsx +10 -0
- package/dist/templates/app/components/AdminUsersClient.tsx +10 -0
- package/dist/templates/app/components/ApiKeysClient.tsx +10 -0
- package/dist/templates/app/components/AutomationsClient.tsx +10 -0
- package/dist/templates/app/components/ChatClient.tsx +13 -0
- package/dist/templates/app/components/ClientOnly.tsx +6 -0
- package/dist/templates/app/components/DocumentsClient.tsx +10 -0
- package/dist/templates/app/components/OAuthConsentClient.tsx +10 -0
- package/dist/templates/app/components/PricingClient.tsx +10 -0
- package/dist/templates/app/components/TeamSettingsClient.tsx +10 -0
- package/dist/templates/app/components/VerifyEmailClient.tsx +10 -0
- package/dist/templates/app/entry.client.tsx +12 -0
- package/dist/templates/app/entry.server.tsx +67 -0
- package/dist/templates/app/root.tsx +91 -0
- package/dist/templates/app/routes/_index.tsx +82 -0
- package/dist/templates/app/routes/admin._index.tsx +57 -0
- package/dist/templates/app/routes/admin.teams.$teamId.tsx +57 -0
- package/dist/templates/app/routes/admin.teams._index.tsx +57 -0
- package/dist/templates/app/routes/admin.users.tsx +57 -0
- package/dist/templates/app/routes/api-keys.tsx +57 -0
- package/dist/templates/app/routes/automations.tsx +57 -0
- package/dist/templates/app/routes/chat._index.tsx +11 -0
- package/dist/templates/app/routes/chat.admin._index.tsx +10 -0
- package/dist/templates/app/routes/chat.admin.teams.$teamId.tsx +10 -0
- package/dist/templates/app/routes/chat.admin.teams._index.tsx +10 -0
- package/dist/templates/app/routes/chat.admin.users.tsx +10 -0
- package/dist/templates/app/routes/chat.api-keys.tsx +10 -0
- package/dist/templates/app/routes/chat.automations.tsx +10 -0
- package/dist/templates/app/routes/chat.documents.tsx +10 -0
- package/dist/templates/app/routes/chat.team.$teamId.settings.tsx +10 -0
- package/dist/templates/app/routes/chat.thread.$threadId.tsx +11 -0
- package/dist/templates/app/routes/chat.tsx +39 -0
- package/dist/templates/app/routes/documents.tsx +57 -0
- package/dist/templates/app/routes/invite.$token.tsx +10 -0
- package/dist/templates/app/routes/login.tsx +334 -0
- package/dist/templates/app/routes/oauth.consent.tsx +10 -0
- package/dist/templates/app/routes/pricing.tsx +10 -0
- package/dist/templates/app/routes/privacy.tsx +197 -0
- package/dist/templates/app/routes/register.tsx +398 -0
- package/dist/templates/app/routes/shared.$shareId.tsx +226 -0
- package/dist/templates/app/routes/team.$teamId.settings.tsx +57 -0
- package/dist/templates/app/routes/terms.tsx +173 -0
- package/dist/templates/app/routes/thread.$threadId.tsx +102 -0
- package/dist/templates/app/routes/verify-email.tsx +10 -0
- package/dist/templates/app/routes.ts +47 -0
- package/dist/templates/config/app.config.ts +216 -0
- package/dist/templates/docs/admin.md +257 -0
- package/dist/templates/docs/api-keys.md +403 -0
- package/dist/templates/docs/authentication.md +247 -0
- package/dist/templates/docs/configuration.md +1212 -0
- package/dist/templates/docs/custom-pages.md +466 -0
- package/dist/templates/docs/deployment.md +362 -0
- package/dist/templates/docs/development.md +411 -0
- package/dist/templates/docs/documents.md +293 -0
- package/dist/templates/docs/extensions.md +639 -0
- package/dist/templates/docs/index.md +139 -0
- package/dist/templates/docs/installation.md +286 -0
- package/dist/templates/docs/mcp.md +952 -0
- package/dist/templates/docs/native-tools.md +688 -0
- package/dist/templates/docs/queue.md +514 -0
- package/dist/templates/docs/scheduled-prompts.md +279 -0
- package/dist/templates/docs/settings.md +415 -0
- package/dist/templates/docs/slack.md +318 -0
- package/dist/templates/docs/styling.md +288 -0
- package/dist/templates/extensions/agents/.gitkeep +0 -0
- package/dist/templates/extensions/pages/.gitkeep +0 -0
- package/dist/templates/extensions/payment-plans/.gitkeep +0 -0
- package/dist/templates/index.html +16 -0
- package/dist/templates/infra-aws/.github/workflows/deploy.yml +95 -0
- package/dist/templates/infra-aws/README.md +207 -0
- package/dist/templates/infra-aws/bin/cdk.ts +18 -0
- package/dist/templates/infra-aws/cdk.json +43 -0
- package/dist/templates/infra-aws/config/deployment.ts +156 -0
- package/dist/templates/infra-aws/lib/chaaskit-stack.ts +419 -0
- package/dist/templates/infra-aws/package.json +27 -0
- package/dist/templates/infra-aws/scripts/build-app.sh +63 -0
- package/dist/templates/infra-aws/tsconfig.json +25 -0
- package/dist/templates/package.json +46 -0
- package/dist/templates/prisma/schema/base.prisma +584 -0
- package/dist/templates/prisma/schema/custom.prisma +24 -0
- package/dist/templates/prisma/schema.prisma +271 -0
- package/dist/templates/public/favicon.svg +4 -0
- package/dist/templates/public/logo.svg +4 -0
- package/dist/templates/react-router.config.ts +11 -0
- package/dist/templates/server.js +52 -0
- package/dist/templates/src/main.tsx +8 -0
- package/dist/templates/tsconfig.json +26 -0
- package/dist/templates/vite.config.ts +26 -0
- 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
|
+
```
|