beecork 1.5.0 → 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/command-handler.js +46 -14
- package/dist/channels/discord.d.ts +3 -6
- package/dist/channels/discord.js +40 -23
- package/dist/channels/index.d.ts +1 -1
- package/dist/channels/loader.js +13 -3
- 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/telegram.d.ts +20 -5
- package/dist/channels/telegram.js +177 -42
- package/dist/channels/types.d.ts +11 -28
- package/dist/channels/voice-state.js +3 -1
- package/dist/channels/webhook.d.ts +1 -4
- package/dist/channels/webhook.js +26 -11
- package/dist/channels/whatsapp.d.ts +8 -4
- package/dist/channels/whatsapp.js +65 -29
- 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 +80 -25
- 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.js +5 -10
- package/dist/daemon.js +88 -38
- package/dist/dashboard/html.js +80 -12
- package/dist/dashboard/routes.js +143 -79
- package/dist/dashboard/server.js +5 -1
- package/dist/db/connection.d.ts +29 -0
- package/dist/db/connection.js +37 -0
- package/dist/db/index.js +30 -12
- package/dist/db/migrations.js +84 -28
- package/dist/delegation/manager.js +10 -4
- package/dist/index.js +39 -59
- package/dist/knowledge/manager.js +26 -12
- package/dist/mcp/handlers.js +126 -57
- package/dist/mcp/server.js +20 -10
- package/dist/mcp/tool-definitions.js +68 -20
- 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.js +1 -1
- 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.js +35 -13
- package/dist/projects/index.d.ts +1 -1
- package/dist/projects/index.js +1 -1
- package/dist/projects/manager.d.ts +0 -4
- package/dist/projects/manager.js +51 -28
- package/dist/projects/router.d.ts +2 -0
- package/dist/projects/router.js +70 -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 +17 -5
- package/dist/session/manager.js +153 -146
- 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 +2 -0
- package/dist/session/subprocess.js +33 -11
- package/dist/session/tab-store.js +4 -3
- package/dist/tasks/scheduler.d.ts +7 -0
- package/dist/tasks/scheduler.js +46 -6
- package/dist/tasks/store.js +20 -6
- package/dist/timeline/logger.js +3 -1
- package/dist/timeline/query.js +9 -3
- package/dist/types.d.ts +34 -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 +1 -0
- package/dist/util/paths.js +12 -2
- package/dist/util/retry.js +1 -1
- package/dist/util/text.js +13 -7
- 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 +9 -2
- package/package.json +6 -1
- package/dist/session/tool-classifier.d.ts +0 -4
- package/dist/session/tool-classifier.js +0 -56
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* WhatsApp integration via @whiskeysockets/baileys.
|
|
3
|
+
*
|
|
4
|
+
* baileys is a peer-optional dependency loaded by dynamic import, and its
|
|
5
|
+
* runtime types are intentionally loose (the lib uses heavy union/index
|
|
6
|
+
* types that don't survive serialization across the dynamic-import boundary).
|
|
7
|
+
* Trying to type every variant of Baileys' message/connection shapes would
|
|
8
|
+
* either pull baileys into the static graph (bloating non-WhatsApp installs)
|
|
9
|
+
* or require maintaining a parallel shim. We accept `any` at this trust
|
|
10
|
+
* boundary; runtime validation lives in the descriptors table inside
|
|
11
|
+
* messages.upsert.
|
|
12
|
+
*/
|
|
13
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
1
14
|
import fs from 'node:fs';
|
|
2
15
|
import { logger } from '../util/logger.js';
|
|
3
16
|
import { saveMedia, isOversized } from '../media/store.js';
|
|
@@ -11,8 +24,6 @@ export class WhatsAppChannel {
|
|
|
11
24
|
id = 'whatsapp';
|
|
12
25
|
name = 'WhatsApp';
|
|
13
26
|
maxMessageLength = WHATSAPP_MAX_LENGTH;
|
|
14
|
-
supportsStreaming = false;
|
|
15
|
-
supportsMedia = true;
|
|
16
27
|
sock = null;
|
|
17
28
|
ctx;
|
|
18
29
|
allowedNumbers;
|
|
@@ -24,12 +35,42 @@ export class WhatsAppChannel {
|
|
|
24
35
|
this.ctx = ctx;
|
|
25
36
|
this.allowedNumbers = new Set(ctx.config.whatsapp?.allowedNumbers ?? []);
|
|
26
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Schedule the next reconnect with exponential backoff. Unlike the previous
|
|
40
|
+
* inline setTimeout, this path retries when `start()` itself rejects (auth
|
|
41
|
+
* failure, baileys init throw, etc.) instead of going permanently silent
|
|
42
|
+
* after a single failed attempt.
|
|
43
|
+
*/
|
|
44
|
+
scheduleReconnect() {
|
|
45
|
+
this.reconnectAttempts++;
|
|
46
|
+
if (this.reconnectAttempts > this.maxReconnectAttempts) {
|
|
47
|
+
logger.error(`WhatsApp reconnect failed after ${this.maxReconnectAttempts} attempts, giving up`);
|
|
48
|
+
this.ctx
|
|
49
|
+
.notifyCallback?.('⚠️ WhatsApp disconnected after 10 reconnection attempts. Restart daemon to reconnect.')
|
|
50
|
+
.catch((err) => logger.error('Failed to send WhatsApp disconnect notification:', err));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const delayIdx = Math.min(this.reconnectAttempts - 1, this.backoffDelays.length - 1);
|
|
54
|
+
const delay = this.backoffDelays[delayIdx];
|
|
55
|
+
logger.warn(`WhatsApp connection closed, reconnecting in ${delay / 1000}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
this.start().catch((err) => {
|
|
58
|
+
logger.error('WhatsApp reconnect attempt failed:', err);
|
|
59
|
+
// Recurse via scheduleReconnect so backoff keeps escalating instead of
|
|
60
|
+
// silently dropping the reconnect chain after one failed start().
|
|
61
|
+
this.scheduleReconnect();
|
|
62
|
+
});
|
|
63
|
+
}, delay);
|
|
64
|
+
}
|
|
27
65
|
async start() {
|
|
28
66
|
// Initialize voice providers (STT + TTS)
|
|
29
67
|
this.voice.init(this.ctx.config);
|
|
30
68
|
try {
|
|
31
|
-
const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, downloadMediaMessage, fetchLatestBaileysVersion } = await import('@whiskeysockets/baileys');
|
|
32
|
-
const
|
|
69
|
+
const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, downloadMediaMessage, fetchLatestBaileysVersion, } = await import('@whiskeysockets/baileys');
|
|
70
|
+
const { getWhatsappSessionPath } = await import('../util/paths.js');
|
|
71
|
+
// Use the centralized path helper — the previous fallback hard-coded
|
|
72
|
+
// process.env.HOME which bypassed BEECORK_HOME for tests/isolation.
|
|
73
|
+
const sessionPath = this.ctx.config.whatsapp?.sessionPath ?? getWhatsappSessionPath();
|
|
33
74
|
fs.mkdirSync(sessionPath, { recursive: true, mode: 0o700 });
|
|
34
75
|
const { state, saveCreds } = await useMultiFileAuthState(sessionPath);
|
|
35
76
|
const { version } = await fetchLatestBaileysVersion().catch(() => ({ version: undefined }));
|
|
@@ -56,21 +97,7 @@ export class WhatsAppChannel {
|
|
|
56
97
|
if (connection === 'close') {
|
|
57
98
|
const reason = lastDisconnect?.error?.output?.statusCode;
|
|
58
99
|
if (reason !== DisconnectReason.loggedOut) {
|
|
59
|
-
this.
|
|
60
|
-
if (this.reconnectAttempts > this.maxReconnectAttempts) {
|
|
61
|
-
logger.error(`WhatsApp reconnect failed after ${this.maxReconnectAttempts} attempts, giving up`);
|
|
62
|
-
this.ctx.notifyCallback?.('⚠️ WhatsApp disconnected after 10 reconnection attempts. Restart daemon to reconnect.')
|
|
63
|
-
.catch(err => logger.error('Failed to send WhatsApp disconnect notification:', err));
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const delayIdx = Math.min(this.reconnectAttempts - 1, this.backoffDelays.length - 1);
|
|
67
|
-
const delay = this.backoffDelays[delayIdx];
|
|
68
|
-
logger.warn(`WhatsApp connection closed, reconnecting in ${delay / 1000}s (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})...`);
|
|
69
|
-
setTimeout(() => {
|
|
70
|
-
this.start().catch(err => {
|
|
71
|
-
logger.error('WhatsApp reconnect failed:', err);
|
|
72
|
-
});
|
|
73
|
-
}, delay);
|
|
100
|
+
this.scheduleReconnect();
|
|
74
101
|
}
|
|
75
102
|
else {
|
|
76
103
|
logger.error('WhatsApp logged out. Please re-scan QR code.');
|
|
@@ -90,13 +117,18 @@ export class WhatsAppChannel {
|
|
|
90
117
|
return;
|
|
91
118
|
// Rate limit check
|
|
92
119
|
if (!inboundLimiter.check(this.id)) {
|
|
93
|
-
await sock
|
|
120
|
+
await sock
|
|
121
|
+
.sendMessage(sender, {
|
|
122
|
+
text: "I'm receiving too many messages right now. Please wait a moment.",
|
|
123
|
+
})
|
|
124
|
+
.catch(() => { });
|
|
94
125
|
return;
|
|
95
126
|
}
|
|
96
127
|
const text = msg.message.conversation ||
|
|
97
128
|
msg.message.extendedTextMessage?.text ||
|
|
98
129
|
msg.message.imageMessage?.caption ||
|
|
99
|
-
msg.message.videoMessage?.caption ||
|
|
130
|
+
msg.message.videoMessage?.caption ||
|
|
131
|
+
'';
|
|
100
132
|
const descriptors = [
|
|
101
133
|
{
|
|
102
134
|
key: 'imageMessage',
|
|
@@ -160,7 +192,7 @@ export class WhatsAppChannel {
|
|
|
160
192
|
const waResults = await Promise.allSettled(waDownloadTasks);
|
|
161
193
|
const media = waResults
|
|
162
194
|
.filter((r) => r.status === 'fulfilled' && r.value !== null)
|
|
163
|
-
.map(r => r.value);
|
|
195
|
+
.map((r) => r.value);
|
|
164
196
|
// Transcribe voice messages if STT is configured
|
|
165
197
|
await this.voice.transcribe(media);
|
|
166
198
|
if (!text && media.length === 0)
|
|
@@ -202,7 +234,11 @@ export class WhatsAppChannel {
|
|
|
202
234
|
return;
|
|
203
235
|
// Send voice reply if TTS generated audio
|
|
204
236
|
if (pipelineResult.audioPath) {
|
|
205
|
-
await sock.sendMessage(sender, {
|
|
237
|
+
await sock.sendMessage(sender, {
|
|
238
|
+
audio: { url: pipelineResult.audioPath },
|
|
239
|
+
mimetype: 'audio/ogg; codecs=opus',
|
|
240
|
+
ptt: true,
|
|
241
|
+
});
|
|
206
242
|
if (pipelineResult.voiceOnly)
|
|
207
243
|
return;
|
|
208
244
|
}
|
|
@@ -210,7 +246,10 @@ export class WhatsAppChannel {
|
|
|
210
246
|
}
|
|
211
247
|
catch (err) {
|
|
212
248
|
logger.error('WhatsApp message handler error:', err);
|
|
213
|
-
await sock
|
|
249
|
+
await sock
|
|
250
|
+
.sendMessage(sender, {
|
|
251
|
+
text: 'Something went wrong processing your message. Check daemon logs for details.',
|
|
252
|
+
})
|
|
214
253
|
.catch((sendErr) => logger.error('WhatsApp: failed to send fallback error message:', sendErr));
|
|
215
254
|
}
|
|
216
255
|
});
|
|
@@ -236,7 +275,7 @@ export class WhatsAppChannel {
|
|
|
236
275
|
text,
|
|
237
276
|
maxLength: WHATSAPP_MAX_LENGTH,
|
|
238
277
|
retryLabel: 'whatsapp-send',
|
|
239
|
-
sendChunk: chunk => sock.sendMessage(peerId, { text: chunk }),
|
|
278
|
+
sendChunk: (chunk) => sock.sendMessage(peerId, { text: chunk }),
|
|
240
279
|
});
|
|
241
280
|
}
|
|
242
281
|
async sendNotification(message, _urgent) {
|
|
@@ -259,9 +298,6 @@ export class WhatsAppChannel {
|
|
|
259
298
|
const status = active ? 'composing' : 'paused';
|
|
260
299
|
await sock.sendPresenceUpdate(status, peerId).catch(() => { });
|
|
261
300
|
}
|
|
262
|
-
onMessage(_handler) {
|
|
263
|
-
// Messages are handled directly in start()
|
|
264
|
-
}
|
|
265
301
|
// ─── Private ───
|
|
266
302
|
async sendResponse(jid, text, tabName) {
|
|
267
303
|
const sock = this.sock;
|
|
@@ -271,7 +307,7 @@ export class WhatsAppChannel {
|
|
|
271
307
|
tabName,
|
|
272
308
|
maxLength: WHATSAPP_MAX_LENGTH,
|
|
273
309
|
retryLabel: 'whatsapp-send',
|
|
274
|
-
sendChunk: chunk => sock.sendMessage(jid, { text: chunk }),
|
|
310
|
+
sendChunk: (chunk) => sock.sendMessage(jid, { text: chunk }),
|
|
275
311
|
});
|
|
276
312
|
}
|
|
277
313
|
catch (err) {
|
package/dist/cli/capabilities.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import readline from 'node:readline';
|
|
2
2
|
function ask(rl, question, defaultValue) {
|
|
3
3
|
const prompt = defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `;
|
|
4
|
-
return new Promise(r => rl.question(prompt, a => r(a.trim() || defaultValue || '')));
|
|
4
|
+
return new Promise((r) => rl.question(prompt, (a) => r(a.trim() || defaultValue || '')));
|
|
5
5
|
}
|
|
6
6
|
export async function enableCapability(packId) {
|
|
7
7
|
const { getAvailablePacks, isEnabled, enablePack } = await import('../capabilities/index.js');
|
|
8
8
|
const packs = getAvailablePacks();
|
|
9
|
-
const pack = packs.find(p => p.id === packId);
|
|
9
|
+
const pack = packs.find((p) => p.id === packId);
|
|
10
10
|
if (!pack) {
|
|
11
11
|
console.log(`Unknown capability: "${packId}"`);
|
|
12
|
-
console.log('Available: ' + packs.map(p => p.id).join(', '));
|
|
12
|
+
console.log('Available: ' + packs.map((p) => p.id).join(', '));
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
15
|
if (isEnabled(packId)) {
|
|
@@ -46,7 +46,7 @@ export async function listCapabilities() {
|
|
|
46
46
|
web: 'Web',
|
|
47
47
|
};
|
|
48
48
|
for (const category of categories) {
|
|
49
|
-
const categoryPacks = packs.filter(p => p.category === category);
|
|
49
|
+
const categoryPacks = packs.filter((p) => p.category === category);
|
|
50
50
|
if (categoryPacks.length === 0)
|
|
51
51
|
continue;
|
|
52
52
|
console.log(` ${categoryNames[category]}:`);
|
package/dist/cli/channel.js
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
const CHANNEL_PREFIX = 'beecork-channel-';
|
|
5
|
+
const SAFE_NPM_PACKAGE = /^[@a-zA-Z0-9_/.-]+$/;
|
|
5
6
|
export function channelInstall(packageName) {
|
|
6
7
|
// Normalize name
|
|
7
|
-
const fullName = packageName.startsWith(CHANNEL_PREFIX)
|
|
8
|
+
const fullName = packageName.startsWith(CHANNEL_PREFIX)
|
|
9
|
+
? packageName
|
|
10
|
+
: `${CHANNEL_PREFIX}${packageName}`;
|
|
11
|
+
if (!SAFE_NPM_PACKAGE.test(fullName)) {
|
|
12
|
+
console.error(`Invalid package name: ${fullName}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
8
15
|
console.log(`Installing channel: ${fullName}...`);
|
|
9
16
|
try {
|
|
10
|
-
|
|
17
|
+
execFileSync('npm', ['install', '-g', fullName], { stdio: 'inherit' });
|
|
11
18
|
console.log(`\nChannel "${fullName}" installed.`);
|
|
12
19
|
console.log('Restart the daemon to activate: beecork stop && beecork start');
|
|
13
20
|
}
|
|
14
|
-
catch
|
|
21
|
+
catch {
|
|
15
22
|
console.error(`Failed to install ${fullName}. Check the package name and try again.`);
|
|
16
23
|
process.exit(1);
|
|
17
24
|
}
|
|
@@ -173,8 +180,11 @@ npm publish
|
|
|
173
180
|
}
|
|
174
181
|
export function channelList() {
|
|
175
182
|
try {
|
|
176
|
-
const output =
|
|
177
|
-
|
|
183
|
+
const output = execFileSync('npm', ['list', '-g', '--depth=0'], {
|
|
184
|
+
encoding: 'utf-8',
|
|
185
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
186
|
+
});
|
|
187
|
+
const lines = output.split('\n').filter((line) => line.includes(CHANNEL_PREFIX));
|
|
178
188
|
if (lines.length === 0) {
|
|
179
189
|
console.log('No community channels installed.');
|
|
180
190
|
console.log(`Install one: beecork channel install <name>`);
|
package/dist/cli/commands.js
CHANGED
|
@@ -4,6 +4,7 @@ import { spawn, execSync } from 'node:child_process';
|
|
|
4
4
|
import { getDb, closeDb } from '../db/index.js';
|
|
5
5
|
import { getConfig } from '../config.js';
|
|
6
6
|
import { TaskStore } from '../tasks/store.js';
|
|
7
|
+
import { TabStore } from '../session/tab-store.js';
|
|
7
8
|
import { getDaemonPid, timeAgo } from './helpers.js';
|
|
8
9
|
import { startService, stopService } from '../service/install.js';
|
|
9
10
|
import { getPidPath, getLogsDir } from '../util/paths.js';
|
|
@@ -71,17 +72,17 @@ export async function showStatus() {
|
|
|
71
72
|
console.log(`Daemon: ${pid ? `running (PID ${pid})` : 'stopped'}`);
|
|
72
73
|
console.log(`Deployment: ${config.deployment}`);
|
|
73
74
|
try {
|
|
74
|
-
|
|
75
|
-
const tabs =
|
|
75
|
+
getDb(); // ensure DB initialized
|
|
76
|
+
const tabs = TabStore.listAll();
|
|
76
77
|
console.log(`\nTabs (${tabs.length}):`);
|
|
77
78
|
for (const tab of tabs) {
|
|
78
|
-
const ago = timeAgo(tab.
|
|
79
|
+
const ago = timeAgo(tab.lastActivityAt);
|
|
79
80
|
const pidInfo = tab.pid ? ` (PID ${tab.pid})` : '';
|
|
80
81
|
console.log(` ${tab.name.padEnd(20)} ${tab.status.padEnd(12)} last active: ${ago}${pidInfo}`);
|
|
81
82
|
}
|
|
82
83
|
const store = new TaskStore();
|
|
83
84
|
const jobs = store.list();
|
|
84
|
-
const activeJobs = jobs.filter(j => j.enabled);
|
|
85
|
+
const activeJobs = jobs.filter((j) => j.enabled);
|
|
85
86
|
console.log(`\nTasks: ${activeJobs.length} active (${jobs.length} total)`);
|
|
86
87
|
if (activeJobs.length > 0) {
|
|
87
88
|
for (const job of activeJobs.slice(0, 5)) {
|
|
@@ -97,8 +98,8 @@ export async function showStatus() {
|
|
|
97
98
|
console.log('');
|
|
98
99
|
}
|
|
99
100
|
export async function listTabs() {
|
|
100
|
-
|
|
101
|
-
const tabs =
|
|
101
|
+
requireDb();
|
|
102
|
+
const tabs = TabStore.listAll();
|
|
102
103
|
closeDb();
|
|
103
104
|
if (tabs.length === 0) {
|
|
104
105
|
console.log('No tabs.');
|
|
@@ -106,8 +107,8 @@ export async function listTabs() {
|
|
|
106
107
|
}
|
|
107
108
|
console.log(`\nTabs (${tabs.length}):\n`);
|
|
108
109
|
for (const tab of tabs) {
|
|
109
|
-
const ago = timeAgo(tab.
|
|
110
|
-
console.log(` ${tab.name.padEnd(20)} [${tab.status}] dir:${tab.
|
|
110
|
+
const ago = timeAgo(tab.lastActivityAt);
|
|
111
|
+
console.log(` ${tab.name.padEnd(20)} [${tab.status}] dir:${tab.workingDir} — ${ago}`);
|
|
111
112
|
}
|
|
112
113
|
console.log('');
|
|
113
114
|
}
|
|
@@ -186,7 +187,9 @@ export async function deleteWatcher(id) {
|
|
|
186
187
|
}
|
|
187
188
|
export async function listMemories() {
|
|
188
189
|
const db = requireDb();
|
|
189
|
-
const memories = db
|
|
190
|
+
const memories = db
|
|
191
|
+
.prepare('SELECT * FROM memories ORDER BY created_at DESC LIMIT 50')
|
|
192
|
+
.all();
|
|
190
193
|
closeDb();
|
|
191
194
|
if (memories.length === 0) {
|
|
192
195
|
console.log('No memories stored.');
|
package/dist/cli/doctor.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import { getConfig } from '../config.js';
|
|
4
|
-
import { getDbPath, getPidPath, getBeecorkHome } from '../util/paths.js';
|
|
4
|
+
import { getDbPath, getPidPath, getBeecorkHome, getConfigPath, getMcpConfigPath, getWhatsappSessionPath, } from '../util/paths.js';
|
|
5
|
+
import { getMediaDir } from '../media/store.js';
|
|
5
6
|
export async function runDoctor() {
|
|
6
7
|
const checks = [];
|
|
7
8
|
// 1. Check Claude binary
|
|
@@ -19,10 +20,14 @@ export async function runDoctor() {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
catch {
|
|
22
|
-
checks.push({
|
|
23
|
+
checks.push({
|
|
24
|
+
name: 'Claude Code',
|
|
25
|
+
status: 'fail',
|
|
26
|
+
message: 'Claude Code binary not found. Install: npm install -g @anthropic-ai/claude-code',
|
|
27
|
+
});
|
|
23
28
|
}
|
|
24
29
|
// 2. Check config file
|
|
25
|
-
const configPath =
|
|
30
|
+
const configPath = getConfigPath();
|
|
26
31
|
if (fs.existsSync(configPath)) {
|
|
27
32
|
try {
|
|
28
33
|
const config = getConfig();
|
|
@@ -30,17 +35,31 @@ export async function runDoctor() {
|
|
|
30
35
|
// 3. Check Telegram token
|
|
31
36
|
if (config.telegram?.token) {
|
|
32
37
|
try {
|
|
33
|
-
const resp = await fetch(`https://api.telegram.org/bot${config.telegram.token}/getMe`, {
|
|
38
|
+
const resp = await fetch(`https://api.telegram.org/bot${config.telegram.token}/getMe`, {
|
|
39
|
+
signal: AbortSignal.timeout(10000),
|
|
40
|
+
});
|
|
34
41
|
if (resp.ok) {
|
|
35
|
-
const data = await resp.json();
|
|
36
|
-
checks.push({
|
|
42
|
+
const data = (await resp.json());
|
|
43
|
+
checks.push({
|
|
44
|
+
name: 'Telegram bot',
|
|
45
|
+
status: 'pass',
|
|
46
|
+
message: `@${data.result.username}`,
|
|
47
|
+
});
|
|
37
48
|
}
|
|
38
49
|
else {
|
|
39
|
-
checks.push({
|
|
50
|
+
checks.push({
|
|
51
|
+
name: 'Telegram bot',
|
|
52
|
+
status: 'fail',
|
|
53
|
+
message: 'Invalid token — getMe returned error',
|
|
54
|
+
});
|
|
40
55
|
}
|
|
41
56
|
}
|
|
42
|
-
catch
|
|
43
|
-
checks.push({
|
|
57
|
+
catch {
|
|
58
|
+
checks.push({
|
|
59
|
+
name: 'Telegram bot',
|
|
60
|
+
status: 'warn',
|
|
61
|
+
message: 'Could not reach Telegram API',
|
|
62
|
+
});
|
|
44
63
|
}
|
|
45
64
|
}
|
|
46
65
|
else {
|
|
@@ -48,12 +67,16 @@ export async function runDoctor() {
|
|
|
48
67
|
}
|
|
49
68
|
// 4. Check WhatsApp session
|
|
50
69
|
if (config.whatsapp?.enabled) {
|
|
51
|
-
const sessionPath = config.whatsapp.sessionPath ||
|
|
70
|
+
const sessionPath = config.whatsapp.sessionPath || getWhatsappSessionPath();
|
|
52
71
|
if (fs.existsSync(sessionPath) && fs.readdirSync(sessionPath).length > 0) {
|
|
53
72
|
checks.push({ name: 'WhatsApp session', status: 'pass', message: sessionPath });
|
|
54
73
|
}
|
|
55
74
|
else {
|
|
56
|
-
checks.push({
|
|
75
|
+
checks.push({
|
|
76
|
+
name: 'WhatsApp session',
|
|
77
|
+
status: 'warn',
|
|
78
|
+
message: 'No session data — QR scan needed',
|
|
79
|
+
});
|
|
57
80
|
}
|
|
58
81
|
}
|
|
59
82
|
}
|
|
@@ -62,14 +85,18 @@ export async function runDoctor() {
|
|
|
62
85
|
}
|
|
63
86
|
}
|
|
64
87
|
else {
|
|
65
|
-
checks.push({
|
|
88
|
+
checks.push({
|
|
89
|
+
name: 'Config',
|
|
90
|
+
status: 'fail',
|
|
91
|
+
message: `Not found at ${configPath}. Run: beecork setup`,
|
|
92
|
+
});
|
|
66
93
|
}
|
|
67
94
|
// 5. Check database
|
|
68
95
|
const dbPath = getDbPath();
|
|
69
96
|
if (fs.existsSync(dbPath)) {
|
|
70
97
|
try {
|
|
71
|
-
const
|
|
72
|
-
const db =
|
|
98
|
+
const { openDb } = await import('../db/connection.js');
|
|
99
|
+
const db = openDb(dbPath, { readonly: true });
|
|
73
100
|
const integrity = db.pragma('integrity_check');
|
|
74
101
|
if (integrity[0]?.integrity_check === 'ok') {
|
|
75
102
|
const size = (fs.statSync(dbPath).size / 1024).toFixed(0);
|
|
@@ -85,7 +112,11 @@ export async function runDoctor() {
|
|
|
85
112
|
}
|
|
86
113
|
}
|
|
87
114
|
else {
|
|
88
|
-
checks.push({
|
|
115
|
+
checks.push({
|
|
116
|
+
name: 'Database',
|
|
117
|
+
status: 'warn',
|
|
118
|
+
message: 'No database yet — starts on first run',
|
|
119
|
+
});
|
|
89
120
|
}
|
|
90
121
|
// 6. Check daemon
|
|
91
122
|
const pidPath = getPidPath();
|
|
@@ -96,7 +127,11 @@ export async function runDoctor() {
|
|
|
96
127
|
checks.push({ name: 'Daemon', status: 'pass', message: `Running (PID ${pid})` });
|
|
97
128
|
}
|
|
98
129
|
catch {
|
|
99
|
-
checks.push({
|
|
130
|
+
checks.push({
|
|
131
|
+
name: 'Daemon',
|
|
132
|
+
status: 'warn',
|
|
133
|
+
message: `Stale PID file (PID ${pid} not running)`,
|
|
134
|
+
});
|
|
100
135
|
}
|
|
101
136
|
}
|
|
102
137
|
else {
|
|
@@ -104,7 +139,7 @@ export async function runDoctor() {
|
|
|
104
139
|
}
|
|
105
140
|
// 7. Check disk space for media
|
|
106
141
|
try {
|
|
107
|
-
const mediaDir =
|
|
142
|
+
const mediaDir = getMediaDir();
|
|
108
143
|
const homeDir = getBeecorkHome();
|
|
109
144
|
// Simple check: can we write a temp file?
|
|
110
145
|
const testPath = `${homeDir}/.doctor-test`;
|
|
@@ -112,25 +147,45 @@ export async function runDoctor() {
|
|
|
112
147
|
fs.unlinkSync(testPath);
|
|
113
148
|
if (fs.existsSync(mediaDir)) {
|
|
114
149
|
const files = fs.readdirSync(mediaDir);
|
|
115
|
-
checks.push({
|
|
150
|
+
checks.push({
|
|
151
|
+
name: 'Media dir',
|
|
152
|
+
status: 'pass',
|
|
153
|
+
message: `${files.length} files in ${mediaDir}`,
|
|
154
|
+
});
|
|
116
155
|
}
|
|
117
156
|
else {
|
|
118
|
-
checks.push({
|
|
157
|
+
checks.push({
|
|
158
|
+
name: 'Media dir',
|
|
159
|
+
status: 'pass',
|
|
160
|
+
message: 'Not created yet (created on first media)',
|
|
161
|
+
});
|
|
119
162
|
}
|
|
120
163
|
}
|
|
121
164
|
catch {
|
|
122
|
-
checks.push({
|
|
165
|
+
checks.push({
|
|
166
|
+
name: 'Disk space',
|
|
167
|
+
status: 'fail',
|
|
168
|
+
message: 'Cannot write to beecork home directory',
|
|
169
|
+
});
|
|
123
170
|
}
|
|
124
171
|
// 8. Check MCP config
|
|
125
|
-
const mcpConfigPath =
|
|
172
|
+
const mcpConfigPath = getMcpConfigPath();
|
|
126
173
|
if (fs.existsSync(mcpConfigPath)) {
|
|
127
174
|
try {
|
|
128
175
|
const mcpConfig = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf-8'));
|
|
129
176
|
const serverCount = Object.keys(mcpConfig.mcpServers || {}).length;
|
|
130
|
-
checks.push({
|
|
177
|
+
checks.push({
|
|
178
|
+
name: 'MCP config',
|
|
179
|
+
status: 'pass',
|
|
180
|
+
message: `${serverCount} server(s) configured`,
|
|
181
|
+
});
|
|
131
182
|
}
|
|
132
183
|
catch {
|
|
133
|
-
checks.push({
|
|
184
|
+
checks.push({
|
|
185
|
+
name: 'MCP config',
|
|
186
|
+
status: 'fail',
|
|
187
|
+
message: 'Invalid JSON in mcp-config.json',
|
|
188
|
+
});
|
|
134
189
|
}
|
|
135
190
|
}
|
|
136
191
|
else {
|
|
@@ -145,8 +200,8 @@ export async function runDoctor() {
|
|
|
145
200
|
for (const check of checks) {
|
|
146
201
|
console.log(` ${icons[check.status]} ${check.name}: ${check.message}`);
|
|
147
202
|
}
|
|
148
|
-
const fails = checks.filter(c => c.status === 'fail').length;
|
|
149
|
-
const warns = checks.filter(c => c.status === 'warn').length;
|
|
203
|
+
const fails = checks.filter((c) => c.status === 'fail').length;
|
|
204
|
+
const warns = checks.filter((c) => c.status === 'warn').length;
|
|
150
205
|
console.log(`\n ${checks.length} checks: ${checks.length - fails - warns} passed, ${warns} warnings, ${fails} failures\n`);
|
|
151
206
|
if (fails > 0)
|
|
152
207
|
process.exit(1);
|
package/dist/cli/handoff.d.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
role: string;
|
|
9
|
-
content: string;
|
|
10
|
-
}>;
|
|
11
|
-
}
|
|
12
|
-
export declare function exportTab(tabName: string): TabInfo | null;
|
|
1
|
+
import { exportTab, formatHandoffInfo, type TabHandoffInfo } from '../session/handoff.js';
|
|
2
|
+
export { exportTab, formatHandoffInfo, type TabHandoffInfo };
|
|
3
|
+
/**
|
|
4
|
+
* CLI-only flow: print the handoff info, spawn claude with `--resume`, and
|
|
5
|
+
* inherit stdio so the user is dropped into an interactive session. Calls
|
|
6
|
+
* `process.exit` on subprocess exit — only safe from the CLI entry point.
|
|
7
|
+
*/
|
|
13
8
|
export declare function attachTab(tabName: string): void;
|
|
14
|
-
export declare function formatHandoffInfo(info: TabInfo): string;
|
|
15
|
-
export {};
|
package/dist/cli/handoff.js
CHANGED
|
@@ -1,21 +1,13 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { getDb } from '../db/index.js';
|
|
3
2
|
import { getConfig } from '../config.js';
|
|
4
|
-
import {
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
sessionId: tab.sessionId,
|
|
13
|
-
workingDir: tab.workingDir,
|
|
14
|
-
status: tab.status,
|
|
15
|
-
lastActivity: tab.lastActivityAt,
|
|
16
|
-
recentMessages: messages.reverse(),
|
|
17
|
-
};
|
|
18
|
-
}
|
|
3
|
+
import { exportTab, formatHandoffInfo } from '../session/handoff.js';
|
|
4
|
+
// Re-export the daemon-shared helpers so existing CLI callers keep working.
|
|
5
|
+
export { exportTab, formatHandoffInfo };
|
|
6
|
+
/**
|
|
7
|
+
* CLI-only flow: print the handoff info, spawn claude with `--resume`, and
|
|
8
|
+
* inherit stdio so the user is dropped into an interactive session. Calls
|
|
9
|
+
* `process.exit` on subprocess exit — only safe from the CLI entry point.
|
|
10
|
+
*/
|
|
19
11
|
export function attachTab(tabName) {
|
|
20
12
|
const info = exportTab(tabName);
|
|
21
13
|
if (!info) {
|
|
@@ -30,10 +22,7 @@ export function attachTab(tabName) {
|
|
|
30
22
|
console.log(` Status: ${info.status}`);
|
|
31
23
|
console.log('');
|
|
32
24
|
// Spawn Claude Code in the terminal, resuming the session
|
|
33
|
-
const child = spawn(bin, [
|
|
34
|
-
'--session-id', info.sessionId,
|
|
35
|
-
'--resume',
|
|
36
|
-
], {
|
|
25
|
+
const child = spawn(bin, ['--session-id', info.sessionId, '--resume'], {
|
|
37
26
|
cwd: info.workingDir,
|
|
38
27
|
stdio: 'inherit', // Attach to terminal
|
|
39
28
|
env: { ...process.env },
|
|
@@ -42,27 +31,3 @@ export function attachTab(tabName) {
|
|
|
42
31
|
process.exit(code ?? 0);
|
|
43
32
|
});
|
|
44
33
|
}
|
|
45
|
-
export function formatHandoffInfo(info) {
|
|
46
|
-
const lines = [
|
|
47
|
-
`Session Handoff — tab "${info.name}"`,
|
|
48
|
-
'',
|
|
49
|
-
`Session ID: ${info.sessionId}`,
|
|
50
|
-
`Working dir: ${info.workingDir}`,
|
|
51
|
-
`Status: ${info.status}`,
|
|
52
|
-
`Last activity: ${info.lastActivity}`,
|
|
53
|
-
'',
|
|
54
|
-
'To resume in terminal:',
|
|
55
|
-
` beecork attach ${info.name}`,
|
|
56
|
-
'',
|
|
57
|
-
'Or manually:',
|
|
58
|
-
` cd ${info.workingDir}`,
|
|
59
|
-
` claude --session-id ${info.sessionId} --resume`,
|
|
60
|
-
];
|
|
61
|
-
if (info.recentMessages.length > 0) {
|
|
62
|
-
lines.push('', 'Recent context:');
|
|
63
|
-
for (const msg of info.recentMessages) {
|
|
64
|
-
lines.push(` [${msg.role}] ${msg.content.slice(0, 150)}${msg.content.length > 150 ? '...' : ''}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return lines.join('\n');
|
|
68
|
-
}
|
package/dist/cli/mcp.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
-
import {
|
|
3
|
-
const MCP_CONFIG_PATH = `${getBeecorkHome()}/mcp-config.json`;
|
|
2
|
+
import { getMcpConfigPath } from '../util/paths.js';
|
|
4
3
|
function loadMcpConfig() {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
const path = getMcpConfigPath();
|
|
5
|
+
if (fs.existsSync(path)) {
|
|
6
|
+
return JSON.parse(fs.readFileSync(path, 'utf-8'));
|
|
7
7
|
}
|
|
8
8
|
return { mcpServers: {} };
|
|
9
9
|
}
|
|
10
10
|
function saveMcpConfig(config) {
|
|
11
|
-
fs.writeFileSync(
|
|
11
|
+
fs.writeFileSync(getMcpConfigPath(), JSON.stringify(config, null, 2), { mode: 0o600 });
|
|
12
12
|
}
|
|
13
13
|
export function mcpAdd(name, command, args) {
|
|
14
14
|
const config = loadMcpConfig();
|