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.
- package/dist/capabilities/index.d.ts +1 -1
- package/dist/capabilities/index.js +1 -1
- package/dist/capabilities/manager.js +13 -9
- package/dist/capabilities/packs.js +3 -1
- package/dist/channels/admin.d.ts +10 -0
- package/dist/channels/admin.js +20 -0
- package/dist/channels/command-handler.d.ts +2 -10
- package/dist/channels/command-handler.js +90 -84
- package/dist/channels/discord.d.ts +4 -9
- package/dist/channels/discord.js +59 -42
- package/dist/channels/index.d.ts +1 -1
- package/dist/channels/loader.js +13 -4
- package/dist/channels/pipeline.js +14 -5
- package/dist/channels/registry.d.ts +17 -1
- package/dist/channels/registry.js +33 -4
- package/dist/channels/send-helpers.d.ts +19 -0
- package/dist/channels/send-helpers.js +21 -0
- package/dist/channels/telegram.d.ts +21 -14
- package/dist/channels/telegram.js +214 -104
- package/dist/channels/types.d.ts +13 -38
- package/dist/channels/voice-state.d.ts +29 -0
- package/dist/channels/voice-state.js +45 -0
- package/dist/channels/webhook.d.ts +2 -5
- package/dist/channels/webhook.js +88 -29
- package/dist/channels/whatsapp.d.ts +9 -7
- package/dist/channels/whatsapp.js +141 -100
- package/dist/cli/capabilities.js +4 -4
- package/dist/cli/channel.js +16 -6
- package/dist/cli/commands.js +12 -9
- package/dist/cli/doctor.js +85 -27
- package/dist/cli/handoff.d.ts +7 -14
- package/dist/cli/handoff.js +9 -44
- package/dist/cli/mcp.js +5 -5
- package/dist/cli/media.js +21 -8
- package/dist/cli/setup.js +9 -8
- package/dist/cli/store.js +29 -12
- package/dist/config.d.ts +5 -1
- package/dist/config.js +20 -22
- package/dist/daemon.js +113 -51
- package/dist/dashboard/html.js +100 -20
- package/dist/dashboard/routes.d.ts +17 -0
- package/dist/dashboard/routes.js +623 -0
- package/dist/dashboard/server.js +38 -489
- package/dist/db/connection.d.ts +29 -0
- package/dist/db/connection.js +37 -0
- package/dist/db/index.js +43 -11
- package/dist/db/migrations.js +114 -22
- package/dist/delegation/manager.js +10 -4
- package/dist/index.js +39 -59
- package/dist/knowledge/manager.js +26 -12
- package/dist/mcp/handlers.d.ts +37 -0
- package/dist/mcp/handlers.js +520 -0
- package/dist/mcp/server.js +44 -858
- package/dist/mcp/tool-definitions.d.ts +1225 -0
- package/dist/mcp/tool-definitions.js +412 -0
- package/dist/mcp/validate.d.ts +23 -0
- package/dist/mcp/validate.js +65 -0
- package/dist/media/factory.js +18 -14
- package/dist/media/generators/dall-e.js +2 -2
- package/dist/media/generators/kling.js +4 -4
- package/dist/media/generators/lyria.js +1 -1
- package/dist/media/generators/nano-banana.d.ts +1 -1
- package/dist/media/generators/nano-banana.js +2 -2
- package/dist/media/generators/poll-util.js +4 -4
- package/dist/media/generators/recraft.js +3 -3
- package/dist/media/generators/runway.js +4 -4
- package/dist/media/generators/stable-diffusion.js +2 -2
- package/dist/media/generators/veo.js +1 -1
- package/dist/media/index.d.ts +2 -7
- package/dist/media/index.js +2 -2
- package/dist/media/store.d.ts +7 -0
- package/dist/media/store.js +18 -4
- package/dist/media/types.d.ts +22 -0
- package/dist/notifications/index.d.ts +2 -4
- package/dist/notifications/index.js +6 -19
- package/dist/notifications/ntfy.js +3 -3
- package/dist/observability/analytics.d.ts +1 -1
- package/dist/observability/analytics.js +41 -16
- package/dist/projects/index.d.ts +3 -2
- package/dist/projects/index.js +2 -2
- package/dist/projects/manager.d.ts +1 -7
- package/dist/projects/manager.js +66 -42
- package/dist/projects/router.d.ts +12 -0
- package/dist/projects/router.js +98 -45
- package/dist/service/install.js +15 -5
- package/dist/service/windows.js +1 -1
- package/dist/session/budget-guard.d.ts +20 -0
- package/dist/session/budget-guard.js +31 -0
- package/dist/session/circuit-breaker.d.ts +5 -3
- package/dist/session/circuit-breaker.js +45 -20
- package/dist/session/context-compactor.d.ts +32 -0
- package/dist/session/context-compactor.js +45 -0
- package/dist/session/context-monitor.js +2 -2
- package/dist/session/handoff.d.ts +21 -0
- package/dist/session/handoff.js +50 -0
- package/dist/session/manager.d.ts +21 -5
- package/dist/session/manager.js +166 -153
- package/dist/session/memory-store.d.ts +29 -0
- package/dist/session/memory-store.js +45 -0
- package/dist/session/message-queue.d.ts +28 -0
- package/dist/session/message-queue.js +52 -0
- package/dist/session/pending-dispatcher.d.ts +31 -0
- package/dist/session/pending-dispatcher.js +120 -0
- package/dist/session/pending-store.d.ts +60 -0
- package/dist/session/pending-store.js +118 -0
- package/dist/session/stale-session.d.ts +31 -0
- package/dist/session/stale-session.js +45 -0
- package/dist/session/subprocess.d.ts +3 -0
- package/dist/session/subprocess.js +54 -11
- package/dist/session/tab-store.d.ts +28 -0
- package/dist/session/tab-store.js +78 -0
- package/dist/tasks/scheduler.d.ts +13 -0
- package/dist/tasks/scheduler.js +97 -18
- package/dist/tasks/store.js +26 -12
- package/dist/timeline/logger.js +3 -1
- package/dist/timeline/query.js +15 -5
- package/dist/types.d.ts +49 -9
- package/dist/util/auto-heal.js +15 -5
- package/dist/util/install-info.js +3 -1
- package/dist/util/logger.d.ts +1 -1
- package/dist/util/logger.js +63 -24
- package/dist/util/paths.d.ts +2 -0
- package/dist/util/paths.js +16 -3
- package/dist/util/rate-limiter.js +8 -0
- package/dist/util/retry.js +1 -1
- package/dist/util/text.d.ts +21 -1
- package/dist/util/text.js +38 -8
- package/dist/voice/index.js +5 -1
- package/dist/voice/stt.js +14 -6
- package/dist/voice/tts.js +1 -1
- package/dist/watchers/scheduler.js +11 -5
- package/package.json +6 -1
- package/dist/session/tool-classifier.d.ts +0 -4
- package/dist/session/tool-classifier.js +0 -56
- package/dist/users/index.d.ts +0 -2
- package/dist/users/index.js +0 -1
- package/dist/users/service.d.ts +0 -17
- package/dist/users/service.js +0 -46
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
// MCP tool handlers. Each handler returns the MCP response shape (or isError).
|
|
2
|
+
// Pulled out of server.ts so the dispatcher there can stay thin and so each
|
|
3
|
+
// handler is independently grep-able / testable.
|
|
4
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
import { expandHome } from '../util/paths.js';
|
|
9
|
+
import { getConfig, validateTabNameOrDefault } from '../config.js';
|
|
10
|
+
import { createTabRecord } from '../db/index.js';
|
|
11
|
+
import { MESSAGE_LIMITS } from '../util/text.js';
|
|
12
|
+
import { TabStore } from '../session/tab-store.js';
|
|
13
|
+
import { PendingMessageStore } from '../session/pending-store.js';
|
|
14
|
+
import { logActivity } from '../timeline/index.js';
|
|
15
|
+
export function ok(text) {
|
|
16
|
+
return { content: [{ type: 'text', text }] };
|
|
17
|
+
}
|
|
18
|
+
export function fail(text) {
|
|
19
|
+
return { content: [{ type: 'text', text }], isError: true };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Convenience wrapper for tools that return structured data — emits pretty-printed
|
|
23
|
+
* JSON inside the standard `text` content. Use for endpoints whose responses are
|
|
24
|
+
* meant to be parsed programmatically (channels, handoff, export_data). Use plain
|
|
25
|
+
* ok() for human-readable lists/summaries.
|
|
26
|
+
*/
|
|
27
|
+
export function jsonOk(data) {
|
|
28
|
+
return ok(JSON.stringify(data, null, 2));
|
|
29
|
+
}
|
|
30
|
+
const MAX_CONTENT_LENGTH = MESSAGE_LIMITS.MCP_CONTENT;
|
|
31
|
+
const MAX_NAME_LENGTH = 256;
|
|
32
|
+
const VALID_SCHEDULE_TYPES = ['at', 'every', 'cron'];
|
|
33
|
+
async function handleMediaGeneration(ctx, mediaType, args) {
|
|
34
|
+
const { prompt, style, duration, provider } = (args || {});
|
|
35
|
+
if (!prompt)
|
|
36
|
+
return fail('Missing prompt');
|
|
37
|
+
const generators = await ctx.getGenerators();
|
|
38
|
+
const gen = provider
|
|
39
|
+
? generators.find((g) => g.id === provider)
|
|
40
|
+
: generators.find((g) => g.supportedTypes.includes(mediaType));
|
|
41
|
+
if (!gen)
|
|
42
|
+
return fail(`No ${mediaType} generator configured. Run: beecork media`);
|
|
43
|
+
try {
|
|
44
|
+
const result = await gen.generate(mediaType, prompt, { style, duration });
|
|
45
|
+
PendingMessageStore.enqueueMedia('default', { type: 'media', filePath: result.filePath, caption: prompt.slice(0, 200) }, ctx.db);
|
|
46
|
+
logActivity('media_generated', `${mediaType} generated`, { details: result.filePath });
|
|
47
|
+
return ok(`Generated ${mediaType}: ${result.filePath}`);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
return fail(`${mediaType} generation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export const HANDLERS = {
|
|
54
|
+
beecork_remember: async (ctx, args) => {
|
|
55
|
+
const { content, scope, category } = (args || {});
|
|
56
|
+
if (!content || content.length > MAX_CONTENT_LENGTH) {
|
|
57
|
+
return fail(`Content is required and must be under ${MAX_CONTENT_LENGTH} characters.`);
|
|
58
|
+
}
|
|
59
|
+
if (scope && scope !== 'tab' && scope !== 'auto') {
|
|
60
|
+
const { addKnowledge } = await import('../knowledge/index.js');
|
|
61
|
+
const currentTab = TabStore.mostRecent(ctx.db);
|
|
62
|
+
addKnowledge(content, scope, {
|
|
63
|
+
category,
|
|
64
|
+
projectPath: currentTab?.workingDir,
|
|
65
|
+
tabName: undefined,
|
|
66
|
+
});
|
|
67
|
+
return ok(`Remembered (${scope}): ${content.slice(0, 100)}`);
|
|
68
|
+
}
|
|
69
|
+
const fullContent = category ? `[${category}] ${content}` : content;
|
|
70
|
+
const existing = ctx.db
|
|
71
|
+
.prepare('SELECT id FROM memories WHERE content = ? AND tab_name IS NULL LIMIT 1')
|
|
72
|
+
.get(fullContent);
|
|
73
|
+
if (existing)
|
|
74
|
+
return ok(`Already remembered: "${fullContent}"`);
|
|
75
|
+
const { MemoryStore } = await import('../session/memory-store.js');
|
|
76
|
+
MemoryStore.add(fullContent, { source: 'tool' }, ctx.db);
|
|
77
|
+
return ok(`Remembered: "${fullContent}"`);
|
|
78
|
+
},
|
|
79
|
+
beecork_task_create: async (ctx, args) => {
|
|
80
|
+
const { name: jobName, scheduleType, schedule, message, tabName, } = (args || {});
|
|
81
|
+
if (!jobName || jobName.length > MAX_NAME_LENGTH) {
|
|
82
|
+
return fail(`Task name is required and must be under ${MAX_NAME_LENGTH} characters.`);
|
|
83
|
+
}
|
|
84
|
+
if (!VALID_SCHEDULE_TYPES.includes(scheduleType)) {
|
|
85
|
+
return fail(`Invalid scheduleType "${scheduleType}". Must be one of: ${VALID_SCHEDULE_TYPES.join(', ')}`);
|
|
86
|
+
}
|
|
87
|
+
if (!message || message.length > MAX_CONTENT_LENGTH) {
|
|
88
|
+
return fail(`Message is required and must be under ${MAX_CONTENT_LENGTH} characters.`);
|
|
89
|
+
}
|
|
90
|
+
const id = uuidv4();
|
|
91
|
+
const tab = tabName || 'default';
|
|
92
|
+
const tabError = validateTabNameOrDefault(tab);
|
|
93
|
+
if (tabError)
|
|
94
|
+
return fail(tabError);
|
|
95
|
+
// Validate schedule expression up-front so misconfigured tasks fail loud instead of silently never firing.
|
|
96
|
+
const { validateSchedule } = await import('../tasks/scheduler.js');
|
|
97
|
+
const scheduleErr = validateSchedule(scheduleType, schedule);
|
|
98
|
+
if (scheduleErr)
|
|
99
|
+
return fail(scheduleErr);
|
|
100
|
+
const { TaskStore } = await import('../tasks/store.js');
|
|
101
|
+
new TaskStore().add({
|
|
102
|
+
id,
|
|
103
|
+
name: jobName,
|
|
104
|
+
scheduleType: scheduleType,
|
|
105
|
+
schedule,
|
|
106
|
+
tabName: tab,
|
|
107
|
+
message,
|
|
108
|
+
payloadType: 'agentTurn',
|
|
109
|
+
enabled: true,
|
|
110
|
+
createdAt: new Date().toISOString(),
|
|
111
|
+
lastRunAt: null,
|
|
112
|
+
nextRunAt: null,
|
|
113
|
+
});
|
|
114
|
+
try {
|
|
115
|
+
ctx.signalCronReload();
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
return ok(`Task created: "${jobName}" (${scheduleType}: ${schedule}) -> tab:${tab}\nID: ${id}\nWARN: signal-reload failed (${err instanceof Error ? err.message : String(err)}); restart daemon to schedule.`);
|
|
119
|
+
}
|
|
120
|
+
return ok(`Task created: "${jobName}" (${scheduleType}: ${schedule}) -> tab:${tab}\nID: ${id}`);
|
|
121
|
+
},
|
|
122
|
+
beecork_task_list: async (ctx) => {
|
|
123
|
+
const jobs = ctx.db.prepare('SELECT * FROM tasks ORDER BY created_at LIMIT 500').all();
|
|
124
|
+
if (jobs.length === 0)
|
|
125
|
+
return ok('No tasks scheduled.');
|
|
126
|
+
const lines = jobs.map((j) => `- ${j.name} [${j.enabled ? 'enabled' : 'disabled'}] (${j.schedule_type}: ${j.schedule}) -> tab:${j.tab_name} (ID: ${j.id})`);
|
|
127
|
+
return ok(lines.join('\n'));
|
|
128
|
+
},
|
|
129
|
+
beecork_task_delete: async (ctx, args) => {
|
|
130
|
+
const { id } = (args || {});
|
|
131
|
+
const result = ctx.db.prepare('DELETE FROM tasks WHERE id = ?').run(id);
|
|
132
|
+
if (result.changes === 0)
|
|
133
|
+
return ok(`No task found with ID: ${id}`);
|
|
134
|
+
try {
|
|
135
|
+
ctx.signalCronReload();
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
/* daemon picks up on restart */
|
|
139
|
+
}
|
|
140
|
+
return ok(`Deleted task: ${id}`);
|
|
141
|
+
},
|
|
142
|
+
beecork_watch_create: async (ctx, args) => {
|
|
143
|
+
const { name: watchName, description: watchDesc, checkCommand, condition, action, actionDetails, schedule: watchSchedule, } = (args || {});
|
|
144
|
+
if (!watchName || watchName.length > MAX_NAME_LENGTH) {
|
|
145
|
+
return fail(`Watcher name is required and must be under ${MAX_NAME_LENGTH} characters.`);
|
|
146
|
+
}
|
|
147
|
+
if (!checkCommand)
|
|
148
|
+
return fail('checkCommand is required.');
|
|
149
|
+
if (!condition)
|
|
150
|
+
return fail('condition is required.');
|
|
151
|
+
if (!watchSchedule)
|
|
152
|
+
return fail('schedule is required.');
|
|
153
|
+
// Schedule validation: watchers use "cron" or interval like "5m". Try both.
|
|
154
|
+
const { validateSchedule } = await import('../tasks/scheduler.js');
|
|
155
|
+
const cronErr = validateSchedule('cron', watchSchedule);
|
|
156
|
+
const intervalErr = validateSchedule('every', watchSchedule);
|
|
157
|
+
if (cronErr && intervalErr)
|
|
158
|
+
return fail(`Invalid schedule "${watchSchedule}" — must be a cron expression or interval like "5m"/"1h"/"1d".`);
|
|
159
|
+
const watchId = uuidv4();
|
|
160
|
+
ctx.db
|
|
161
|
+
.prepare(`INSERT INTO watchers (id, name, description, check_command, condition, action, action_details, schedule)
|
|
162
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`)
|
|
163
|
+
.run(watchId, watchName, watchDesc || null, checkCommand, condition, action || 'notify', actionDetails || null, watchSchedule);
|
|
164
|
+
try {
|
|
165
|
+
ctx.signalWatcherReload();
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
return ok(`Watcher created: "${watchName}" (${watchSchedule})\nID: ${watchId}\nWARN: signal-reload failed (${err instanceof Error ? err.message : String(err)}); restart daemon to schedule.`);
|
|
169
|
+
}
|
|
170
|
+
return ok(`Watcher created: "${watchName}" (${watchSchedule})\nID: ${watchId}`);
|
|
171
|
+
},
|
|
172
|
+
beecork_watch_list: async (ctx) => {
|
|
173
|
+
const watchers = ctx.db
|
|
174
|
+
.prepare('SELECT * FROM watchers ORDER BY created_at LIMIT 500')
|
|
175
|
+
.all();
|
|
176
|
+
if (watchers.length === 0)
|
|
177
|
+
return ok('No watchers configured.');
|
|
178
|
+
const watchLines = watchers.map((w) => `- ${w.name} [${w.enabled ? 'enabled' : 'disabled'}] ${w.schedule} | action: ${w.action} | triggers: ${w.trigger_count} (ID: ${w.id})`);
|
|
179
|
+
return ok(watchLines.join('\n'));
|
|
180
|
+
},
|
|
181
|
+
beecork_watch_delete: async (ctx, args) => {
|
|
182
|
+
const { id: watchDelId } = (args || {});
|
|
183
|
+
const watchDelResult = ctx.db.prepare('DELETE FROM watchers WHERE id = ?').run(watchDelId);
|
|
184
|
+
if (watchDelResult.changes === 0)
|
|
185
|
+
return ok(`No watcher found with ID: ${watchDelId}`);
|
|
186
|
+
try {
|
|
187
|
+
ctx.signalWatcherReload();
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
/* daemon picks up on restart */
|
|
191
|
+
}
|
|
192
|
+
return ok(`Deleted watcher: ${watchDelId}`);
|
|
193
|
+
},
|
|
194
|
+
beecork_tab_create: async (ctx, args) => {
|
|
195
|
+
const { name: tabName, workingDir, template: templateName, systemPrompt, } = (args || {});
|
|
196
|
+
if (!tabName)
|
|
197
|
+
return fail('Tab name is required.');
|
|
198
|
+
// tab create does NOT allow "default" — that tab is auto-managed by the daemon.
|
|
199
|
+
const { validateTabName } = await import('../config.js');
|
|
200
|
+
const tabCreateError = validateTabName(tabName);
|
|
201
|
+
if (tabCreateError)
|
|
202
|
+
return fail(tabCreateError);
|
|
203
|
+
const config = getConfig();
|
|
204
|
+
const template = templateName ? config.tabTemplates?.[templateName] : undefined;
|
|
205
|
+
if (templateName && !template) {
|
|
206
|
+
return fail(`Template "${templateName}" not found. Available: ${Object.keys(config.tabTemplates || {}).join(', ') || 'none'}`);
|
|
207
|
+
}
|
|
208
|
+
const dirInput = workingDir || template?.workingDir || os.homedir();
|
|
209
|
+
const dir = path.resolve(expandHome(dirInput));
|
|
210
|
+
const tabSystemPrompt = systemPrompt || template?.systemPrompt || null;
|
|
211
|
+
try {
|
|
212
|
+
// createTabRecord validates existence + isDirectory and throws on failure.
|
|
213
|
+
const result = createTabRecord(ctx.db, {
|
|
214
|
+
name: tabName,
|
|
215
|
+
workingDir: dir,
|
|
216
|
+
systemPrompt: tabSystemPrompt,
|
|
217
|
+
});
|
|
218
|
+
if (!result.created)
|
|
219
|
+
return ok(`Tab "${tabName}" already exists.`);
|
|
220
|
+
const parts = [`Created tab: "${tabName}" (working dir: ${dir})`];
|
|
221
|
+
if (tabSystemPrompt)
|
|
222
|
+
parts.push(`System prompt: "${tabSystemPrompt.slice(0, 100)}${tabSystemPrompt.length > 100 ? '...' : ''}"`);
|
|
223
|
+
if (templateName)
|
|
224
|
+
parts.push(`Template: ${templateName}`);
|
|
225
|
+
return ok(parts.join('\n'));
|
|
226
|
+
}
|
|
227
|
+
catch (err) {
|
|
228
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
beecork_tab_list: async (ctx) => {
|
|
232
|
+
const tabs = TabStore.listAll(ctx.db);
|
|
233
|
+
if (tabs.length === 0)
|
|
234
|
+
return ok('No tabs.');
|
|
235
|
+
const lines = tabs.map((t) => `- ${t.name} [${t.status}] dir:${t.workingDir} last:${t.lastActivityAt}`);
|
|
236
|
+
return ok(lines.join('\n'));
|
|
237
|
+
},
|
|
238
|
+
beecork_send_message: async (ctx, args) => {
|
|
239
|
+
const { tabName, message } = (args || {});
|
|
240
|
+
if (!tabName || !message)
|
|
241
|
+
return fail('Both tabName and message are required.');
|
|
242
|
+
const sendTabError = validateTabNameOrDefault(tabName);
|
|
243
|
+
if (sendTabError)
|
|
244
|
+
return fail(sendTabError);
|
|
245
|
+
if (message.length > MAX_CONTENT_LENGTH) {
|
|
246
|
+
return fail(`Message must be under ${MAX_CONTENT_LENGTH} characters.`);
|
|
247
|
+
}
|
|
248
|
+
PendingMessageStore.enqueueUser(tabName, message, ctx.db);
|
|
249
|
+
return ok(`Message queued for tab "${tabName}".`);
|
|
250
|
+
},
|
|
251
|
+
beecork_recall: async (ctx, args) => {
|
|
252
|
+
const { query, limit } = (args || {});
|
|
253
|
+
if (typeof query !== 'string' || query.length === 0)
|
|
254
|
+
return fail('query is required');
|
|
255
|
+
if (query.length > 256)
|
|
256
|
+
return fail('query is too long (max 256 chars)');
|
|
257
|
+
const maxResults = Math.min(typeof limit === 'number' && limit > 0 ? limit : 10, 50);
|
|
258
|
+
const memories = ctx.db
|
|
259
|
+
.prepare('SELECT content, tab_name, source, created_at FROM memories WHERE content LIKE ? ORDER BY created_at DESC LIMIT ?')
|
|
260
|
+
.all(`%${query}%`, maxResults);
|
|
261
|
+
const { searchKnowledge } = await import('../knowledge/index.js');
|
|
262
|
+
const knowledgeResults = searchKnowledge(query);
|
|
263
|
+
const allResults = [
|
|
264
|
+
...knowledgeResults.map((k) => k.content),
|
|
265
|
+
...memories.map((m) => m.content),
|
|
266
|
+
];
|
|
267
|
+
if (allResults.length === 0)
|
|
268
|
+
return ok(`No relevant knowledge found matching "${query}".`);
|
|
269
|
+
return ok(allResults.join('\n---\n'));
|
|
270
|
+
},
|
|
271
|
+
beecork_notify: async (ctx, args) => {
|
|
272
|
+
const { message, urgent } = (args || {});
|
|
273
|
+
if (!message)
|
|
274
|
+
return fail('Message is required.');
|
|
275
|
+
const prefix = urgent ? '🚨 ' : '';
|
|
276
|
+
PendingMessageStore.enqueueNotification(prefix + message, ctx.db);
|
|
277
|
+
return ok('Notification sent to user.');
|
|
278
|
+
},
|
|
279
|
+
beecork_status: async (ctx) => {
|
|
280
|
+
const tabCount = TabStore.countAll(ctx.db);
|
|
281
|
+
const activeTabs = TabStore.countRunning(ctx.db);
|
|
282
|
+
const taskCount = ctx.db.prepare('SELECT COUNT(*) as c FROM tasks WHERE enabled = 1').get().c;
|
|
283
|
+
const watcherCount = ctx.db.prepare('SELECT COUNT(*) as c FROM watchers WHERE enabled = 1').get().c;
|
|
284
|
+
const memoryCount = ctx.db.prepare('SELECT COUNT(*) as c FROM memories').get().c;
|
|
285
|
+
return ok([
|
|
286
|
+
`Tabs: ${tabCount} total, ${activeTabs} running`,
|
|
287
|
+
`Tasks: ${taskCount} active`,
|
|
288
|
+
`Watchers: ${watcherCount} active`,
|
|
289
|
+
`Memories: ${memoryCount} stored`,
|
|
290
|
+
].join('\n'));
|
|
291
|
+
},
|
|
292
|
+
beecork_send_media: async (ctx, args) => {
|
|
293
|
+
const { filePath, caption, tabName } = (args || {});
|
|
294
|
+
if (!filePath)
|
|
295
|
+
return fail('filePath is required');
|
|
296
|
+
let resolved;
|
|
297
|
+
try {
|
|
298
|
+
const { assertInsideMediaDir } = await import('../media/store.js');
|
|
299
|
+
resolved = assertInsideMediaDir(filePath);
|
|
300
|
+
}
|
|
301
|
+
catch (err) {
|
|
302
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
303
|
+
}
|
|
304
|
+
if (!fs.existsSync(resolved))
|
|
305
|
+
return fail(`File not found: ${resolved}`);
|
|
306
|
+
const tab = tabName || 'default';
|
|
307
|
+
PendingMessageStore.enqueueMedia(tab, { type: 'media', filePath: resolved, caption }, ctx.db);
|
|
308
|
+
return ok(`Media queued for sending: ${resolved}`);
|
|
309
|
+
},
|
|
310
|
+
beecork_channels: async () => {
|
|
311
|
+
const config = getConfig();
|
|
312
|
+
const channels = [];
|
|
313
|
+
if (config.telegram?.token)
|
|
314
|
+
channels.push({ id: 'telegram', name: 'Telegram', streaming: true, media: true });
|
|
315
|
+
if (config.whatsapp?.enabled)
|
|
316
|
+
channels.push({ id: 'whatsapp', name: 'WhatsApp', streaming: false, media: true });
|
|
317
|
+
if (config.webhook?.enabled)
|
|
318
|
+
channels.push({ id: 'webhook', name: 'Webhook', streaming: false, media: false });
|
|
319
|
+
if (config.discord?.token)
|
|
320
|
+
channels.push({ id: 'discord', name: 'Discord', streaming: false, media: true });
|
|
321
|
+
return jsonOk(channels);
|
|
322
|
+
},
|
|
323
|
+
beecork_cost: async (_ctx, args) => {
|
|
324
|
+
const { tabName } = (args || {});
|
|
325
|
+
const { getCostSummary, formatCostSummary } = await import('../observability/analytics.js');
|
|
326
|
+
const summary = getCostSummary();
|
|
327
|
+
if (tabName) {
|
|
328
|
+
const tab = summary.perTab.find((t) => t.name === tabName);
|
|
329
|
+
if (!tab)
|
|
330
|
+
return fail(`Tab "${tabName}" not found`);
|
|
331
|
+
return ok(`Tab "${tabName}": $${tab.cost.toFixed(4)} (${tab.messages} messages)`);
|
|
332
|
+
}
|
|
333
|
+
return ok(formatCostSummary(summary));
|
|
334
|
+
},
|
|
335
|
+
beecork_failed_deliveries: async (ctx) => {
|
|
336
|
+
const failed = ctx.db
|
|
337
|
+
.prepare("SELECT m.content, m.created_at, m.retry_count, t.name as tab_name FROM messages m JOIN tabs t ON t.id = m.tab_id WHERE m.delivery_status = 'failed' ORDER BY m.created_at DESC LIMIT 20")
|
|
338
|
+
.all();
|
|
339
|
+
if (failed.length === 0)
|
|
340
|
+
return ok('No failed deliveries.');
|
|
341
|
+
const lines = failed.map((f) => `[${f.created_at}] tab:${f.tab_name} retries:${f.retry_count}\n ${f.content.slice(0, 200)}`);
|
|
342
|
+
return ok(lines.join('\n\n'));
|
|
343
|
+
},
|
|
344
|
+
beecork_activity: async (_ctx, args) => {
|
|
345
|
+
const hours = (args || {}).hours || 24;
|
|
346
|
+
const { getActivitySummary, formatActivitySummary } = await import('../observability/analytics.js');
|
|
347
|
+
return ok(formatActivitySummary(getActivitySummary(hours)));
|
|
348
|
+
},
|
|
349
|
+
beecork_export_data: async (ctx, args) => {
|
|
350
|
+
const { type: dataType, days = 30 } = (args || {});
|
|
351
|
+
const since = new Date(Date.now() - days * 86400000).toISOString();
|
|
352
|
+
let data;
|
|
353
|
+
switch (dataType) {
|
|
354
|
+
case 'costs':
|
|
355
|
+
data = ctx.db
|
|
356
|
+
.prepare("SELECT date(created_at) as day, SUM(cost_usd) as cost, COUNT(*) as messages FROM messages WHERE role = 'assistant' AND created_at > ? GROUP BY date(created_at) ORDER BY day")
|
|
357
|
+
.all(since);
|
|
358
|
+
break;
|
|
359
|
+
case 'messages':
|
|
360
|
+
data = ctx.db
|
|
361
|
+
.prepare('SELECT m.role, m.content, m.cost_usd, m.created_at, t.name as tab FROM messages m JOIN tabs t ON t.id = m.tab_id WHERE m.created_at > ? ORDER BY m.created_at DESC LIMIT 500')
|
|
362
|
+
.all(since);
|
|
363
|
+
break;
|
|
364
|
+
case 'crons':
|
|
365
|
+
data = ctx.db.prepare('SELECT * FROM tasks ORDER BY created_at').all();
|
|
366
|
+
break;
|
|
367
|
+
default:
|
|
368
|
+
return fail('Invalid type. Use: costs, messages, or crons');
|
|
369
|
+
}
|
|
370
|
+
return jsonOk(data);
|
|
371
|
+
},
|
|
372
|
+
beecork_handoff: async (_ctx, args) => {
|
|
373
|
+
const { tabName } = (args || {});
|
|
374
|
+
const { exportTab } = await import('../session/handoff.js');
|
|
375
|
+
const exported = exportTab(tabName);
|
|
376
|
+
if (!exported)
|
|
377
|
+
return fail(`Tab "${tabName}" not found`);
|
|
378
|
+
const info = {
|
|
379
|
+
sessionId: exported.sessionId,
|
|
380
|
+
workingDir: exported.workingDir,
|
|
381
|
+
status: exported.status,
|
|
382
|
+
resumeCommand: `beecork attach ${tabName}`,
|
|
383
|
+
manualCommand: `cd ${exported.workingDir} && claude --session-id ${exported.sessionId} --resume`,
|
|
384
|
+
recentMessages: exported.recentMessages.map((m) => ({
|
|
385
|
+
role: m.role,
|
|
386
|
+
preview: m.content.slice(0, 200),
|
|
387
|
+
})),
|
|
388
|
+
};
|
|
389
|
+
return jsonOk(info);
|
|
390
|
+
},
|
|
391
|
+
beecork_delegate: async (ctx, args) => {
|
|
392
|
+
const { tabName, message, returnToTab } = (args || {});
|
|
393
|
+
try {
|
|
394
|
+
const { createDelegation } = await import('../delegation/manager.js');
|
|
395
|
+
const delegation = createDelegation(returnToTab || 'default', tabName, message, returnToTab);
|
|
396
|
+
PendingMessageStore.enqueueDelegation(tabName, message, ctx.db);
|
|
397
|
+
return ok(`Delegated to tab "${tabName}". Result will be sent back to "${delegation.returnToTab}" when complete.\n\nDelegation ID: ${delegation.id}`);
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
return fail(`Delegation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
beecork_delegation_status: async (_ctx, args) => {
|
|
404
|
+
const { tabName } = (args || {});
|
|
405
|
+
const { getPendingDelegations } = await import('../delegation/manager.js');
|
|
406
|
+
const delegations = getPendingDelegations(tabName);
|
|
407
|
+
if (delegations.length === 0)
|
|
408
|
+
return ok('No pending delegations.');
|
|
409
|
+
const lines = delegations.map((d) => `${d.sourceTab} → ${d.targetTab} [${d.status}] (depth ${d.depth})\n "${d.message.slice(0, 100)}"`);
|
|
410
|
+
return ok(lines.join('\n\n'));
|
|
411
|
+
},
|
|
412
|
+
beecork_project_create: async (_ctx, args) => {
|
|
413
|
+
const { name, path: customPath } = (args || {});
|
|
414
|
+
try {
|
|
415
|
+
const { createProject } = await import('../projects/index.js');
|
|
416
|
+
const project = createProject(name, customPath);
|
|
417
|
+
return ok(`Folder "${name}" registered at ${project.path}`);
|
|
418
|
+
}
|
|
419
|
+
catch (err) {
|
|
420
|
+
return fail(err instanceof Error ? err.message : String(err));
|
|
421
|
+
}
|
|
422
|
+
},
|
|
423
|
+
beecork_project_list: async () => {
|
|
424
|
+
const { listProjects } = await import('../projects/index.js');
|
|
425
|
+
const projects = listProjects();
|
|
426
|
+
if (projects.length === 0)
|
|
427
|
+
return ok('No folders discovered. Create one with beecork_project_create.');
|
|
428
|
+
const lines = projects.map((p) => `${p.type === 'category' ? '📁' : '📦'} ${p.name} — ${p.path}`);
|
|
429
|
+
return ok(lines.join('\n'));
|
|
430
|
+
},
|
|
431
|
+
beecork_close_tab: async (ctx, args) => {
|
|
432
|
+
const { tabName } = (args || {});
|
|
433
|
+
// Mark tab stopped so the daemon's recovery loop cleans up the subprocess.
|
|
434
|
+
// MCP server is a child of `claude` and has no TabManager; we can only mutate the DB.
|
|
435
|
+
TabStore.markRunningAsStopped(tabName, ctx.db);
|
|
436
|
+
const deleted = TabStore.deleteWithMessages(tabName, ctx.db);
|
|
437
|
+
if (!deleted)
|
|
438
|
+
return fail(`Tab "${tabName}" not found.`);
|
|
439
|
+
return ok(`Tab "${tabName}" permanently closed.`);
|
|
440
|
+
},
|
|
441
|
+
beecork_generate_image: (ctx, args) => handleMediaGeneration(ctx, 'image', args),
|
|
442
|
+
beecork_generate_video: (ctx, args) => handleMediaGeneration(ctx, 'video', args),
|
|
443
|
+
beecork_generate_audio: (ctx, args) => handleMediaGeneration(ctx, 'audio', args),
|
|
444
|
+
beecork_media_providers: async (ctx) => {
|
|
445
|
+
const generators = await ctx.getGenerators();
|
|
446
|
+
if (generators.length === 0)
|
|
447
|
+
return ok('No media generators configured. Add mediaGenerators to config.json.');
|
|
448
|
+
const lines = generators.map((g) => `- ${g.name} (${g.id}): ${g.supportedTypes.join(', ')}`);
|
|
449
|
+
return ok(lines.join('\n'));
|
|
450
|
+
},
|
|
451
|
+
beecork_knowledge: async (ctx, args) => {
|
|
452
|
+
const { scope: knowledgeScope } = (args || {});
|
|
453
|
+
const { getGlobalKnowledge, getProjectKnowledge, getTabKnowledge, getAllKnowledge, formatKnowledgeForContext, } = await import('../knowledge/index.js');
|
|
454
|
+
let entries;
|
|
455
|
+
if (knowledgeScope === 'global') {
|
|
456
|
+
entries = getGlobalKnowledge();
|
|
457
|
+
}
|
|
458
|
+
else if (knowledgeScope === 'project') {
|
|
459
|
+
const currentTab = TabStore.mostRecent(ctx.db);
|
|
460
|
+
entries = currentTab ? getProjectKnowledge(currentTab.workingDir) : [];
|
|
461
|
+
}
|
|
462
|
+
else if (knowledgeScope === 'tab') {
|
|
463
|
+
const currentTab = TabStore.mostRecent(ctx.db);
|
|
464
|
+
entries = currentTab ? getTabKnowledge(currentTab.name) : [];
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
entries = getAllKnowledge();
|
|
468
|
+
}
|
|
469
|
+
if (entries.length === 0)
|
|
470
|
+
return ok('No knowledge stored yet.');
|
|
471
|
+
return ok(formatKnowledgeForContext(entries));
|
|
472
|
+
},
|
|
473
|
+
beecork_capabilities: async () => {
|
|
474
|
+
const { getAvailablePacks, isEnabled } = await import('../capabilities/index.js');
|
|
475
|
+
const packs = getAvailablePacks();
|
|
476
|
+
const lines = packs.map((p) => `${isEnabled(p.id) ? '✓ enabled' : '○ available'} | ${p.id} — ${p.name}: ${p.description}`);
|
|
477
|
+
return ok(lines.join('\n'));
|
|
478
|
+
},
|
|
479
|
+
beecork_history: async (_ctx, args) => {
|
|
480
|
+
const { date, tabName, limit } = (args || {});
|
|
481
|
+
const { getTimeline, formatTimeline } = await import('../timeline/index.js');
|
|
482
|
+
const events = getTimeline({
|
|
483
|
+
date: date || new Date().toISOString().slice(0, 10),
|
|
484
|
+
tabName,
|
|
485
|
+
limit,
|
|
486
|
+
});
|
|
487
|
+
return ok(formatTimeline(events));
|
|
488
|
+
},
|
|
489
|
+
beecork_replay: async (ctx, args) => {
|
|
490
|
+
const { eventId } = (args || {});
|
|
491
|
+
const { getReplayInfo } = await import('../timeline/index.js');
|
|
492
|
+
const info = getReplayInfo(eventId);
|
|
493
|
+
if (!info)
|
|
494
|
+
return fail('Event not found or not replayable.');
|
|
495
|
+
PendingMessageStore.enqueueReplay(info.tabName, info.message, ctx.db);
|
|
496
|
+
return ok(`Replaying in tab "${info.tabName}": ${info.message.slice(0, 200)}`);
|
|
497
|
+
},
|
|
498
|
+
beecork_store_search: async (_ctx, args) => {
|
|
499
|
+
const { query } = (args || {});
|
|
500
|
+
try {
|
|
501
|
+
const response = await fetch(`https://registry.npmjs.org/-/v1/search?text=beecork+${encodeURIComponent(query)}&size=10`, { signal: AbortSignal.timeout(10000) });
|
|
502
|
+
if (!response.ok)
|
|
503
|
+
return fail('npm registry search failed');
|
|
504
|
+
const data = (await response.json());
|
|
505
|
+
const packages = (data.objects || []).filter((o) => o.package.name.startsWith('beecork-'));
|
|
506
|
+
if (packages.length === 0)
|
|
507
|
+
return ok(`No beecork packages found for "${query}". You can create one with: beecork channel create <name> or beecork media create <name>`);
|
|
508
|
+
const lines = packages.map((o) => `${o.package.name}@${o.package.version} — ${o.package.description || 'No description'}`);
|
|
509
|
+
return ok(`${packages.length} package(s):\n${lines.join('\n')}\n\nInstall: npm install -g <package-name>`);
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
return fail('Failed to search npm registry');
|
|
513
|
+
}
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
// Aliases retained for backward compatibility with older Claude sessions that cached
|
|
517
|
+
// the cron_* tool names. These dispatch to the same handlers as their task_* counterparts.
|
|
518
|
+
HANDLERS.beecork_cron_create = HANDLERS.beecork_task_create;
|
|
519
|
+
HANDLERS.beecork_cron_list = HANDLERS.beecork_task_list;
|
|
520
|
+
HANDLERS.beecork_cron_delete = HANDLERS.beecork_task_delete;
|