beecork 1.4.11 → 1.6.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 (138) hide show
  1. package/dist/capabilities/index.d.ts +1 -1
  2. package/dist/capabilities/index.js +1 -1
  3. package/dist/capabilities/manager.js +13 -9
  4. package/dist/capabilities/packs.js +3 -1
  5. package/dist/channels/admin.d.ts +10 -0
  6. package/dist/channels/admin.js +20 -0
  7. package/dist/channels/command-handler.d.ts +2 -10
  8. package/dist/channels/command-handler.js +90 -84
  9. package/dist/channels/discord.d.ts +4 -9
  10. package/dist/channels/discord.js +59 -42
  11. package/dist/channels/index.d.ts +1 -1
  12. package/dist/channels/loader.js +13 -4
  13. package/dist/channels/pipeline.js +14 -5
  14. package/dist/channels/registry.d.ts +17 -1
  15. package/dist/channels/registry.js +33 -4
  16. package/dist/channels/send-helpers.d.ts +19 -0
  17. package/dist/channels/send-helpers.js +21 -0
  18. package/dist/channels/telegram.d.ts +21 -14
  19. package/dist/channels/telegram.js +214 -104
  20. package/dist/channels/types.d.ts +13 -38
  21. package/dist/channels/voice-state.d.ts +29 -0
  22. package/dist/channels/voice-state.js +45 -0
  23. package/dist/channels/webhook.d.ts +2 -5
  24. package/dist/channels/webhook.js +88 -29
  25. package/dist/channels/whatsapp.d.ts +9 -7
  26. package/dist/channels/whatsapp.js +141 -100
  27. package/dist/cli/capabilities.js +4 -4
  28. package/dist/cli/channel.js +16 -6
  29. package/dist/cli/commands.js +12 -9
  30. package/dist/cli/doctor.js +85 -27
  31. package/dist/cli/handoff.d.ts +7 -14
  32. package/dist/cli/handoff.js +9 -44
  33. package/dist/cli/mcp.js +5 -5
  34. package/dist/cli/media.js +21 -8
  35. package/dist/cli/setup.js +9 -8
  36. package/dist/cli/store.js +29 -12
  37. package/dist/config.d.ts +5 -1
  38. package/dist/config.js +20 -22
  39. package/dist/daemon.js +113 -51
  40. package/dist/dashboard/html.js +100 -20
  41. package/dist/dashboard/routes.d.ts +17 -0
  42. package/dist/dashboard/routes.js +623 -0
  43. package/dist/dashboard/server.js +38 -489
  44. package/dist/db/connection.d.ts +29 -0
  45. package/dist/db/connection.js +37 -0
  46. package/dist/db/index.js +43 -11
  47. package/dist/db/migrations.js +114 -22
  48. package/dist/delegation/manager.js +10 -4
  49. package/dist/index.js +39 -59
  50. package/dist/knowledge/manager.js +26 -12
  51. package/dist/mcp/handlers.d.ts +37 -0
  52. package/dist/mcp/handlers.js +520 -0
  53. package/dist/mcp/server.js +44 -858
  54. package/dist/mcp/tool-definitions.d.ts +1225 -0
  55. package/dist/mcp/tool-definitions.js +412 -0
  56. package/dist/mcp/validate.d.ts +23 -0
  57. package/dist/mcp/validate.js +65 -0
  58. package/dist/media/factory.js +18 -14
  59. package/dist/media/generators/dall-e.js +2 -2
  60. package/dist/media/generators/kling.js +4 -4
  61. package/dist/media/generators/lyria.js +1 -1
  62. package/dist/media/generators/nano-banana.d.ts +1 -1
  63. package/dist/media/generators/nano-banana.js +2 -2
  64. package/dist/media/generators/poll-util.js +4 -4
  65. package/dist/media/generators/recraft.js +3 -3
  66. package/dist/media/generators/runway.js +4 -4
  67. package/dist/media/generators/stable-diffusion.js +2 -2
  68. package/dist/media/generators/veo.js +1 -1
  69. package/dist/media/index.d.ts +2 -7
  70. package/dist/media/index.js +2 -2
  71. package/dist/media/store.d.ts +7 -0
  72. package/dist/media/store.js +18 -4
  73. package/dist/media/types.d.ts +22 -0
  74. package/dist/notifications/index.d.ts +2 -4
  75. package/dist/notifications/index.js +6 -19
  76. package/dist/notifications/ntfy.js +3 -3
  77. package/dist/observability/analytics.d.ts +1 -1
  78. package/dist/observability/analytics.js +41 -16
  79. package/dist/projects/index.d.ts +3 -2
  80. package/dist/projects/index.js +2 -2
  81. package/dist/projects/manager.d.ts +1 -7
  82. package/dist/projects/manager.js +66 -42
  83. package/dist/projects/router.d.ts +12 -0
  84. package/dist/projects/router.js +98 -45
  85. package/dist/service/install.js +15 -5
  86. package/dist/service/windows.js +1 -1
  87. package/dist/session/budget-guard.d.ts +20 -0
  88. package/dist/session/budget-guard.js +31 -0
  89. package/dist/session/circuit-breaker.d.ts +5 -3
  90. package/dist/session/circuit-breaker.js +45 -20
  91. package/dist/session/context-compactor.d.ts +32 -0
  92. package/dist/session/context-compactor.js +45 -0
  93. package/dist/session/context-monitor.js +2 -2
  94. package/dist/session/handoff.d.ts +21 -0
  95. package/dist/session/handoff.js +50 -0
  96. package/dist/session/manager.d.ts +21 -5
  97. package/dist/session/manager.js +166 -153
  98. package/dist/session/memory-store.d.ts +29 -0
  99. package/dist/session/memory-store.js +45 -0
  100. package/dist/session/message-queue.d.ts +28 -0
  101. package/dist/session/message-queue.js +52 -0
  102. package/dist/session/pending-dispatcher.d.ts +31 -0
  103. package/dist/session/pending-dispatcher.js +120 -0
  104. package/dist/session/pending-store.d.ts +60 -0
  105. package/dist/session/pending-store.js +118 -0
  106. package/dist/session/stale-session.d.ts +31 -0
  107. package/dist/session/stale-session.js +45 -0
  108. package/dist/session/subprocess.d.ts +3 -0
  109. package/dist/session/subprocess.js +54 -11
  110. package/dist/session/tab-store.d.ts +28 -0
  111. package/dist/session/tab-store.js +78 -0
  112. package/dist/tasks/scheduler.d.ts +13 -0
  113. package/dist/tasks/scheduler.js +97 -18
  114. package/dist/tasks/store.js +26 -12
  115. package/dist/timeline/logger.js +3 -1
  116. package/dist/timeline/query.js +15 -5
  117. package/dist/types.d.ts +49 -9
  118. package/dist/util/auto-heal.js +15 -5
  119. package/dist/util/install-info.js +3 -1
  120. package/dist/util/logger.d.ts +1 -1
  121. package/dist/util/logger.js +63 -24
  122. package/dist/util/paths.d.ts +2 -0
  123. package/dist/util/paths.js +16 -3
  124. package/dist/util/rate-limiter.js +8 -0
  125. package/dist/util/retry.js +1 -1
  126. package/dist/util/text.d.ts +21 -1
  127. package/dist/util/text.js +38 -8
  128. package/dist/voice/index.js +5 -1
  129. package/dist/voice/stt.js +14 -6
  130. package/dist/voice/tts.js +1 -1
  131. package/dist/watchers/scheduler.js +11 -5
  132. package/package.json +6 -1
  133. package/dist/session/tool-classifier.d.ts +0 -4
  134. package/dist/session/tool-classifier.js +0 -56
  135. package/dist/users/index.d.ts +0 -2
  136. package/dist/users/index.js +0 -1
  137. package/dist/users/service.d.ts +0 -17
  138. package/dist/users/service.js +0 -46
@@ -0,0 +1,412 @@
1
+ // MCP tool definitions for the Beecork MCP server.
2
+ // One array, one place to maintain the public tool surface. Handler logic
3
+ // lives in `./handlers.ts`; the server file (`./server.ts`) glues them.
4
+ export const TOOL_DEFINITIONS = [
5
+ {
6
+ name: 'beecork_remember',
7
+ description: "Store a fact in Beecork's long-term memory. Use this for preferences, decisions, server addresses, outcomes, or anything the user might want recalled in future sessions.",
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ content: { type: 'string', description: 'The fact or information to remember' },
12
+ scope: {
13
+ type: 'string',
14
+ enum: ['global', 'project', 'tab', 'auto'],
15
+ description: 'Where to store: global (about the user), project (about this folder), tab (about this conversation), or auto (Claude decides)',
16
+ },
17
+ category: {
18
+ type: 'string',
19
+ description: 'For global scope: people, preferences, routines, or general',
20
+ },
21
+ },
22
+ required: ['content'],
23
+ },
24
+ },
25
+ {
26
+ name: 'beecork_task_create',
27
+ description: 'Schedule a task that will run automatically. The task sends a message to a Beecork tab at the scheduled time.',
28
+ inputSchema: {
29
+ type: 'object',
30
+ properties: {
31
+ name: { type: 'string', description: 'Human-readable name for the task' },
32
+ scheduleType: {
33
+ type: 'string',
34
+ enum: ['at', 'every', 'cron'],
35
+ description: '"at" = one-time ISO datetime, "every" = interval like "30m"/"2h"/"1d", "cron" = cron expression like "0 9 * * 1"',
36
+ },
37
+ schedule: {
38
+ type: 'string',
39
+ description: 'The schedule value (ISO datetime, interval, or cron expression)',
40
+ },
41
+ message: { type: 'string', description: 'The prompt/message to send when the task fires' },
42
+ tabName: {
43
+ type: 'string',
44
+ description: 'Which tab to send the message to (default: "default")',
45
+ },
46
+ },
47
+ required: ['name', 'scheduleType', 'schedule', 'message'],
48
+ },
49
+ },
50
+ {
51
+ name: 'beecork_task_list',
52
+ description: 'List all scheduled tasks.',
53
+ inputSchema: { type: 'object', properties: {} },
54
+ },
55
+ {
56
+ name: 'beecork_task_delete',
57
+ description: 'Delete a scheduled task by ID.',
58
+ inputSchema: {
59
+ type: 'object',
60
+ properties: { id: { type: 'string', description: 'The ID of the task to delete' } },
61
+ required: ['id'],
62
+ },
63
+ },
64
+ {
65
+ name: 'beecork_cron_create',
66
+ description: '[DEPRECATED — use beecork_task_create] Schedule a task that will run automatically.',
67
+ inputSchema: {
68
+ type: 'object',
69
+ properties: {
70
+ name: { type: 'string', description: 'Human-readable name for the job' },
71
+ scheduleType: { type: 'string', enum: ['at', 'every', 'cron'] },
72
+ schedule: { type: 'string' },
73
+ message: { type: 'string' },
74
+ tabName: { type: 'string' },
75
+ },
76
+ required: ['name', 'scheduleType', 'schedule', 'message'],
77
+ },
78
+ },
79
+ {
80
+ name: 'beecork_cron_list',
81
+ description: '[DEPRECATED — use beecork_task_list] List all scheduled tasks.',
82
+ inputSchema: { type: 'object', properties: {} },
83
+ },
84
+ {
85
+ name: 'beecork_cron_delete',
86
+ description: '[DEPRECATED — use beecork_task_delete] Delete a scheduled task by ID.',
87
+ inputSchema: {
88
+ type: 'object',
89
+ properties: { id: { type: 'string' } },
90
+ required: ['id'],
91
+ },
92
+ },
93
+ {
94
+ name: 'beecork_watch_create',
95
+ description: 'Create a watcher that periodically runs a check command and triggers an action when a condition is met.',
96
+ inputSchema: {
97
+ type: 'object',
98
+ properties: {
99
+ name: { type: 'string', description: 'Human-readable name for the watcher' },
100
+ description: { type: 'string', description: 'What to watch (natural language)' },
101
+ checkCommand: { type: 'string', description: 'Shell command to run for checking' },
102
+ condition: {
103
+ type: 'string',
104
+ description: 'When to trigger: "contains X", "not contains X", "> N", "< N", "any", "error"',
105
+ },
106
+ action: {
107
+ type: 'string',
108
+ enum: ['notify', 'fix', 'delegate'],
109
+ description: 'What to do when triggered (default: notify)',
110
+ },
111
+ actionDetails: {
112
+ type: 'string',
113
+ description: 'For fix: command to run. For delegate: tab name + message.',
114
+ },
115
+ schedule: {
116
+ type: 'string',
117
+ description: 'How often to check: cron expression or interval like "5m", "1h"',
118
+ },
119
+ },
120
+ required: ['name', 'checkCommand', 'condition', 'schedule'],
121
+ },
122
+ },
123
+ {
124
+ name: 'beecork_watch_list',
125
+ description: 'List all watchers.',
126
+ inputSchema: { type: 'object', properties: {} },
127
+ },
128
+ {
129
+ name: 'beecork_watch_delete',
130
+ description: 'Delete a watcher by ID.',
131
+ inputSchema: {
132
+ type: 'object',
133
+ properties: { id: { type: 'string' } },
134
+ required: ['id'],
135
+ },
136
+ },
137
+ {
138
+ name: 'beecork_tab_create',
139
+ description: 'Create a new Beecork tab (an isolated Claude session with its own working directory and history).',
140
+ inputSchema: {
141
+ type: 'object',
142
+ properties: {
143
+ name: {
144
+ type: 'string',
145
+ description: 'Tab name (alphanumeric + hyphens, max 32 chars). Cannot be "default".',
146
+ },
147
+ workingDir: {
148
+ type: 'string',
149
+ description: "Absolute path or ~/path for the tab's working directory",
150
+ },
151
+ template: {
152
+ type: 'string',
153
+ description: 'Name of a tab template from config (sets workingDir + systemPrompt)',
154
+ },
155
+ systemPrompt: { type: 'string', description: 'Tab-specific system prompt for Claude' },
156
+ },
157
+ required: ['name'],
158
+ },
159
+ },
160
+ {
161
+ name: 'beecork_tab_list',
162
+ description: 'List all Beecork tabs and their status.',
163
+ inputSchema: { type: 'object', properties: {} },
164
+ },
165
+ {
166
+ name: 'beecork_send_message',
167
+ description: "Send a message to another Beecork tab. The message will be processed asynchronously by that tab's Claude subprocess.",
168
+ inputSchema: {
169
+ type: 'object',
170
+ properties: {
171
+ tabName: { type: 'string', description: 'Which tab to send to' },
172
+ message: { type: 'string', description: 'The message text' },
173
+ },
174
+ required: ['tabName', 'message'],
175
+ },
176
+ },
177
+ {
178
+ name: 'beecork_recall',
179
+ description: "Search Beecork's memory and knowledge files for facts matching a query.",
180
+ inputSchema: {
181
+ type: 'object',
182
+ properties: {
183
+ query: { type: 'string', description: 'Search query (matches content substrings)' },
184
+ limit: { type: 'number', description: 'Max results (default 10, max 50)' },
185
+ },
186
+ required: ['query'],
187
+ },
188
+ },
189
+ {
190
+ name: 'beecork_notify',
191
+ description: 'Send a notification to the user via their configured channels (Telegram, Discord, etc.).',
192
+ inputSchema: {
193
+ type: 'object',
194
+ properties: {
195
+ message: { type: 'string', description: 'The notification text' },
196
+ urgent: { type: 'boolean', description: 'Prepend a 🚨 prefix' },
197
+ },
198
+ required: ['message'],
199
+ },
200
+ },
201
+ {
202
+ name: 'beecork_status',
203
+ description: 'Get a summary of Beecork state: tab/task/watcher/memory counts.',
204
+ inputSchema: { type: 'object', properties: {} },
205
+ },
206
+ {
207
+ name: 'beecork_send_media',
208
+ description: "Send a generated or local media file (image/video/audio) through the user's channels.",
209
+ inputSchema: {
210
+ type: 'object',
211
+ properties: {
212
+ filePath: { type: 'string', description: 'Absolute path to the media file' },
213
+ caption: { type: 'string', description: 'Optional caption' },
214
+ tabName: {
215
+ type: 'string',
216
+ description: 'Tab to attribute the send to (default: "default")',
217
+ },
218
+ },
219
+ required: ['filePath'],
220
+ },
221
+ },
222
+ {
223
+ name: 'beecork_channels',
224
+ description: 'List configured channels and their capabilities.',
225
+ inputSchema: { type: 'object', properties: {} },
226
+ },
227
+ {
228
+ name: 'beecork_cost',
229
+ description: 'Get cost summary (today, 7-day, 30-day, per-tab).',
230
+ inputSchema: {
231
+ type: 'object',
232
+ properties: { tabName: { type: 'string', description: 'Filter to a single tab' } },
233
+ },
234
+ },
235
+ {
236
+ name: 'beecork_failed_deliveries',
237
+ description: 'List recent messages that failed to deliver via channels.',
238
+ inputSchema: { type: 'object', properties: {} },
239
+ },
240
+ {
241
+ name: 'beecork_activity',
242
+ description: 'Get activity summary for the last N hours.',
243
+ inputSchema: {
244
+ type: 'object',
245
+ properties: { hours: { type: 'number', description: 'Lookback window (default 24)' } },
246
+ },
247
+ },
248
+ {
249
+ name: 'beecork_export_data',
250
+ description: 'Export historical data as JSON for analysis.',
251
+ inputSchema: {
252
+ type: 'object',
253
+ properties: {
254
+ type: {
255
+ type: 'string',
256
+ enum: ['costs', 'messages', 'crons'],
257
+ description: 'What to export',
258
+ },
259
+ days: { type: 'number', description: 'Lookback window in days (default 30)' },
260
+ },
261
+ required: ['type'],
262
+ },
263
+ },
264
+ {
265
+ name: 'beecork_handoff',
266
+ description: "Get info to resume a tab's session in your terminal.",
267
+ inputSchema: {
268
+ type: 'object',
269
+ properties: { tabName: { type: 'string' } },
270
+ required: ['tabName'],
271
+ },
272
+ },
273
+ {
274
+ name: 'beecork_delegate',
275
+ description: 'Delegate a sub-task to another tab. The result will be sent back when complete.',
276
+ inputSchema: {
277
+ type: 'object',
278
+ properties: {
279
+ tabName: { type: 'string', description: 'Tab to delegate to' },
280
+ message: { type: 'string', description: 'The task description' },
281
+ returnToTab: {
282
+ type: 'string',
283
+ description: 'Tab to notify when complete (default: "default")',
284
+ },
285
+ },
286
+ required: ['tabName', 'message'],
287
+ },
288
+ },
289
+ {
290
+ name: 'beecork_delegation_status',
291
+ description: 'List pending delegations.',
292
+ inputSchema: {
293
+ type: 'object',
294
+ properties: { tabName: { type: 'string', description: 'Filter by source or target tab' } },
295
+ },
296
+ },
297
+ {
298
+ name: 'beecork_project_create',
299
+ description: 'Register a project folder so the router can detect it from natural language.',
300
+ inputSchema: {
301
+ type: 'object',
302
+ properties: {
303
+ name: { type: 'string', description: 'Project name (also the folder name)' },
304
+ path: {
305
+ type: 'string',
306
+ description: 'Optional parent directory (must be under a scan path)',
307
+ },
308
+ },
309
+ required: ['name'],
310
+ },
311
+ },
312
+ {
313
+ name: 'beecork_project_list',
314
+ description: 'List registered projects/folders.',
315
+ inputSchema: { type: 'object', properties: {} },
316
+ },
317
+ {
318
+ name: 'beecork_close_tab',
319
+ description: 'Permanently close a tab — stops its subprocess and deletes its message history.',
320
+ inputSchema: {
321
+ type: 'object',
322
+ properties: { tabName: { type: 'string' } },
323
+ required: ['tabName'],
324
+ },
325
+ },
326
+ {
327
+ name: 'beecork_generate_image',
328
+ description: 'Generate an image via a configured media provider (DALL-E, Recraft, etc.).',
329
+ inputSchema: {
330
+ type: 'object',
331
+ properties: {
332
+ prompt: { type: 'string' },
333
+ style: { type: 'string' },
334
+ provider: { type: 'string' },
335
+ },
336
+ required: ['prompt'],
337
+ },
338
+ },
339
+ {
340
+ name: 'beecork_generate_video',
341
+ description: 'Generate a video via a configured media provider.',
342
+ inputSchema: {
343
+ type: 'object',
344
+ properties: {
345
+ prompt: { type: 'string' },
346
+ duration: { type: 'number' },
347
+ provider: { type: 'string' },
348
+ },
349
+ required: ['prompt'],
350
+ },
351
+ },
352
+ {
353
+ name: 'beecork_generate_audio',
354
+ description: 'Generate audio via a configured media provider.',
355
+ inputSchema: {
356
+ type: 'object',
357
+ properties: {
358
+ prompt: { type: 'string' },
359
+ provider: { type: 'string' },
360
+ },
361
+ required: ['prompt'],
362
+ },
363
+ },
364
+ {
365
+ name: 'beecork_media_providers',
366
+ description: 'List configured media generators and what they support.',
367
+ inputSchema: { type: 'object', properties: {} },
368
+ },
369
+ {
370
+ name: 'beecork_knowledge',
371
+ description: 'List all stored knowledge (memories + knowledge files), optionally filtered by scope.',
372
+ inputSchema: {
373
+ type: 'object',
374
+ properties: { scope: { type: 'string', enum: ['global', 'project', 'tab', 'all'] } },
375
+ },
376
+ },
377
+ {
378
+ name: 'beecork_capabilities',
379
+ description: 'List available capability packs and their status.',
380
+ inputSchema: { type: 'object', properties: {} },
381
+ },
382
+ {
383
+ name: 'beecork_history',
384
+ description: 'Get the activity timeline for a given day/tab.',
385
+ inputSchema: {
386
+ type: 'object',
387
+ properties: {
388
+ date: { type: 'string', description: 'ISO date YYYY-MM-DD (default: today)' },
389
+ tabName: { type: 'string' },
390
+ limit: { type: 'number' },
391
+ },
392
+ },
393
+ },
394
+ {
395
+ name: 'beecork_replay',
396
+ description: 'Re-run a past activity event by ID.',
397
+ inputSchema: {
398
+ type: 'object',
399
+ properties: { eventId: { type: 'string' } },
400
+ required: ['eventId'],
401
+ },
402
+ },
403
+ {
404
+ name: 'beecork_store_search',
405
+ description: 'Search the Beecork community extension registry (npm packages starting with "beecork-").',
406
+ inputSchema: {
407
+ type: 'object',
408
+ properties: { query: { type: 'string' } },
409
+ required: ['query'],
410
+ },
411
+ },
412
+ ];
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Minimal MCP tool-input validator. Walks the declared `inputSchema` from
3
+ * `tool-definitions.ts` and rejects calls that violate `required`, `type`, or
4
+ * `enum`. Handlers previously had ad-hoc checks for some fields and silently
5
+ * accepted invalid inputs for others — this single validator converts the
6
+ * inconsistency into a uniform "Invalid input: ..." failure.
7
+ *
8
+ * Deliberately handwritten (no Ajv/zod dep) to avoid pulling a runtime dep
9
+ * for one validation pass.
10
+ */
11
+ interface PropertySchema {
12
+ type?: string | string[];
13
+ enum?: readonly unknown[];
14
+ items?: PropertySchema;
15
+ description?: string;
16
+ }
17
+ export interface InputSchema {
18
+ type?: string;
19
+ properties?: Record<string, PropertySchema>;
20
+ required?: readonly string[];
21
+ }
22
+ export declare function validateToolArgs(schema: unknown, args: Record<string, unknown> | undefined): string | null;
23
+ export {};
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Minimal MCP tool-input validator. Walks the declared `inputSchema` from
3
+ * `tool-definitions.ts` and rejects calls that violate `required`, `type`, or
4
+ * `enum`. Handlers previously had ad-hoc checks for some fields and silently
5
+ * accepted invalid inputs for others — this single validator converts the
6
+ * inconsistency into a uniform "Invalid input: ..." failure.
7
+ *
8
+ * Deliberately handwritten (no Ajv/zod dep) to avoid pulling a runtime dep
9
+ * for one validation pass.
10
+ */
11
+ export function validateToolArgs(schema, args) {
12
+ if (!schema || typeof schema !== 'object')
13
+ return null;
14
+ const s = schema;
15
+ const a = args || {};
16
+ // Required fields
17
+ for (const field of s.required ?? []) {
18
+ const value = a[field];
19
+ if (value === undefined || value === null || value === '') {
20
+ return `Invalid input: "${field}" is required`;
21
+ }
22
+ }
23
+ // Per-property type/enum checks
24
+ for (const [field, propSchema] of Object.entries(s.properties ?? {})) {
25
+ const value = a[field];
26
+ if (value === undefined || value === null)
27
+ continue;
28
+ const typeError = checkType(value, propSchema, field);
29
+ if (typeError)
30
+ return typeError;
31
+ if (propSchema.enum && !propSchema.enum.includes(value)) {
32
+ return `Invalid input: "${field}" must be one of ${JSON.stringify(propSchema.enum)} (got ${JSON.stringify(value)})`;
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ function checkType(value, schema, field) {
38
+ if (!schema.type)
39
+ return null;
40
+ const types = (Array.isArray(schema.type) ? schema.type : [schema.type]);
41
+ for (const t of types) {
42
+ if (matchesType(value, t))
43
+ return null;
44
+ }
45
+ return `Invalid input: "${field}" must be ${types.join(' | ')} (got ${typeof value})`;
46
+ }
47
+ function matchesType(value, t) {
48
+ switch (t) {
49
+ case 'string':
50
+ return typeof value === 'string';
51
+ case 'number':
52
+ return typeof value === 'number' && Number.isFinite(value);
53
+ case 'integer':
54
+ return typeof value === 'number' && Number.isInteger(value);
55
+ case 'boolean':
56
+ return typeof value === 'boolean';
57
+ case 'object':
58
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
59
+ case 'array':
60
+ return Array.isArray(value);
61
+ default:
62
+ // Unknown types fall through to accept (lenient).
63
+ return true;
64
+ }
65
+ }
@@ -9,24 +9,28 @@ import { NanoBananaGenerator } from './generators/nano-banana.js';
9
9
  import { ElevenLabsMusicGenerator } from './generators/elevenlabs-music.js';
10
10
  import { LyriaGenerator } from './generators/lyria.js';
11
11
  import { RecraftGenerator } from './generators/recraft.js';
12
+ // Lookup table — adding a new generator becomes one entry instead of one switch case.
13
+ const GENERATOR_BUILDERS = {
14
+ 'dall-e': (k, m) => new DalleGenerator(k, m),
15
+ 'stable-diffusion': (k) => new StableDiffusionGenerator(k),
16
+ runway: (k) => new RunwayGenerator(k),
17
+ veo: (k) => new VeoGenerator(k),
18
+ kling: (k) => new KlingGenerator(k),
19
+ 'elevenlabs-sfx': (k) => new ElevenLabsSfxGenerator(k),
20
+ 'nano-banana': (k, m) => new NanoBananaGenerator(k, m),
21
+ 'elevenlabs-music': (k) => new ElevenLabsMusicGenerator(k),
22
+ lyria: (k, m) => new LyriaGenerator(k, m),
23
+ recraft: (k) => new RecraftGenerator(k),
24
+ };
12
25
  export function createMediaGenerator(config) {
13
26
  if (!config.apiKey) {
14
27
  logger.warn(`Media generator ${config.provider}: missing API key`);
15
28
  return null;
16
29
  }
17
- switch (config.provider) {
18
- case 'dall-e': return new DalleGenerator(config.apiKey, config.model);
19
- case 'stable-diffusion': return new StableDiffusionGenerator(config.apiKey);
20
- case 'runway': return new RunwayGenerator(config.apiKey);
21
- case 'veo': return new VeoGenerator(config.apiKey);
22
- case 'kling': return new KlingGenerator(config.apiKey);
23
- case 'elevenlabs-sfx': return new ElevenLabsSfxGenerator(config.apiKey);
24
- case 'nano-banana': return new NanoBananaGenerator(config.apiKey, config.model);
25
- case 'elevenlabs-music': return new ElevenLabsMusicGenerator(config.apiKey);
26
- case 'lyria': return new LyriaGenerator(config.apiKey, config.model);
27
- case 'recraft': return new RecraftGenerator(config.apiKey);
28
- default:
29
- logger.warn(`Unknown media generator: ${config.provider}`);
30
- return null;
30
+ const build = GENERATOR_BUILDERS[config.provider];
31
+ if (!build) {
32
+ logger.warn(`Unknown media generator: ${config.provider}`);
33
+ return null;
31
34
  }
35
+ return build(config.apiKey, config.model);
32
36
  }
@@ -16,7 +16,7 @@ export class DalleGenerator {
16
16
  const response = await fetch('https://api.openai.com/v1/images/generations', {
17
17
  method: 'POST',
18
18
  headers: {
19
- 'Authorization': `Bearer ${this.apiKey}`,
19
+ Authorization: `Bearer ${this.apiKey}`,
20
20
  'Content-Type': 'application/json',
21
21
  },
22
22
  body: JSON.stringify({
@@ -33,7 +33,7 @@ export class DalleGenerator {
33
33
  const err = await response.text();
34
34
  throw new Error(`DALL-E error ${response.status}: ${err.slice(0, 200)}`);
35
35
  }
36
- const data = await response.json();
36
+ const data = (await response.json());
37
37
  const buffer = Buffer.from(data.data[0].b64_json, 'base64');
38
38
  const filePath = saveMedia(buffer, 'png', 'generated-image.png');
39
39
  return { filePath, mimeType: 'image/png' };
@@ -14,7 +14,7 @@ export class KlingGenerator {
14
14
  const response = await fetch('https://api.klingai.com/v1/videos/text2video', {
15
15
  method: 'POST',
16
16
  headers: {
17
- 'Authorization': `Bearer ${this.apiKey}`,
17
+ Authorization: `Bearer ${this.apiKey}`,
18
18
  'Content-Type': 'application/json',
19
19
  },
20
20
  body: JSON.stringify({
@@ -28,14 +28,14 @@ export class KlingGenerator {
28
28
  const err = await response.text();
29
29
  throw new Error(`Kling error ${response.status}: ${err.slice(0, 200)}`);
30
30
  }
31
- const { data } = await response.json();
31
+ const { data } = (await response.json());
32
32
  // Poll for completion
33
- const headers = { 'Authorization': `Bearer ${this.apiKey}` };
33
+ const headers = { Authorization: `Bearer ${this.apiKey}` };
34
34
  const videoUrl = await pollForCompletion({
35
35
  statusUrl: `https://api.klingai.com/v1/videos/text2video/${data.task_id}`,
36
36
  headers,
37
37
  isComplete: (d) => d.data.task_status === 'succeed' && !!d.data.task_result?.videos[0],
38
- isFailed: (d) => d.data.task_status === 'failed' ? 'generation failed' : null,
38
+ isFailed: (d) => (d.data.task_status === 'failed' ? 'generation failed' : null),
39
39
  getResultUrl: (d) => d.data.task_result.videos[0].url,
40
40
  label: 'Kling',
41
41
  });
@@ -29,7 +29,7 @@ export class LyriaGenerator {
29
29
  const err = await response.text();
30
30
  throw new Error(`Lyria error ${response.status}: ${err.slice(0, 200)}`);
31
31
  }
32
- const data = await response.json();
32
+ const data = (await response.json());
33
33
  const audioPart = data.candidates?.[0]?.content?.parts?.find((p) => p.inlineData);
34
34
  if (!audioPart?.inlineData?.data) {
35
35
  throw new Error('Lyria returned no audio data');
@@ -6,5 +6,5 @@ export declare class NanoBananaGenerator implements MediaGenerator {
6
6
  readonly name = "Google Nano Banana";
7
7
  readonly supportedTypes: MediaType[];
8
8
  constructor(apiKey: string, model?: string);
9
- generate(type: MediaType, prompt: string, options?: GenerateOptions): Promise<GenerateResult>;
9
+ generate(type: MediaType, prompt: string, _options?: GenerateOptions): Promise<GenerateResult>;
10
10
  }
@@ -9,7 +9,7 @@ export class NanoBananaGenerator {
9
9
  this.apiKey = apiKey;
10
10
  this.model = model;
11
11
  }
12
- async generate(type, prompt, options) {
12
+ async generate(type, prompt, _options) {
13
13
  if (type !== 'image')
14
14
  throw new Error('Nano Banana only supports image generation');
15
15
  // Gemini image generation API
@@ -31,7 +31,7 @@ export class NanoBananaGenerator {
31
31
  const err = await response.text();
32
32
  throw new Error(`Nano Banana error ${response.status}: ${err.slice(0, 200)}`);
33
33
  }
34
- const data = await response.json();
34
+ const data = (await response.json());
35
35
  const imagePart = data.candidates?.[0]?.content?.parts?.find((p) => p.inlineData);
36
36
  if (!imagePart?.inlineData?.data) {
37
37
  throw new Error('Nano Banana returned no image data');
@@ -1,8 +1,8 @@
1
1
  import { logger } from '../../util/logger.js';
2
2
  export async function pollForCompletion(opts) {
3
- const { statusUrl, headers, isComplete, isFailed, getResultUrl, interval = 5000, maxAttempts = 60, label = 'API' } = opts;
3
+ const { statusUrl, headers, isComplete, isFailed, getResultUrl, interval = 5000, maxAttempts = 60, label = 'API', } = opts;
4
4
  for (let i = 0; i < maxAttempts; i++) {
5
- await new Promise(r => setTimeout(r, interval));
5
+ await new Promise((r) => setTimeout(r, interval));
6
6
  const resp = await fetch(statusUrl, { headers, signal: AbortSignal.timeout(10000) });
7
7
  if (!resp.ok) {
8
8
  logger.warn(`[${label}] Poll failed: ${resp.status}`);
@@ -11,12 +11,12 @@ export async function pollForCompletion(opts) {
11
11
  }
12
12
  continue;
13
13
  }
14
- const data = await resp.json();
14
+ const data = (await resp.json());
15
15
  const failMsg = isFailed(data);
16
16
  if (failMsg)
17
17
  throw new Error(`${label} generation failed: ${failMsg}`);
18
18
  if (isComplete(data))
19
19
  return getResultUrl(data);
20
20
  }
21
- throw new Error(`${label} generation timed out after ${maxAttempts * interval / 1000}s`);
21
+ throw new Error(`${label} generation timed out after ${(maxAttempts * interval) / 1000}s`);
22
22
  }