beecork 1.3.11 → 1.4.1
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/README.md +3 -4
- package/dist/channels/command-handler.js +0 -14
- package/dist/channels/telegram.js +80 -93
- package/dist/channels/types.d.ts +0 -2
- package/dist/cli/channel.js +0 -1
- package/dist/cli/setup.js +5 -82
- package/dist/config.js +9 -25
- package/dist/daemon.js +5 -21
- package/dist/db/index.js +1 -2
- package/dist/db/migrations.js +10 -11
- package/dist/index.js +0 -23
- package/dist/mcp/server.js +5 -10
- package/dist/session/manager.d.ts +0 -1
- package/dist/session/manager.js +5 -32
- package/dist/tasks/scheduler.js +0 -3
- package/dist/types.d.ts +2 -17
- package/package.json +16 -18
- package/dist/machines/index.d.ts +0 -1
- package/dist/machines/index.js +0 -1
- package/dist/machines/registry.d.ts +0 -15
- package/dist/machines/registry.js +0 -46
- package/dist/memory/extractor.d.ts +0 -5
- package/dist/memory/extractor.js +0 -157
- package/dist/pipe/anthropic-client.d.ts +0 -14
- package/dist/pipe/anthropic-client.js +0 -98
- package/dist/pipe/brain.d.ts +0 -22
- package/dist/pipe/brain.js +0 -160
- package/dist/pipe/memory-store.d.ts +0 -10
- package/dist/pipe/memory-store.js +0 -50
- package/dist/pipe/project-scanner.d.ts +0 -6
- package/dist/pipe/project-scanner.js +0 -26
- package/dist/pipe/types.d.ts +0 -46
- package/dist/pipe/types.js +0 -1
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Message Claude Code from Telegram at 2am — it wakes up, does the work, message
|
|
|
25
25
|
- **Task scheduling** — Tell Claude Code to set up recurring tasks via MCP tools — it wakes up, runs the task, reports back.
|
|
26
26
|
- **Memory** — Cross-session memory so Claude Code never loses context.
|
|
27
27
|
- **MCP server** — 38 tools Claude Code can call to manage tabs, memory, cron jobs, watchers, media, folders, and more.
|
|
28
|
-
- **Smart routing** —
|
|
28
|
+
- **Smart routing** — Messages are automatically routed to the right project folder and tab using project-name detection, sticky conversation context, and learned patterns. No API key required.
|
|
29
29
|
- **Background service** — Runs as a launchd (macOS), systemd (Linux), or Task Scheduler (Windows) service. Starts on login, runs silently.
|
|
30
30
|
|
|
31
31
|
## Quick Start
|
|
@@ -90,7 +90,6 @@ beecork media setup # Configure media generators
|
|
|
90
90
|
beecork activity # View activity summary
|
|
91
91
|
beecork history # Show activity timeline
|
|
92
92
|
beecork folders # List discovered folders
|
|
93
|
-
beecork machines # List registered machines
|
|
94
93
|
beecork templates # List tab templates
|
|
95
94
|
beecork store search <q> # Search community extensions
|
|
96
95
|
beecork store install <p> # Install a community package
|
|
@@ -113,9 +112,9 @@ Local install is `npm install -g beecork`. VPS requires the same plus a properly
|
|
|
113
112
|
## Architecture
|
|
114
113
|
|
|
115
114
|
```
|
|
116
|
-
Telegram/WhatsApp/Discord
|
|
115
|
+
Telegram/WhatsApp/Discord/Webhook
|
|
117
116
|
|
|
|
118
|
-
|
|
117
|
+
Shared pipeline (deterministic router)
|
|
119
118
|
|
|
|
120
119
|
Daemon (always-on)
|
|
121
120
|
|
|
|
@@ -133,20 +133,6 @@ export async function handleSharedCommand(ctx, tabManager) {
|
|
|
133
133
|
return { handled: true, response: `Tab "${tabName}" not found.` };
|
|
134
134
|
return { handled: true, response: formatHandoffInfo(info) };
|
|
135
135
|
}
|
|
136
|
-
// /machines
|
|
137
|
-
if (text === '/machines' || text.startsWith('/machines@')) {
|
|
138
|
-
const { listMachines } = await import('../machines/index.js');
|
|
139
|
-
const machines = listMachines();
|
|
140
|
-
if (machines.length === 0)
|
|
141
|
-
return { handled: true, response: 'No machines registered.' };
|
|
142
|
-
const list = machines.map(m => {
|
|
143
|
-
const primary = m.isPrimary ? ' ⭐' : '';
|
|
144
|
-
const remote = m.host ? ` (${m.sshUser}@${m.host})` : ' (local)';
|
|
145
|
-
const paths = m.projectPaths.slice(0, 3).join(', ');
|
|
146
|
-
return `• ${m.name}${primary}${remote}\n Projects: ${paths}`;
|
|
147
|
-
}).join('\n\n');
|
|
148
|
-
return { handled: true, response: `🖥 ${machines.length} machine(s):\n\n${list}` };
|
|
149
|
-
}
|
|
150
136
|
// /folders (also accept legacy /projects)
|
|
151
137
|
if (text === '/folders' || text === '/projects' || text.startsWith('/folders@') || text.startsWith('/projects@')) {
|
|
152
138
|
const { listProjects } = await import('../projects/index.js');
|
|
@@ -299,7 +299,7 @@ export class TelegramChannel {
|
|
|
299
299
|
await this.sendResponse(chatId, formatted.slice(0, 4000));
|
|
300
300
|
return;
|
|
301
301
|
}
|
|
302
|
-
// Shared command handler (covers /tabs, /stop, /tab, /projects, /project, /newproject, /close, /fresh, /register, /link, /users, /cost, /activity, /handoff
|
|
302
|
+
// Shared command handler (covers /tabs, /stop, /tab, /projects, /project, /newproject, /close, /fresh, /register, /link, /users, /cost, /activity, /handoff)
|
|
303
303
|
const { handleSharedCommand } = await import('./command-handler.js');
|
|
304
304
|
const result = await handleSharedCommand({
|
|
305
305
|
userId: String(userId || 'default'),
|
|
@@ -348,109 +348,96 @@ export class TelegramChannel {
|
|
|
348
348
|
let responseText;
|
|
349
349
|
let responseError;
|
|
350
350
|
let responseTab;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
else {
|
|
363
|
-
// Telegram-specific: streaming message edits
|
|
364
|
-
let streamMsgId = null;
|
|
365
|
-
let streamBuffer = '';
|
|
366
|
-
let lastEditTime = 0;
|
|
367
|
-
// We need the effective tab name for the stream prefix, but it's determined
|
|
368
|
-
// inside the pipeline. Use a mutable ref that the pipeline result will fill.
|
|
369
|
-
let effectiveTabForStream = overrideTabName || tabName;
|
|
370
|
-
const onTextChunk = async (chunk) => {
|
|
371
|
-
streamBuffer += chunk;
|
|
372
|
-
const now = Date.now();
|
|
373
|
-
if (streamBuffer.length < 100 || now - lastEditTime < 1000)
|
|
374
|
-
return;
|
|
375
|
-
lastEditTime = now;
|
|
376
|
-
try {
|
|
377
|
-
const prefix = effectiveTabForStream !== 'default' ? `[${effectiveTabForStream}] ` : '';
|
|
378
|
-
const preview = prefix + streamBuffer.slice(0, 4000) + (streamBuffer.length > 4000 ? '...' : '');
|
|
379
|
-
if (!streamMsgId) {
|
|
380
|
-
const sent = await this.bot.sendMessage(chatId, preview);
|
|
381
|
-
streamMsgId = sent.message_id;
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
384
|
-
await this.bot.editMessageText(preview, { chat_id: chatId, message_id: streamMsgId });
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
catch { /* edit failures are non-critical */ }
|
|
388
|
-
};
|
|
389
|
-
// Shared pipeline handles: routing, media prompt, progress, sendMessage, TTS
|
|
390
|
-
const pipelineResult = await processInboundMessage({
|
|
391
|
-
text,
|
|
392
|
-
media,
|
|
393
|
-
channelId: 'telegram',
|
|
394
|
-
tabManager: this.ctx.tabManager,
|
|
395
|
-
voiceReplyMode: this.ctx.config.voice?.replyMode,
|
|
396
|
-
ttsProvider: this.ttsProvider,
|
|
397
|
-
userId: String(chatId),
|
|
398
|
-
sendProgress: (msg) => {
|
|
399
|
-
this.bot.sendMessage(chatId, msg).catch(() => { });
|
|
400
|
-
},
|
|
401
|
-
overrideTabName,
|
|
402
|
-
onTextChunk,
|
|
403
|
-
});
|
|
404
|
-
// Empty result means no prompt and no media
|
|
405
|
-
if (!pipelineResult.responseText) {
|
|
406
|
-
clearInterval(typingInterval);
|
|
407
|
-
clearTimeout(stillWorkingTimeout);
|
|
351
|
+
// Telegram-specific: streaming message edits
|
|
352
|
+
let streamMsgId = null;
|
|
353
|
+
let streamBuffer = '';
|
|
354
|
+
let lastEditTime = 0;
|
|
355
|
+
// We need the effective tab name for the stream prefix, but it's determined
|
|
356
|
+
// inside the pipeline. Use a mutable ref that the pipeline result will fill.
|
|
357
|
+
let effectiveTabForStream = overrideTabName || tabName;
|
|
358
|
+
const onTextChunk = async (chunk) => {
|
|
359
|
+
streamBuffer += chunk;
|
|
360
|
+
const now = Date.now();
|
|
361
|
+
if (streamBuffer.length < 100 || now - lastEditTime < 1000)
|
|
408
362
|
return;
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
if (streamMsgId && !responseError) {
|
|
417
|
-
clearInterval(typingInterval);
|
|
418
|
-
clearTimeout(stillWorkingTimeout);
|
|
419
|
-
await this.setReaction(chatId, messageId, '✅');
|
|
420
|
-
// Send voice if available (even with streaming)
|
|
421
|
-
if (pipelineResult.audioPath) {
|
|
422
|
-
await this.bot.sendVoice(chatId, pipelineResult.audioPath);
|
|
423
|
-
if (pipelineResult.voiceOnly)
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
try {
|
|
427
|
-
const prefix = responseTab !== 'default' ? `[${responseTab}] ` : '';
|
|
428
|
-
const finalText = prefix + responseText;
|
|
429
|
-
if (finalText.length <= 4096) {
|
|
430
|
-
await this.bot.editMessageText(finalText, { chat_id: chatId, message_id: streamMsgId });
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
await this.sendResponse(chatId, responseText, responseTab);
|
|
434
|
-
}
|
|
363
|
+
lastEditTime = now;
|
|
364
|
+
try {
|
|
365
|
+
const prefix = effectiveTabForStream !== 'default' ? `[${effectiveTabForStream}] ` : '';
|
|
366
|
+
const preview = prefix + streamBuffer.slice(0, 4000) + (streamBuffer.length > 4000 ? '...' : '');
|
|
367
|
+
if (!streamMsgId) {
|
|
368
|
+
const sent = await this.bot.sendMessage(chatId, preview);
|
|
369
|
+
streamMsgId = sent.message_id;
|
|
435
370
|
}
|
|
436
|
-
|
|
437
|
-
await this.
|
|
371
|
+
else {
|
|
372
|
+
await this.bot.editMessageText(preview, { chat_id: chatId, message_id: streamMsgId });
|
|
438
373
|
}
|
|
439
|
-
return;
|
|
440
374
|
}
|
|
441
|
-
|
|
375
|
+
catch { /* edit failures are non-critical */ }
|
|
376
|
+
};
|
|
377
|
+
// Shared pipeline handles: routing, media prompt, progress, sendMessage, TTS
|
|
378
|
+
const pipelineResult = await processInboundMessage({
|
|
379
|
+
text,
|
|
380
|
+
media,
|
|
381
|
+
channelId: 'telegram',
|
|
382
|
+
tabManager: this.ctx.tabManager,
|
|
383
|
+
voiceReplyMode: this.ctx.config.voice?.replyMode,
|
|
384
|
+
ttsProvider: this.ttsProvider,
|
|
385
|
+
userId: String(chatId),
|
|
386
|
+
sendProgress: (msg) => {
|
|
387
|
+
this.bot.sendMessage(chatId, msg).catch(() => { });
|
|
388
|
+
},
|
|
389
|
+
overrideTabName,
|
|
390
|
+
onTextChunk,
|
|
391
|
+
});
|
|
392
|
+
// Empty result means no prompt and no media
|
|
393
|
+
if (!pipelineResult.responseText) {
|
|
394
|
+
clearInterval(typingInterval);
|
|
395
|
+
clearTimeout(stillWorkingTimeout);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
// Update the effective tab for stream prefix (now known)
|
|
399
|
+
effectiveTabForStream = pipelineResult.tabName;
|
|
400
|
+
responseText = pipelineResult.responseText;
|
|
401
|
+
responseError = pipelineResult.isError;
|
|
402
|
+
responseTab = pipelineResult.tabName;
|
|
403
|
+
// Telegram-specific: if streaming was active and no error, edit the final message
|
|
404
|
+
if (streamMsgId && !responseError) {
|
|
405
|
+
clearInterval(typingInterval);
|
|
406
|
+
clearTimeout(stillWorkingTimeout);
|
|
407
|
+
await this.setReaction(chatId, messageId, '✅');
|
|
408
|
+
// Send voice if available (even with streaming)
|
|
442
409
|
if (pipelineResult.audioPath) {
|
|
443
|
-
clearInterval(typingInterval);
|
|
444
|
-
clearTimeout(stillWorkingTimeout);
|
|
445
|
-
await this.setReaction(chatId, messageId, responseError ? '❌' : '✅');
|
|
446
410
|
await this.bot.sendVoice(chatId, pipelineResult.audioPath);
|
|
447
411
|
if (pipelineResult.voiceOnly)
|
|
448
412
|
return;
|
|
449
|
-
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
const prefix = responseTab !== 'default' ? `[${responseTab}] ` : '';
|
|
416
|
+
const finalText = prefix + responseText;
|
|
417
|
+
if (finalText.length <= 4096) {
|
|
418
|
+
await this.bot.editMessageText(finalText, { chat_id: chatId, message_id: streamMsgId });
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
450
421
|
await this.sendResponse(chatId, responseText, responseTab);
|
|
451
|
-
return;
|
|
452
422
|
}
|
|
453
423
|
}
|
|
424
|
+
catch {
|
|
425
|
+
await this.sendResponse(chatId, responseText, responseTab);
|
|
426
|
+
}
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
// Send voice reply if TTS generated audio (non-streaming path)
|
|
430
|
+
if (pipelineResult.audioPath) {
|
|
431
|
+
clearInterval(typingInterval);
|
|
432
|
+
clearTimeout(stillWorkingTimeout);
|
|
433
|
+
await this.setReaction(chatId, messageId, responseError ? '❌' : '✅');
|
|
434
|
+
await this.bot.sendVoice(chatId, pipelineResult.audioPath);
|
|
435
|
+
if (pipelineResult.voiceOnly)
|
|
436
|
+
return;
|
|
437
|
+
if (!responseError) {
|
|
438
|
+
await this.sendResponse(chatId, responseText, responseTab);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
454
441
|
}
|
|
455
442
|
clearInterval(typingInterval);
|
|
456
443
|
clearTimeout(stillWorkingTimeout);
|
package/dist/channels/types.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { TabManager } from '../session/manager.js';
|
|
2
|
-
import type { PipeBrain } from '../pipe/brain.js';
|
|
3
2
|
import type { BeecorkConfig } from '../types.js';
|
|
4
3
|
/** A media file attached to a message */
|
|
5
4
|
export interface MediaAttachment {
|
|
@@ -62,7 +61,6 @@ export interface Channel {
|
|
|
62
61
|
export interface ChannelContext {
|
|
63
62
|
config: BeecorkConfig;
|
|
64
63
|
tabManager: TabManager;
|
|
65
|
-
pipeBrain: PipeBrain | null;
|
|
66
64
|
/** Broadcast notification to all channels + notification providers */
|
|
67
65
|
notifyCallback?: (message: string) => Promise<void>;
|
|
68
66
|
}
|
package/dist/cli/channel.js
CHANGED
package/dist/cli/setup.js
CHANGED
|
@@ -124,24 +124,13 @@ export async function setupWizard() {
|
|
|
124
124
|
tabs: {
|
|
125
125
|
default: {
|
|
126
126
|
workingDir: defaultDir,
|
|
127
|
-
approvalMode: 'yolo',
|
|
128
|
-
approvalTimeoutMinutes: 30,
|
|
129
127
|
},
|
|
130
128
|
},
|
|
131
129
|
memory: {
|
|
132
|
-
enabled: true,
|
|
133
130
|
dbPath: '~/.beecork/memory.db',
|
|
134
131
|
maxLongTermEntries: 1000,
|
|
135
132
|
},
|
|
136
|
-
|
|
137
|
-
enabled: false,
|
|
138
|
-
anthropicApiKey: '',
|
|
139
|
-
routingModel: 'claude-haiku-4-5-20251001',
|
|
140
|
-
complexModel: 'claude-sonnet-4-6-20250514',
|
|
141
|
-
confidenceThreshold: 0.75,
|
|
142
|
-
projectScanPaths: scanPaths,
|
|
143
|
-
maxFollowUps: 5,
|
|
144
|
-
},
|
|
133
|
+
projectScanPaths: scanPaths,
|
|
145
134
|
deployment: 'local',
|
|
146
135
|
};
|
|
147
136
|
// Write everything
|
|
@@ -155,18 +144,10 @@ export async function setupWizard() {
|
|
|
155
144
|
// Generate MCP config
|
|
156
145
|
generateMcpConfig();
|
|
157
146
|
console.log(`✓ MCP config generated at ${getMcpConfigPath()}`);
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
if (config.pipe.enabled) {
|
|
163
|
-
const { scanForProjects } = await import('../pipe/project-scanner.js');
|
|
164
|
-
const { PipeMemoryStore } = await import('../pipe/memory-store.js');
|
|
165
|
-
const projects = scanForProjects(config.pipe.projectScanPaths);
|
|
166
|
-
const store = new PipeMemoryStore();
|
|
167
|
-
for (const p of projects) {
|
|
168
|
-
store.upsertProject(p);
|
|
169
|
-
}
|
|
147
|
+
// Discover projects on disk
|
|
148
|
+
{
|
|
149
|
+
const { discoverProjects } = await import('../projects/index.js');
|
|
150
|
+
const projects = discoverProjects(config.projectScanPaths);
|
|
170
151
|
console.log(`✓ Discovered ${projects.length} projects`);
|
|
171
152
|
closeDb();
|
|
172
153
|
}
|
|
@@ -202,11 +183,6 @@ export async function setupWizard() {
|
|
|
202
183
|
console.log(' beecork dashboard — open web control panel');
|
|
203
184
|
console.log(' beecork quickstart — full getting-started checklist');
|
|
204
185
|
console.log('');
|
|
205
|
-
console.log(' ★ Recommended: Smart folder routing');
|
|
206
|
-
console.log(' If you work in multiple folders, Beecork can auto-detect which');
|
|
207
|
-
console.log(' folder you mean and route messages to the right tab.');
|
|
208
|
-
console.log(' Run: beecork pipe setup');
|
|
209
|
-
console.log('');
|
|
210
186
|
console.log(' Add more channels:');
|
|
211
187
|
console.log(' beecork whatsapp — connect WhatsApp');
|
|
212
188
|
console.log(' beecork discord — connect Discord');
|
|
@@ -260,56 +236,3 @@ function generateMcpConfig() {
|
|
|
260
236
|
};
|
|
261
237
|
fs.writeFileSync(getMcpConfigPath(), JSON.stringify(mcpConfig, null, 2) + '\n');
|
|
262
238
|
}
|
|
263
|
-
const BEECORK_MARKER_START = '<!-- BEECORK START -->';
|
|
264
|
-
const BEECORK_MARKER_END = '<!-- BEECORK END -->';
|
|
265
|
-
const BEECORK_CLAUDE_MD = `${BEECORK_MARKER_START}
|
|
266
|
-
## Beecork — Always-On Tools
|
|
267
|
-
|
|
268
|
-
You have Beecork MCP tools available when running inside Beecork:
|
|
269
|
-
|
|
270
|
-
**Memory:** beecork_remember, beecork_recall — store and search long-term memory
|
|
271
|
-
**Scheduling:** beecork_task_create/list/delete — schedule recurring tasks (aliases: beecork_cron_*)
|
|
272
|
-
**Watchers:** beecork_watch_create/list/delete — monitor conditions and auto-respond
|
|
273
|
-
**Tabs:** beecork_tab_create/list, beecork_send_message — manage virtual tabs
|
|
274
|
-
**Communication:** beecork_notify, beecork_send_media — notify user, send files
|
|
275
|
-
**Delegation:** beecork_delegate, beecork_delegation_status — delegate tasks to other tabs
|
|
276
|
-
**Media:** beecork_generate_image/video/audio — generate media via AI providers
|
|
277
|
-
**Projects:** beecork_project_create/list — manage projects
|
|
278
|
-
**Observability:** beecork_cost, beecork_activity, beecork_status — track spending and activity
|
|
279
|
-
**Handoff:** beecork_handoff — export session for terminal resume
|
|
280
|
-
|
|
281
|
-
### Self-Extension
|
|
282
|
-
|
|
283
|
-
You can install new capabilities for yourself. If the user asks for a tool or media provider that isn't configured:
|
|
284
|
-
|
|
285
|
-
1. Check if a community package exists: \`beecork-media-<name>\` or \`beecork-channel-<name>\` on npm
|
|
286
|
-
2. Install it: \`npm install -g beecork-media-<name>\`
|
|
287
|
-
3. Or create a custom MCP server: write a Node.js script wrapping the API, register in \`~/.beecork/mcp-config.json\`
|
|
288
|
-
4. Tell the user to restart: \`beecork stop && beecork start\`
|
|
289
|
-
|
|
290
|
-
### Guidelines
|
|
291
|
-
|
|
292
|
-
- Always call beecork_recall at the start of complex tasks
|
|
293
|
-
- Always beecork_remember important outcomes and decisions
|
|
294
|
-
- Use beecork_notify for progress on long tasks
|
|
295
|
-
- Use beecork_delegate for independent subtasks that need their own workspace
|
|
296
|
-
${BEECORK_MARKER_END}`;
|
|
297
|
-
function injectClaudeMd() {
|
|
298
|
-
const claudeMdPath = path.join(os.homedir(), '.claude', 'CLAUDE.md');
|
|
299
|
-
const claudeDir = path.dirname(claudeMdPath);
|
|
300
|
-
fs.mkdirSync(claudeDir, { recursive: true });
|
|
301
|
-
let content = '';
|
|
302
|
-
if (fs.existsSync(claudeMdPath)) {
|
|
303
|
-
content = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
304
|
-
// Remove old injection if present
|
|
305
|
-
const startIdx = content.indexOf(BEECORK_MARKER_START);
|
|
306
|
-
const endIdx = content.indexOf(BEECORK_MARKER_END);
|
|
307
|
-
if (startIdx !== -1 && endIdx !== -1) {
|
|
308
|
-
content = content.slice(0, startIdx) + content.slice(endIdx + BEECORK_MARKER_END.length);
|
|
309
|
-
content = content.trim();
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
// Append Beecork section
|
|
313
|
-
content = content + '\n\n' + BEECORK_CLAUDE_MD + '\n';
|
|
314
|
-
fs.writeFileSync(claudeMdPath, content);
|
|
315
|
-
}
|
package/dist/config.js
CHANGED
|
@@ -4,33 +4,18 @@ import path from 'node:path';
|
|
|
4
4
|
import { getConfigPath, expandHome } from './util/paths.js';
|
|
5
5
|
const DEFAULT_TAB_CONFIG = {
|
|
6
6
|
workingDir: os.homedir(),
|
|
7
|
-
approvalMode: 'yolo',
|
|
8
|
-
approvalTimeoutMinutes: 30,
|
|
9
|
-
};
|
|
10
|
-
const DEFAULT_PIPE_CONFIG = {
|
|
11
|
-
enabled: false,
|
|
12
|
-
anthropicApiKey: '',
|
|
13
|
-
routingModel: 'claude-haiku-4-5-20251001',
|
|
14
|
-
complexModel: 'claude-sonnet-4-6-20250514',
|
|
15
|
-
confidenceThreshold: 0.75,
|
|
16
|
-
projectScanPaths: ['~/Coding', '~/Projects', '~/code', '~/dev'],
|
|
17
|
-
maxFollowUps: 5,
|
|
18
7
|
};
|
|
8
|
+
const DEFAULT_PROJECT_SCAN_PATHS = ['~/Coding', '~/Projects', '~/code', '~/dev'];
|
|
19
9
|
/**
|
|
20
|
-
* INTENTIONAL:
|
|
21
|
-
*
|
|
22
|
-
* Users can override
|
|
10
|
+
* INTENTIONAL: --dangerously-skip-permissions is required for unattended operation.
|
|
11
|
+
* Without it, Claude Code would block on every tool call waiting for user input.
|
|
12
|
+
* Users can override defaultFlags in ~/.beecork/config.json.
|
|
23
13
|
*/
|
|
24
14
|
const DEFAULT_CONFIG = {
|
|
25
15
|
telegram: {
|
|
26
16
|
token: '',
|
|
27
17
|
allowedUserIds: [],
|
|
28
18
|
},
|
|
29
|
-
/**
|
|
30
|
-
* INTENTIONAL: --dangerously-skip-permissions is required for unattended operation.
|
|
31
|
-
* Without it, Claude Code would block on every tool call waiting for user input.
|
|
32
|
-
* Users can override defaultFlags in ~/.beecork/config.json.
|
|
33
|
-
*/
|
|
34
19
|
claudeCode: {
|
|
35
20
|
bin: 'claude',
|
|
36
21
|
defaultFlags: ['--dangerously-skip-permissions'],
|
|
@@ -39,11 +24,10 @@ const DEFAULT_CONFIG = {
|
|
|
39
24
|
default: { ...DEFAULT_TAB_CONFIG },
|
|
40
25
|
},
|
|
41
26
|
memory: {
|
|
42
|
-
enabled: true,
|
|
43
27
|
dbPath: '~/.beecork/memory.db',
|
|
44
28
|
maxLongTermEntries: 1000,
|
|
45
29
|
},
|
|
46
|
-
|
|
30
|
+
projectScanPaths: [...DEFAULT_PROJECT_SCAN_PATHS],
|
|
47
31
|
deployment: 'local',
|
|
48
32
|
};
|
|
49
33
|
let cachedConfig = null;
|
|
@@ -116,10 +100,10 @@ function mergeWithDefaults(raw) {
|
|
|
116
100
|
...DEFAULT_CONFIG.memory,
|
|
117
101
|
...raw.memory,
|
|
118
102
|
},
|
|
119
|
-
pipe
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
103
|
+
// Fall back to legacy pipe.projectScanPaths so old configs keep working
|
|
104
|
+
projectScanPaths: raw.projectScanPaths
|
|
105
|
+
?? raw.pipe?.projectScanPaths
|
|
106
|
+
?? [...DEFAULT_PROJECT_SCAN_PATHS],
|
|
123
107
|
deployment: raw.deployment ?? DEFAULT_CONFIG.deployment,
|
|
124
108
|
// Preserve optional config sections (no defaults needed)
|
|
125
109
|
whatsapp: raw.whatsapp,
|
package/dist/daemon.js
CHANGED
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
2
|
import { getConfig } from './config.js';
|
|
4
3
|
import { getDb, closeDb } from './db/index.js';
|
|
5
4
|
import { TabManager } from './session/manager.js';
|
|
6
5
|
import { ChannelRegistry, TelegramChannel, WhatsAppChannel } from './channels/index.js';
|
|
7
6
|
import { TaskScheduler } from './tasks/scheduler.js';
|
|
8
7
|
import { WatcherScheduler } from './watchers/scheduler.js';
|
|
9
|
-
import { PipeBrain } from './pipe/brain.js';
|
|
10
8
|
import { ensureBeecorkDirs, getPidPath, getBeecorkHome } from './util/paths.js';
|
|
11
9
|
import { execSync } from 'node:child_process';
|
|
12
10
|
import { logger } from './util/logger.js';
|
|
@@ -18,7 +16,6 @@ let tabManager;
|
|
|
18
16
|
let channelRegistry;
|
|
19
17
|
let taskScheduler;
|
|
20
18
|
let watcherScheduler;
|
|
21
|
-
let pipeBrain = null;
|
|
22
19
|
let pollInterval;
|
|
23
20
|
let shutdownFn = null;
|
|
24
21
|
const notificationProviders = [];
|
|
@@ -52,14 +49,10 @@ async function main() {
|
|
|
52
49
|
const config = getConfig();
|
|
53
50
|
// 2. Initialize database
|
|
54
51
|
getDb();
|
|
55
|
-
//
|
|
56
|
-
const { registerThisMachine } = await import('./machines/index.js');
|
|
57
|
-
const projectPaths = config.pipe?.projectScanPaths || [os.homedir()];
|
|
58
|
-
registerThisMachine(projectPaths);
|
|
59
|
-
// 2c. Discover projects in workspace
|
|
52
|
+
// 2a. Discover projects in workspace
|
|
60
53
|
try {
|
|
61
54
|
const { discoverProjects, ensureCategory } = await import('./projects/index.js');
|
|
62
|
-
const projects = discoverProjects(config.
|
|
55
|
+
const projects = discoverProjects(config.projectScanPaths);
|
|
63
56
|
ensureCategory('general'); // Ensure default category exists
|
|
64
57
|
logger.info(`Discovered ${projects.length} projects`);
|
|
65
58
|
}
|
|
@@ -80,19 +73,13 @@ async function main() {
|
|
|
80
73
|
logger.info(`PID file written: ${process.pid}`);
|
|
81
74
|
// 4. Create TabManager
|
|
82
75
|
tabManager = new TabManager(config);
|
|
83
|
-
// 5.
|
|
84
|
-
if (config.pipe?.enabled && config.pipe?.anthropicApiKey) {
|
|
85
|
-
pipeBrain = new PipeBrain(config, tabManager);
|
|
86
|
-
const projectCount = await pipeBrain.discoverProjects();
|
|
87
|
-
logger.info(`Pipe brain initialized — ${projectCount} projects discovered`);
|
|
88
|
-
}
|
|
89
|
-
// 6. Ensure default tab
|
|
76
|
+
// 5. Ensure default tab
|
|
90
77
|
tabManager.ensureTab('default');
|
|
91
|
-
//
|
|
78
|
+
// 6. Recover crashed tabs
|
|
92
79
|
await recoverCrashedTabs();
|
|
93
80
|
// Start channels via registry
|
|
94
81
|
channelRegistry = new ChannelRegistry();
|
|
95
|
-
const channelCtx = { config, tabManager,
|
|
82
|
+
const channelCtx = { config, tabManager, notifyCallback: broadcastNotify };
|
|
96
83
|
if (config.telegram?.token) {
|
|
97
84
|
channelRegistry.register(new TelegramChannel(channelCtx));
|
|
98
85
|
}
|
|
@@ -148,9 +135,6 @@ async function main() {
|
|
|
148
135
|
}
|
|
149
136
|
// Wire up broadcast notifications to all active channels
|
|
150
137
|
tabManager.setNotifyCallback(broadcastNotify);
|
|
151
|
-
if (pipeBrain) {
|
|
152
|
-
pipeBrain.setNotifyCallback(broadcastNotify);
|
|
153
|
-
}
|
|
154
138
|
// 9. Start task scheduler
|
|
155
139
|
taskScheduler = new TaskScheduler(tabManager, broadcastNotify);
|
|
156
140
|
taskScheduler.loadAndSchedule();
|
package/dist/db/index.js
CHANGED
|
@@ -63,8 +63,7 @@ export function getDb() {
|
|
|
63
63
|
walInterval = setInterval(() => {
|
|
64
64
|
try {
|
|
65
65
|
db?.pragma('wal_checkpoint(PASSIVE)');
|
|
66
|
-
// Prune old
|
|
67
|
-
db?.exec('DELETE FROM routing_history WHERE created_at < (SELECT created_at FROM routing_history ORDER BY created_at DESC LIMIT 1 OFFSET 999)');
|
|
66
|
+
// Prune old permission history (keep last 1000 entries)
|
|
68
67
|
db?.exec('DELETE FROM permission_history WHERE created_at < (SELECT created_at FROM permission_history ORDER BY created_at DESC LIMIT 1 OFFSET 999)');
|
|
69
68
|
db?.exec("DELETE FROM activity_log WHERE created_at < datetime('now', '-90 days')");
|
|
70
69
|
}
|
package/dist/db/migrations.js
CHANGED
|
@@ -142,17 +142,8 @@ const MIGRATIONS = [
|
|
|
142
142
|
},
|
|
143
143
|
{
|
|
144
144
|
version: 12,
|
|
145
|
-
description: '
|
|
146
|
-
up:
|
|
147
|
-
id TEXT PRIMARY KEY,
|
|
148
|
-
name TEXT NOT NULL,
|
|
149
|
-
host TEXT,
|
|
150
|
-
ssh_user TEXT,
|
|
151
|
-
project_paths TEXT,
|
|
152
|
-
is_primary INTEGER DEFAULT 0,
|
|
153
|
-
last_seen_at TEXT DEFAULT (datetime('now')),
|
|
154
|
-
created_at TEXT DEFAULT (datetime('now'))
|
|
155
|
-
)`,
|
|
145
|
+
description: 'Removed machines table (retired multi-machine scaffolding)',
|
|
146
|
+
up: '',
|
|
156
147
|
},
|
|
157
148
|
{
|
|
158
149
|
version: 13,
|
|
@@ -239,6 +230,14 @@ const MIGRATIONS = [
|
|
|
239
230
|
description: 'Add index on activity_log.created_at',
|
|
240
231
|
up: 'CREATE INDEX IF NOT EXISTS idx_activity_log_created ON activity_log(created_at)',
|
|
241
232
|
},
|
|
233
|
+
{
|
|
234
|
+
version: 22,
|
|
235
|
+
description: 'Add indices on memories for dedup lookups and tab-scoped reads',
|
|
236
|
+
up: `
|
|
237
|
+
CREATE INDEX IF NOT EXISTS idx_memories_content ON memories(content);
|
|
238
|
+
CREATE INDEX IF NOT EXISTS idx_memories_tab_name ON memories(tab_name, created_at);
|
|
239
|
+
`,
|
|
240
|
+
},
|
|
242
241
|
];
|
|
243
242
|
export function runMigrations(db) {
|
|
244
243
|
// Ensure schema_version table exists
|
package/dist/index.js
CHANGED
|
@@ -336,8 +336,6 @@ program
|
|
|
336
336
|
console.log(` workingDir: ${tmpl.workingDir}`);
|
|
337
337
|
if (tmpl.systemPrompt)
|
|
338
338
|
console.log(` systemPrompt: "${tmpl.systemPrompt.slice(0, 80)}${tmpl.systemPrompt.length > 80 ? '...' : ''}"`);
|
|
339
|
-
if (tmpl.approvalMode)
|
|
340
|
-
console.log(` approvalMode: ${tmpl.approvalMode}`);
|
|
341
339
|
}
|
|
342
340
|
console.log('');
|
|
343
341
|
});
|
|
@@ -407,27 +405,6 @@ program
|
|
|
407
405
|
const { getActivitySummary, formatActivitySummary } = await import('./observability/analytics.js');
|
|
408
406
|
console.log(formatActivitySummary(getActivitySummary(h)));
|
|
409
407
|
});
|
|
410
|
-
program
|
|
411
|
-
.command('machines')
|
|
412
|
-
.description('List registered machines')
|
|
413
|
-
.action(async () => {
|
|
414
|
-
const { listMachines } = await import('./machines/index.js');
|
|
415
|
-
const machines = listMachines();
|
|
416
|
-
if (machines.length === 0) {
|
|
417
|
-
console.log('No machines registered. Start the daemon to register this machine.');
|
|
418
|
-
return;
|
|
419
|
-
}
|
|
420
|
-
console.log(`\n${machines.length} machine(s):\n`);
|
|
421
|
-
for (const m of machines) {
|
|
422
|
-
const primary = m.isPrimary ? ' (primary)' : '';
|
|
423
|
-
const remote = m.host ? ` — ${m.sshUser}@${m.host}` : ' — local';
|
|
424
|
-
console.log(` ${m.name}${primary}${remote}`);
|
|
425
|
-
for (const p of m.projectPaths) {
|
|
426
|
-
console.log(` ${p}`);
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
console.log('');
|
|
430
|
-
});
|
|
431
408
|
program
|
|
432
409
|
.command('folders')
|
|
433
410
|
.alias('projects')
|
package/dist/mcp/server.js
CHANGED
|
@@ -322,11 +322,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
322
322
|
required: ['tabName'],
|
|
323
323
|
},
|
|
324
324
|
},
|
|
325
|
-
{
|
|
326
|
-
name: 'beecork_machines',
|
|
327
|
-
description: 'List registered machines and their folder paths. Shows which machine handles which folders.',
|
|
328
|
-
inputSchema: { type: 'object', properties: {} },
|
|
329
|
-
},
|
|
330
325
|
{
|
|
331
326
|
name: 'beecork_delegate',
|
|
332
327
|
description: 'Delegate a task to another tab. The target tab runs independently and the result is automatically sent back to the source tab when complete. Use this for tasks that need their own working directory or context.',
|
|
@@ -493,6 +488,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
493
488
|
}
|
|
494
489
|
// Default: existing tab memory behavior
|
|
495
490
|
const fullContent = category ? `[${category}] ${content}` : content;
|
|
491
|
+
// Dedup: skip insert if an identical fact already exists
|
|
492
|
+
const existing = db.prepare('SELECT id FROM memories WHERE content = ? AND tab_name IS NULL LIMIT 1').get(fullContent);
|
|
493
|
+
if (existing) {
|
|
494
|
+
return ok(`Already remembered: "${fullContent}"`);
|
|
495
|
+
}
|
|
496
496
|
db.prepare('INSERT INTO memories (content, source) VALUES (?, ?)').run(fullContent, 'tool');
|
|
497
497
|
return ok(`Remembered: "${fullContent}"`);
|
|
498
498
|
}
|
|
@@ -761,11 +761,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
761
761
|
};
|
|
762
762
|
return ok(JSON.stringify(info, null, 2));
|
|
763
763
|
}
|
|
764
|
-
case 'beecork_machines': {
|
|
765
|
-
const { listMachines } = await import('../machines/index.js');
|
|
766
|
-
const machines = listMachines();
|
|
767
|
-
return ok(JSON.stringify(machines, null, 2));
|
|
768
|
-
}
|
|
769
764
|
case 'beecork_delegate': {
|
|
770
765
|
const { tabName, message, returnToTab } = args;
|
|
771
766
|
try {
|
|
@@ -23,7 +23,6 @@ export declare class TabManager {
|
|
|
23
23
|
resume?: boolean;
|
|
24
24
|
onTextChunk?: (text: string) => void;
|
|
25
25
|
onToolUse?: (toolName: string, toolInput: Record<string, unknown>) => void;
|
|
26
|
-
skipExtraction?: boolean;
|
|
27
26
|
projectPath?: string;
|
|
28
27
|
_compactionDepth?: number;
|
|
29
28
|
}): Promise<SendResult>;
|