@qwickapps/qwickbrain-proxy 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/.github/workflows/publish.yml +92 -0
  2. package/CHANGELOG.md +47 -0
  3. package/LICENSE +45 -0
  4. package/README.md +165 -0
  5. package/dist/bin/cli.d.ts +3 -0
  6. package/dist/bin/cli.d.ts.map +1 -0
  7. package/dist/bin/cli.js +142 -0
  8. package/dist/bin/cli.js.map +1 -0
  9. package/dist/db/client.d.ts +10 -0
  10. package/dist/db/client.d.ts.map +1 -0
  11. package/dist/db/client.js +23 -0
  12. package/dist/db/client.js.map +1 -0
  13. package/dist/db/schema.d.ts +551 -0
  14. package/dist/db/schema.d.ts.map +1 -0
  15. package/dist/db/schema.js +65 -0
  16. package/dist/db/schema.js.map +1 -0
  17. package/dist/index.d.ts +7 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +5 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/lib/__tests__/cache-manager.test.d.ts +2 -0
  22. package/dist/lib/__tests__/cache-manager.test.d.ts.map +1 -0
  23. package/dist/lib/__tests__/cache-manager.test.js +202 -0
  24. package/dist/lib/__tests__/cache-manager.test.js.map +1 -0
  25. package/dist/lib/__tests__/connection-manager.test.d.ts +2 -0
  26. package/dist/lib/__tests__/connection-manager.test.d.ts.map +1 -0
  27. package/dist/lib/__tests__/connection-manager.test.js +188 -0
  28. package/dist/lib/__tests__/connection-manager.test.js.map +1 -0
  29. package/dist/lib/__tests__/proxy-server.test.d.ts +2 -0
  30. package/dist/lib/__tests__/proxy-server.test.d.ts.map +1 -0
  31. package/dist/lib/__tests__/proxy-server.test.js +205 -0
  32. package/dist/lib/__tests__/proxy-server.test.js.map +1 -0
  33. package/dist/lib/__tests__/qwickbrain-client.test.d.ts +2 -0
  34. package/dist/lib/__tests__/qwickbrain-client.test.d.ts.map +1 -0
  35. package/dist/lib/__tests__/qwickbrain-client.test.js +233 -0
  36. package/dist/lib/__tests__/qwickbrain-client.test.js.map +1 -0
  37. package/dist/lib/cache-manager.d.ts +25 -0
  38. package/dist/lib/cache-manager.d.ts.map +1 -0
  39. package/dist/lib/cache-manager.js +149 -0
  40. package/dist/lib/cache-manager.js.map +1 -0
  41. package/dist/lib/connection-manager.d.ts +26 -0
  42. package/dist/lib/connection-manager.d.ts.map +1 -0
  43. package/dist/lib/connection-manager.js +130 -0
  44. package/dist/lib/connection-manager.js.map +1 -0
  45. package/dist/lib/proxy-server.d.ts +19 -0
  46. package/dist/lib/proxy-server.d.ts.map +1 -0
  47. package/dist/lib/proxy-server.js +258 -0
  48. package/dist/lib/proxy-server.js.map +1 -0
  49. package/dist/lib/qwickbrain-client.d.ts +24 -0
  50. package/dist/lib/qwickbrain-client.d.ts.map +1 -0
  51. package/dist/lib/qwickbrain-client.js +197 -0
  52. package/dist/lib/qwickbrain-client.js.map +1 -0
  53. package/dist/types/config.d.ts +186 -0
  54. package/dist/types/config.d.ts.map +1 -0
  55. package/dist/types/config.js +42 -0
  56. package/dist/types/config.js.map +1 -0
  57. package/dist/types/mcp.d.ts +223 -0
  58. package/dist/types/mcp.d.ts.map +1 -0
  59. package/dist/types/mcp.js +78 -0
  60. package/dist/types/mcp.js.map +1 -0
  61. package/dist/version.d.ts +2 -0
  62. package/dist/version.d.ts.map +1 -0
  63. package/dist/version.js +9 -0
  64. package/dist/version.js.map +1 -0
  65. package/drizzle/0000_fat_rafael_vega.sql +41 -0
  66. package/drizzle/0001_goofy_invisible_woman.sql +2 -0
  67. package/drizzle/meta/0000_snapshot.json +276 -0
  68. package/drizzle/meta/0001_snapshot.json +295 -0
  69. package/drizzle/meta/_journal.json +20 -0
  70. package/drizzle.config.ts +12 -0
  71. package/package.json +65 -0
  72. package/src/bin/cli.ts +158 -0
  73. package/src/db/client.ts +34 -0
  74. package/src/db/schema.ts +68 -0
  75. package/src/index.ts +6 -0
  76. package/src/lib/__tests__/cache-manager.test.ts +264 -0
  77. package/src/lib/__tests__/connection-manager.test.ts +255 -0
  78. package/src/lib/__tests__/proxy-server.test.ts +261 -0
  79. package/src/lib/__tests__/qwickbrain-client.test.ts +310 -0
  80. package/src/lib/cache-manager.ts +201 -0
  81. package/src/lib/connection-manager.ts +156 -0
  82. package/src/lib/proxy-server.ts +320 -0
  83. package/src/lib/qwickbrain-client.ts +260 -0
  84. package/src/types/config.ts +47 -0
  85. package/src/types/mcp.ts +97 -0
  86. package/src/version.ts +11 -0
  87. package/test/fixtures/test-mcp.json +5 -0
  88. package/test-mcp-client.js +67 -0
  89. package/test-proxy.sh +25 -0
  90. package/tsconfig.json +22 -0
@@ -0,0 +1,320 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ } from '@modelcontextprotocol/sdk/types.js';
7
+ import { ConnectionManager } from './connection-manager.js';
8
+ import { CacheManager } from './cache-manager.js';
9
+ import { QwickBrainClient } from './qwickbrain-client.js';
10
+ import type { Config } from '../types/config.js';
11
+ import type { DB } from '../db/client.js';
12
+ import type { MCPResponse, MCPResponseMetadata } from '../types/mcp.js';
13
+ import { VERSION } from '../version.js';
14
+
15
+ export class ProxyServer {
16
+ private server: Server;
17
+ private connectionManager: ConnectionManager;
18
+ private cacheManager: CacheManager;
19
+ private qwickbrainClient: QwickBrainClient;
20
+ private config: Config;
21
+
22
+ constructor(db: DB, config: Config) {
23
+ this.config = config;
24
+ this.server = new Server(
25
+ {
26
+ name: 'qwickbrain-proxy',
27
+ version: VERSION,
28
+ },
29
+ {
30
+ capabilities: {
31
+ tools: {},
32
+ },
33
+ }
34
+ );
35
+
36
+ this.qwickbrainClient = new QwickBrainClient(config.qwickbrain);
37
+
38
+ this.connectionManager = new ConnectionManager(
39
+ this.qwickbrainClient,
40
+ config.connection
41
+ );
42
+
43
+ this.cacheManager = new CacheManager(db, config.cache);
44
+
45
+ this.setupHandlers();
46
+ this.setupConnectionListeners();
47
+ }
48
+
49
+ private setupConnectionListeners(): void {
50
+ this.connectionManager.on('stateChange', ({ from, to }) => {
51
+ console.error(`Connection state: ${from} → ${to}`);
52
+ });
53
+
54
+ this.connectionManager.on('reconnecting', ({ attempt, delay }) => {
55
+ console.error(`Reconnecting (attempt ${attempt}, delay ${delay}ms)...`);
56
+ });
57
+
58
+ this.connectionManager.on('connected', ({ latencyMs }) => {
59
+ console.error(`Connected to QwickBrain (latency: ${latencyMs}ms)`);
60
+ });
61
+
62
+ this.connectionManager.on('disconnected', ({ error }) => {
63
+ console.error(`Disconnected from QwickBrain: ${error}`);
64
+ });
65
+ }
66
+
67
+ private setupHandlers(): void {
68
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
69
+ tools: [
70
+ {
71
+ name: 'get_workflow',
72
+ description: 'Get a workflow definition by name',
73
+ inputSchema: {
74
+ type: 'object',
75
+ properties: {
76
+ name: { type: 'string', description: 'Workflow name' },
77
+ },
78
+ required: ['name'],
79
+ },
80
+ },
81
+ {
82
+ name: 'get_document',
83
+ description: 'Get a document by name and type',
84
+ inputSchema: {
85
+ type: 'object',
86
+ properties: {
87
+ name: { type: 'string', description: 'Document name' },
88
+ doc_type: { type: 'string', description: 'Document type (rule, frd, design, etc.)' },
89
+ project: { type: 'string', description: 'Project name (optional)' },
90
+ },
91
+ required: ['name', 'doc_type'],
92
+ },
93
+ },
94
+ {
95
+ name: 'get_memory',
96
+ description: 'Get a memory/context document by name',
97
+ inputSchema: {
98
+ type: 'object',
99
+ properties: {
100
+ name: { type: 'string', description: 'Memory name' },
101
+ project: { type: 'string', description: 'Project name (optional)' },
102
+ },
103
+ required: ['name'],
104
+ },
105
+ },
106
+ ],
107
+ }));
108
+
109
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
110
+ const { name, arguments: args } = request.params;
111
+
112
+ try {
113
+ let result: MCPResponse;
114
+
115
+ switch (name) {
116
+ case 'get_workflow':
117
+ result = await this.handleGetWorkflow(args?.name as string);
118
+ break;
119
+ case 'get_document':
120
+ result = await this.handleGetDocument(
121
+ args?.doc_type as string,
122
+ args?.name as string,
123
+ args?.project as string | undefined
124
+ );
125
+ break;
126
+ case 'get_memory':
127
+ result = await this.handleGetMemory(
128
+ args?.name as string,
129
+ args?.project as string | undefined
130
+ );
131
+ break;
132
+ default:
133
+ throw new Error(`Unknown tool: ${name}`);
134
+ }
135
+
136
+ return {
137
+ content: [
138
+ {
139
+ type: 'text',
140
+ text: JSON.stringify(result, null, 2),
141
+ },
142
+ ],
143
+ };
144
+ } catch (error) {
145
+ const errorMessage = error instanceof Error ? error.message : String(error);
146
+ return {
147
+ content: [
148
+ {
149
+ type: 'text',
150
+ text: JSON.stringify({
151
+ error: {
152
+ code: 'TOOL_ERROR',
153
+ message: errorMessage,
154
+ },
155
+ _metadata: {
156
+ source: 'cache',
157
+ status: this.connectionManager.getState(),
158
+ },
159
+ }, null, 2),
160
+ },
161
+ ],
162
+ isError: true,
163
+ };
164
+ }
165
+ });
166
+ }
167
+
168
+ private createMetadata(source: 'live' | 'cache' | 'stale_cache', age?: number): MCPResponseMetadata {
169
+ return {
170
+ source,
171
+ age_seconds: age,
172
+ status: this.connectionManager.getState(),
173
+ };
174
+ }
175
+
176
+ private async handleGetWorkflow(name: string): Promise<MCPResponse> {
177
+ return this.handleGetDocument('workflow', name);
178
+ }
179
+
180
+ private async handleGetDocument(
181
+ docType: string,
182
+ name: string,
183
+ project?: string
184
+ ): Promise<MCPResponse> {
185
+ // Try cache first
186
+ const cached = await this.cacheManager.getDocument(docType, name, project);
187
+
188
+ if (cached && !cached.isExpired) {
189
+ return {
190
+ data: cached.data,
191
+ _metadata: this.createMetadata('cache', cached.age),
192
+ };
193
+ }
194
+
195
+ // Try remote if connected
196
+ if (this.connectionManager.getState() === 'connected') {
197
+ try {
198
+ const result = await this.connectionManager.execute(async () => {
199
+ return await this.qwickbrainClient.getDocument(docType, name, project);
200
+ });
201
+
202
+ // Cache the result
203
+ await this.cacheManager.setDocument(
204
+ docType,
205
+ name,
206
+ result.content,
207
+ project,
208
+ result.metadata
209
+ );
210
+
211
+ return {
212
+ data: result,
213
+ _metadata: this.createMetadata('live'),
214
+ };
215
+ } catch (error) {
216
+ console.error('Failed to fetch from QwickBrain:', error);
217
+ // Fall through to stale cache
218
+ }
219
+ }
220
+
221
+ // Try stale cache
222
+ if (cached) {
223
+ return {
224
+ data: cached.data,
225
+ _metadata: {
226
+ ...this.createMetadata('stale_cache', cached.age),
227
+ warning: `QwickBrain unavailable - serving cached data (${cached.age}s old)`,
228
+ },
229
+ };
230
+ }
231
+
232
+ // No cache, no connection
233
+ return {
234
+ error: {
235
+ code: 'UNAVAILABLE',
236
+ message: `QwickBrain unavailable and no cached data for ${docType}:${name}`,
237
+ suggestions: [
238
+ 'Check internet connection',
239
+ 'Wait for automatic reconnection',
240
+ docType === 'workflow' ? 'Try /plan command as fallback' : undefined,
241
+ ].filter(Boolean) as string[],
242
+ },
243
+ _metadata: this.createMetadata('cache'),
244
+ };
245
+ }
246
+
247
+ private async handleGetMemory(name: string, project?: string): Promise<MCPResponse> {
248
+ // Similar logic to handleGetDocument but for memories
249
+ const cached = await this.cacheManager.getMemory(name, project);
250
+
251
+ if (cached && !cached.isExpired) {
252
+ return {
253
+ data: cached.data,
254
+ _metadata: this.createMetadata('cache', cached.age),
255
+ };
256
+ }
257
+
258
+ if (this.connectionManager.getState() === 'connected') {
259
+ try {
260
+ const result = await this.connectionManager.execute(async () => {
261
+ return await this.qwickbrainClient.getMemory(name, project);
262
+ });
263
+
264
+ await this.cacheManager.setMemory(name, result.content, project, result.metadata);
265
+
266
+ return {
267
+ data: result,
268
+ _metadata: this.createMetadata('live'),
269
+ };
270
+ } catch (error) {
271
+ console.error('Failed to fetch memory from QwickBrain:', error);
272
+ }
273
+ }
274
+
275
+ if (cached) {
276
+ return {
277
+ data: cached.data,
278
+ _metadata: {
279
+ ...this.createMetadata('stale_cache', cached.age),
280
+ warning: `QwickBrain unavailable - serving cached memory (${cached.age}s old)`,
281
+ },
282
+ };
283
+ }
284
+
285
+ return {
286
+ error: {
287
+ code: 'UNAVAILABLE',
288
+ message: `QwickBrain unavailable and no cached memory: ${name}`,
289
+ suggestions: ['Check connection', 'Wait for reconnection'],
290
+ },
291
+ _metadata: this.createMetadata('cache'),
292
+ };
293
+ }
294
+
295
+ async start(): Promise<void> {
296
+ // Clean up expired cache entries on startup
297
+ const { documentsDeleted, memoriesDeleted } = await this.cacheManager.cleanupExpiredEntries();
298
+ if (documentsDeleted > 0 || memoriesDeleted > 0) {
299
+ console.error(`Cache cleanup: removed ${documentsDeleted} documents, ${memoriesDeleted} memories`);
300
+ }
301
+
302
+ // Connect to QwickBrain
303
+ await this.qwickbrainClient.connect();
304
+
305
+ // Start connection manager
306
+ await this.connectionManager.start();
307
+
308
+ // Start MCP server
309
+ const transport = new StdioServerTransport();
310
+ await this.server.connect(transport);
311
+
312
+ console.error('QwickBrain Proxy started');
313
+ }
314
+
315
+ async stop(): Promise<void> {
316
+ this.connectionManager.stop();
317
+ await this.qwickbrainClient.disconnect();
318
+ await this.server.close();
319
+ }
320
+ }
@@ -0,0 +1,260 @@
1
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
+ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
4
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
5
+ import type { Config } from '../types/config.js';
6
+ import { z } from 'zod';
7
+ import { VERSION } from '../version.js';
8
+
9
+ // Zod schema for MCP tool response validation
10
+ const MCPToolResponseSchema = z.object({
11
+ content: z.array(
12
+ z.object({
13
+ type: z.literal('text'),
14
+ text: z.string(),
15
+ })
16
+ ),
17
+ });
18
+
19
+ // Zod schema for the document/memory data inside the MCP response
20
+ const QwickBrainDocumentSchema = z.object({
21
+ document: z
22
+ .object({
23
+ content: z.string(),
24
+ metadata: z.record(z.unknown()).optional(),
25
+ })
26
+ .optional(),
27
+ });
28
+
29
+ // Zod schema for HTTP API responses
30
+ const HTTPResponseSchema = z.object({
31
+ content: z.string(),
32
+ metadata: z.record(z.unknown()).optional(),
33
+ });
34
+
35
+ export interface QwickBrainResponse {
36
+ content: string;
37
+ metadata?: Record<string, unknown>;
38
+ }
39
+
40
+ export class QwickBrainClient {
41
+ private client: Client | null = null;
42
+ private transport: Transport | null = null;
43
+ private mode: 'mcp' | 'http' | 'sse';
44
+ private config: Config['qwickbrain'];
45
+
46
+ constructor(config: Config['qwickbrain']) {
47
+ this.mode = config.mode;
48
+ this.config = config;
49
+ }
50
+
51
+ async connect(): Promise<void> {
52
+ if (this.mode === 'mcp') {
53
+ await this.connectMCP();
54
+ } else if (this.mode === 'sse') {
55
+ await this.connectSSE();
56
+ }
57
+ // HTTP mode doesn't need persistent connection
58
+ }
59
+
60
+ private async connectMCP(): Promise<void> {
61
+ if (!this.config.command) {
62
+ throw new Error('MCP mode requires command to be configured');
63
+ }
64
+
65
+ this.client = new Client(
66
+ {
67
+ name: 'qwickbrain-proxy',
68
+ version: VERSION,
69
+ },
70
+ {
71
+ capabilities: {},
72
+ }
73
+ );
74
+
75
+ this.transport = new StdioClientTransport({
76
+ command: this.config.command,
77
+ args: this.config.args || [],
78
+ });
79
+
80
+ await this.client.connect(this.transport);
81
+ }
82
+
83
+ private async connectSSE(): Promise<void> {
84
+ if (!this.config.url) {
85
+ throw new Error('SSE mode requires url to be configured');
86
+ }
87
+
88
+ this.client = new Client(
89
+ {
90
+ name: 'qwickbrain-proxy',
91
+ version: VERSION,
92
+ },
93
+ {
94
+ capabilities: {},
95
+ }
96
+ );
97
+
98
+ this.transport = new SSEClientTransport(
99
+ new URL(this.config.url)
100
+ );
101
+
102
+ await this.client.connect(this.transport);
103
+ }
104
+
105
+ async getDocument(
106
+ docType: string,
107
+ name: string,
108
+ project?: string
109
+ ): Promise<QwickBrainResponse> {
110
+ if (this.mode === 'mcp' || this.mode === 'sse') {
111
+ return this.getDocumentMCP(docType, name, project);
112
+ } else {
113
+ return this.getDocumentHTTP(docType, name, project);
114
+ }
115
+ }
116
+
117
+ private async getDocumentMCP(
118
+ docType: string,
119
+ name: string,
120
+ project?: string
121
+ ): Promise<QwickBrainResponse> {
122
+ if (!this.client) {
123
+ throw new Error('MCP client not connected');
124
+ }
125
+
126
+ const result = await this.client.callTool({
127
+ name: 'get_document',
128
+ arguments: {
129
+ doc_type: docType,
130
+ name,
131
+ project,
132
+ },
133
+ });
134
+
135
+ // Validate MCP response structure
136
+ const parsed = MCPToolResponseSchema.parse(result);
137
+ const data = QwickBrainDocumentSchema.parse(JSON.parse(parsed.content[0].text));
138
+
139
+ return {
140
+ content: data.document?.content || '',
141
+ metadata: data.document?.metadata,
142
+ };
143
+ }
144
+
145
+ private async getDocumentHTTP(
146
+ docType: string,
147
+ name: string,
148
+ project?: string
149
+ ): Promise<QwickBrainResponse> {
150
+ if (!this.config.url) {
151
+ throw new Error('HTTP mode requires url to be configured');
152
+ }
153
+
154
+ const response = await fetch(`${this.config.url}/mcp/document`, {
155
+ method: 'POST',
156
+ headers: {
157
+ 'Content-Type': 'application/json',
158
+ ...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
159
+ },
160
+ body: JSON.stringify({ doc_type: docType, name, project }),
161
+ });
162
+
163
+ if (!response.ok) {
164
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
165
+ }
166
+
167
+ const json = await response.json();
168
+ return HTTPResponseSchema.parse(json);
169
+ }
170
+
171
+ async getMemory(name: string, project?: string): Promise<QwickBrainResponse> {
172
+ if (this.mode === 'mcp' || this.mode === 'sse') {
173
+ return this.getMemoryMCP(name, project);
174
+ } else {
175
+ return this.getMemoryHTTP(name, project);
176
+ }
177
+ }
178
+
179
+ private async getMemoryMCP(
180
+ name: string,
181
+ project?: string
182
+ ): Promise<QwickBrainResponse> {
183
+ if (!this.client) {
184
+ throw new Error('MCP client not connected');
185
+ }
186
+
187
+ const result = await this.client.callTool({
188
+ name: 'get_memory',
189
+ arguments: {
190
+ name,
191
+ project,
192
+ },
193
+ });
194
+
195
+ // Validate MCP response structure
196
+ const parsed = MCPToolResponseSchema.parse(result);
197
+ const data = QwickBrainDocumentSchema.parse(JSON.parse(parsed.content[0].text));
198
+
199
+ return {
200
+ content: data.document?.content || '',
201
+ metadata: data.document?.metadata,
202
+ };
203
+ }
204
+
205
+ private async getMemoryHTTP(
206
+ name: string,
207
+ project?: string
208
+ ): Promise<QwickBrainResponse> {
209
+ if (!this.config.url) {
210
+ throw new Error('HTTP mode requires url to be configured');
211
+ }
212
+
213
+ const response = await fetch(`${this.config.url}/mcp/memory`, {
214
+ method: 'POST',
215
+ headers: {
216
+ 'Content-Type': 'application/json',
217
+ ...(this.config.apiKey && { 'Authorization': `Bearer ${this.config.apiKey}` }),
218
+ },
219
+ body: JSON.stringify({ name, project }),
220
+ });
221
+
222
+ if (!response.ok) {
223
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
224
+ }
225
+
226
+ const json = await response.json();
227
+ return HTTPResponseSchema.parse(json);
228
+ }
229
+
230
+ async healthCheck(): Promise<boolean> {
231
+ try {
232
+ if (this.mode === 'mcp' || this.mode === 'sse') {
233
+ // For MCP/SSE mode, check if client is connected
234
+ if (!this.client) {
235
+ await this.connect();
236
+ }
237
+ // Try listing tools as health check
238
+ await this.client!.listTools();
239
+ return true;
240
+ } else {
241
+ // For HTTP mode, ping health endpoint
242
+ if (!this.config.url) {
243
+ return false;
244
+ }
245
+ const response = await fetch(`${this.config.url}/health`);
246
+ return response.ok;
247
+ }
248
+ } catch (error) {
249
+ return false;
250
+ }
251
+ }
252
+
253
+ async disconnect(): Promise<void> {
254
+ if ((this.mode === 'mcp' || this.mode === 'sse') && this.client && this.transport) {
255
+ await this.client.close();
256
+ this.client = null;
257
+ this.transport = null;
258
+ }
259
+ }
260
+ }
@@ -0,0 +1,47 @@
1
+ import { z } from 'zod';
2
+
3
+ export const ConfigSchema = z.object({
4
+ qwickbrain: z.object({
5
+ mode: z.enum(['mcp', 'http', 'sse']).default('sse'),
6
+ // For MCP mode (local stdio)
7
+ command: z.string().optional(),
8
+ args: z.array(z.string()).optional(),
9
+ // For HTTP mode (cloud/remote) or SSE mode
10
+ url: z.string().url().optional(),
11
+ apiKey: z.string().optional(),
12
+ }).default({}),
13
+ cache: z.object({
14
+ dir: z.string().optional(),
15
+ ttl: z.object({
16
+ workflows: z.number().default(86400), // 24 hours
17
+ rules: z.number().default(86400), // 24 hours
18
+ documents: z.number().default(21600), // 6 hours
19
+ memories: z.number().default(3600), // 1 hour
20
+ }).default({}),
21
+ preload: z.array(z.string()).default(['workflows', 'rules']),
22
+ }).default({}),
23
+ connection: z.object({
24
+ healthCheckInterval: z.number().default(30000), // 30 seconds
25
+ timeout: z.number().default(5000), // 5 seconds
26
+ maxReconnectAttempts: z.number().default(10),
27
+ reconnectBackoff: z.object({
28
+ initial: z.number().default(1000), // 1 second
29
+ max: z.number().default(60000), // 60 seconds
30
+ multiplier: z.number().default(2),
31
+ }).default({}),
32
+ }).default({}),
33
+ sync: z.object({
34
+ interval: z.number().default(300000), // 5 minutes
35
+ batchSize: z.number().default(10),
36
+ }).default({}),
37
+ });
38
+
39
+ export type Config = z.infer<typeof ConfigSchema>;
40
+
41
+ export const CacheConfigSchema = z.object({
42
+ ttl: z.number(),
43
+ offline: z.boolean(),
44
+ preload: z.boolean(),
45
+ });
46
+
47
+ export type CacheConfig = z.infer<typeof CacheConfigSchema>;