beecork 1.4.11 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/capabilities/index.d.ts +1 -1
  2. package/dist/capabilities/index.js +1 -1
  3. package/dist/capabilities/manager.js +13 -9
  4. package/dist/capabilities/packs.js +3 -1
  5. package/dist/channels/admin.d.ts +10 -0
  6. package/dist/channels/admin.js +20 -0
  7. package/dist/channels/command-handler.d.ts +2 -10
  8. package/dist/channels/command-handler.js +90 -84
  9. package/dist/channels/discord.d.ts +4 -9
  10. package/dist/channels/discord.js +59 -42
  11. package/dist/channels/index.d.ts +1 -1
  12. package/dist/channels/loader.js +13 -4
  13. package/dist/channels/pipeline.js +14 -5
  14. package/dist/channels/registry.d.ts +17 -1
  15. package/dist/channels/registry.js +33 -4
  16. package/dist/channels/send-helpers.d.ts +19 -0
  17. package/dist/channels/send-helpers.js +21 -0
  18. package/dist/channels/telegram.d.ts +21 -14
  19. package/dist/channels/telegram.js +214 -104
  20. package/dist/channels/types.d.ts +13 -38
  21. package/dist/channels/voice-state.d.ts +29 -0
  22. package/dist/channels/voice-state.js +45 -0
  23. package/dist/channels/webhook.d.ts +2 -5
  24. package/dist/channels/webhook.js +88 -29
  25. package/dist/channels/whatsapp.d.ts +9 -7
  26. package/dist/channels/whatsapp.js +141 -100
  27. package/dist/cli/capabilities.js +4 -4
  28. package/dist/cli/channel.js +16 -6
  29. package/dist/cli/commands.js +12 -9
  30. package/dist/cli/doctor.js +85 -27
  31. package/dist/cli/handoff.d.ts +7 -14
  32. package/dist/cli/handoff.js +9 -44
  33. package/dist/cli/mcp.js +5 -5
  34. package/dist/cli/media.js +21 -8
  35. package/dist/cli/setup.js +9 -8
  36. package/dist/cli/store.js +29 -12
  37. package/dist/config.d.ts +5 -1
  38. package/dist/config.js +20 -22
  39. package/dist/daemon.js +113 -51
  40. package/dist/dashboard/html.js +100 -20
  41. package/dist/dashboard/routes.d.ts +17 -0
  42. package/dist/dashboard/routes.js +623 -0
  43. package/dist/dashboard/server.js +38 -489
  44. package/dist/db/connection.d.ts +29 -0
  45. package/dist/db/connection.js +37 -0
  46. package/dist/db/index.js +43 -11
  47. package/dist/db/migrations.js +114 -22
  48. package/dist/delegation/manager.js +10 -4
  49. package/dist/index.js +39 -59
  50. package/dist/knowledge/manager.js +26 -12
  51. package/dist/mcp/handlers.d.ts +37 -0
  52. package/dist/mcp/handlers.js +520 -0
  53. package/dist/mcp/server.js +44 -858
  54. package/dist/mcp/tool-definitions.d.ts +1225 -0
  55. package/dist/mcp/tool-definitions.js +412 -0
  56. package/dist/mcp/validate.d.ts +23 -0
  57. package/dist/mcp/validate.js +65 -0
  58. package/dist/media/factory.js +18 -14
  59. package/dist/media/generators/dall-e.js +2 -2
  60. package/dist/media/generators/kling.js +4 -4
  61. package/dist/media/generators/lyria.js +1 -1
  62. package/dist/media/generators/nano-banana.d.ts +1 -1
  63. package/dist/media/generators/nano-banana.js +2 -2
  64. package/dist/media/generators/poll-util.js +4 -4
  65. package/dist/media/generators/recraft.js +3 -3
  66. package/dist/media/generators/runway.js +4 -4
  67. package/dist/media/generators/stable-diffusion.js +2 -2
  68. package/dist/media/generators/veo.js +1 -1
  69. package/dist/media/index.d.ts +2 -7
  70. package/dist/media/index.js +2 -2
  71. package/dist/media/store.d.ts +7 -0
  72. package/dist/media/store.js +18 -4
  73. package/dist/media/types.d.ts +22 -0
  74. package/dist/notifications/index.d.ts +2 -4
  75. package/dist/notifications/index.js +6 -19
  76. package/dist/notifications/ntfy.js +3 -3
  77. package/dist/observability/analytics.d.ts +1 -1
  78. package/dist/observability/analytics.js +41 -16
  79. package/dist/projects/index.d.ts +3 -2
  80. package/dist/projects/index.js +2 -2
  81. package/dist/projects/manager.d.ts +1 -7
  82. package/dist/projects/manager.js +66 -42
  83. package/dist/projects/router.d.ts +12 -0
  84. package/dist/projects/router.js +98 -45
  85. package/dist/service/install.js +15 -5
  86. package/dist/service/windows.js +1 -1
  87. package/dist/session/budget-guard.d.ts +20 -0
  88. package/dist/session/budget-guard.js +31 -0
  89. package/dist/session/circuit-breaker.d.ts +5 -3
  90. package/dist/session/circuit-breaker.js +45 -20
  91. package/dist/session/context-compactor.d.ts +32 -0
  92. package/dist/session/context-compactor.js +45 -0
  93. package/dist/session/context-monitor.js +2 -2
  94. package/dist/session/handoff.d.ts +21 -0
  95. package/dist/session/handoff.js +50 -0
  96. package/dist/session/manager.d.ts +21 -5
  97. package/dist/session/manager.js +166 -153
  98. package/dist/session/memory-store.d.ts +29 -0
  99. package/dist/session/memory-store.js +45 -0
  100. package/dist/session/message-queue.d.ts +28 -0
  101. package/dist/session/message-queue.js +52 -0
  102. package/dist/session/pending-dispatcher.d.ts +31 -0
  103. package/dist/session/pending-dispatcher.js +120 -0
  104. package/dist/session/pending-store.d.ts +60 -0
  105. package/dist/session/pending-store.js +118 -0
  106. package/dist/session/stale-session.d.ts +31 -0
  107. package/dist/session/stale-session.js +45 -0
  108. package/dist/session/subprocess.d.ts +3 -0
  109. package/dist/session/subprocess.js +54 -11
  110. package/dist/session/tab-store.d.ts +28 -0
  111. package/dist/session/tab-store.js +78 -0
  112. package/dist/tasks/scheduler.d.ts +13 -0
  113. package/dist/tasks/scheduler.js +97 -18
  114. package/dist/tasks/store.js +26 -12
  115. package/dist/timeline/logger.js +3 -1
  116. package/dist/timeline/query.js +15 -5
  117. package/dist/types.d.ts +49 -9
  118. package/dist/util/auto-heal.js +15 -5
  119. package/dist/util/install-info.js +3 -1
  120. package/dist/util/logger.d.ts +1 -1
  121. package/dist/util/logger.js +63 -24
  122. package/dist/util/paths.d.ts +2 -0
  123. package/dist/util/paths.js +16 -3
  124. package/dist/util/rate-limiter.js +8 -0
  125. package/dist/util/retry.js +1 -1
  126. package/dist/util/text.d.ts +21 -1
  127. package/dist/util/text.js +38 -8
  128. package/dist/voice/index.js +5 -1
  129. package/dist/voice/stt.js +14 -6
  130. package/dist/voice/tts.js +1 -1
  131. package/dist/watchers/scheduler.js +11 -5
  132. package/package.json +6 -1
  133. package/dist/session/tool-classifier.d.ts +0 -4
  134. package/dist/session/tool-classifier.js +0 -56
  135. package/dist/users/index.d.ts +0 -2
  136. package/dist/users/index.js +0 -1
  137. package/dist/users/service.d.ts +0 -17
  138. package/dist/users/service.js +0 -46
@@ -1,17 +1,24 @@
1
- import { execSync } from 'node:child_process';
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) ? packageName : `${CHANNEL_PREFIX}${packageName}`;
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
- execSync(`npm install -g ${fullName}`, { stdio: 'inherit' });
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 (err) {
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 = execSync('npm list -g --depth=0', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
177
- const lines = output.split('\n').filter(line => line.includes(CHANNEL_PREFIX));
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>`);
@@ -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
- const db = getDb();
75
- const tabs = db.prepare('SELECT * FROM tabs ORDER BY last_activity_at DESC').all();
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.last_activity_at);
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
- const db = requireDb();
101
- const tabs = db.prepare('SELECT * FROM tabs ORDER BY last_activity_at DESC').all();
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.last_activity_at);
110
- console.log(` ${tab.name.padEnd(20)} [${tab.status}] dir:${tab.working_dir} — ${ago}`);
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.prepare('SELECT * FROM memories ORDER BY created_at DESC LIMIT 50').all();
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.');
@@ -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({ name: 'Claude Code', status: 'fail', message: 'Claude Code binary not found. Install: npm install -g @anthropic-ai/claude-code' });
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 = `${getBeecorkHome()}/config.json`;
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`, { signal: AbortSignal.timeout(10000) });
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({ name: 'Telegram bot', status: 'pass', message: `@${data.result.username}` });
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({ name: 'Telegram bot', status: 'fail', message: 'Invalid token — getMe returned error' });
50
+ checks.push({
51
+ name: 'Telegram bot',
52
+ status: 'fail',
53
+ message: 'Invalid token — getMe returned error',
54
+ });
40
55
  }
41
56
  }
42
- catch (err) {
43
- checks.push({ name: 'Telegram bot', status: 'warn', message: 'Could not reach Telegram API' });
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 || `${getBeecorkHome()}/whatsapp-session`;
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({ name: 'WhatsApp session', status: 'warn', message: 'No session data — QR scan needed' });
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({ name: 'Config', status: 'fail', message: `Not found at ${configPath}. Run: beecork setup` });
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 Database = (await import('better-sqlite3')).default;
72
- const db = new Database(dbPath, { readonly: true });
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({ name: 'Database', status: 'warn', message: 'No database yet — starts on first run' });
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({ name: 'Daemon', status: 'warn', message: `Stale PID file (PID ${pid} not running)` });
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 = `${getBeecorkHome()}/media`;
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,38 +147,61 @@ 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({ name: 'Media dir', status: 'pass', message: `${files.length} files in ${mediaDir}` });
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({ name: 'Media dir', status: 'pass', message: 'Not created yet (created on first media)' });
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({ name: 'Disk space', status: 'fail', message: 'Cannot write to beecork home directory' });
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 = `${getBeecorkHome()}/mcp-config.json`;
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({ name: 'MCP config', status: 'pass', message: `${serverCount} server(s) configured` });
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({ name: 'MCP config', status: 'fail', message: 'Invalid JSON in mcp-config.json' });
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 {
137
192
  checks.push({ name: 'MCP config', status: 'pass', message: 'Default (beecork MCP only)' });
138
193
  }
139
- // Print results
194
+ // Print results — gate ANSI on a real TTY so piping to a file/grep yields plain text.
140
195
  console.log('\nBeecork Doctor\n');
141
- const icons = { pass: '\x1b[32m✓\x1b[0m', warn: '\x1b[33m!\x1b[0m', fail: '\x1b[31m✗\x1b[0m' };
196
+ const colored = process.stdout.isTTY && !process.env.NO_COLOR;
197
+ const icons = colored
198
+ ? { pass: '\x1b[32m✓\x1b[0m', warn: '\x1b[33m!\x1b[0m', fail: '\x1b[31m✗\x1b[0m' }
199
+ : { pass: '✓', warn: '!', fail: '✗' };
142
200
  for (const check of checks) {
143
201
  console.log(` ${icons[check.status]} ${check.name}: ${check.message}`);
144
202
  }
145
- const fails = checks.filter(c => c.status === 'fail').length;
146
- 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;
147
205
  console.log(`\n ${checks.length} checks: ${checks.length - fails - warns} passed, ${warns} warnings, ${fails} failures\n`);
148
206
  if (fails > 0)
149
207
  process.exit(1);
@@ -1,15 +1,8 @@
1
- interface TabInfo {
2
- name: string;
3
- sessionId: string;
4
- workingDir: string;
5
- status: string;
6
- lastActivity: string;
7
- recentMessages: Array<{
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 {};
@@ -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
- export function exportTab(tabName) {
5
- const db = getDb();
6
- const tab = db.prepare('SELECT * FROM tabs WHERE name = ?').get(tabName);
7
- if (!tab)
8
- return null;
9
- const messages = db.prepare('SELECT role, content FROM messages WHERE tab_id = ? ORDER BY created_at DESC LIMIT 5').all(tab.id);
10
- return {
11
- name: tab.name,
12
- sessionId: tab.session_id,
13
- workingDir: tab.working_dir,
14
- status: tab.status,
15
- lastActivity: tab.last_activity_at,
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 { getBeecorkHome } from '../util/paths.js';
3
- const MCP_CONFIG_PATH = `${getBeecorkHome()}/mcp-config.json`;
2
+ import { getMcpConfigPath } from '../util/paths.js';
4
3
  function loadMcpConfig() {
5
- if (fs.existsSync(MCP_CONFIG_PATH)) {
6
- return JSON.parse(fs.readFileSync(MCP_CONFIG_PATH, 'utf-8'));
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(MCP_CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
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();
package/dist/cli/media.js CHANGED
@@ -1,12 +1,20 @@
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
  const IMAGE_PROVIDERS = [
7
- { id: 'nano-banana', name: 'Google Nano Banana', keyHint: 'Google AI API key (from ai.google.dev)' },
7
+ {
8
+ id: 'nano-banana',
9
+ name: 'Google Nano Banana',
10
+ keyHint: 'Google AI API key (from ai.google.dev)',
11
+ },
8
12
  { id: 'dall-e', name: 'DALL-E (OpenAI)', keyHint: 'OpenAI API key (sk-...)' },
9
- { id: 'stable-diffusion', name: 'Stable Diffusion (Stability AI)', keyHint: 'Stability AI API key' },
13
+ {
14
+ id: 'stable-diffusion',
15
+ name: 'Stable Diffusion (Stability AI)',
16
+ keyHint: 'Stability AI API key',
17
+ },
10
18
  { id: 'recraft', name: 'Recraft (Images + SVG Vectors)', keyHint: 'Recraft API key' },
11
19
  ];
12
20
  const VIDEO_PROVIDERS = [
@@ -17,7 +25,11 @@ const VIDEO_PROVIDERS = [
17
25
  const AUDIO_PROVIDERS = [
18
26
  { id: 'elevenlabs-music', name: 'ElevenLabs Music', keyHint: 'ElevenLabs API key (xi-...)' },
19
27
  { id: 'lyria', name: 'Google Lyria (Music)', keyHint: 'Google AI API key (from ai.google.dev)' },
20
- { id: 'elevenlabs-sfx', name: 'ElevenLabs Sound Effects', keyHint: 'ElevenLabs API key (xi-...)' },
28
+ {
29
+ id: 'elevenlabs-sfx',
30
+ name: 'ElevenLabs Sound Effects',
31
+ keyHint: 'ElevenLabs API key (xi-...)',
32
+ },
21
33
  ];
22
34
  export async function mediaSetup() {
23
35
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -27,7 +39,8 @@ export async function mediaSetup() {
27
39
  console.log('\nMedia Generation Setup\n');
28
40
  console.log('Configure AI providers for generating images, videos, and audio.');
29
41
  console.log('You need API keys from each provider.\n');
30
- console.log('Already configured: ' + (generators.length > 0 ? generators.map(g => g.provider).join(', ') : 'none'));
42
+ console.log('Already configured: ' +
43
+ (generators.length > 0 ? generators.map((g) => g.provider).join(', ') : 'none'));
31
44
  console.log('');
32
45
  // Image providers
33
46
  console.log('Image Generation:');
@@ -42,7 +55,7 @@ export async function mediaSetup() {
42
55
  const apiKey = await ask(rl, ` ${provider.keyHint}`);
43
56
  if (apiKey) {
44
57
  // Remove existing same provider if any
45
- const filtered = generators.filter(g => g.provider !== provider.id);
58
+ const filtered = generators.filter((g) => g.provider !== provider.id);
46
59
  filtered.push({ provider: provider.id, apiKey });
47
60
  generators.length = 0;
48
61
  generators.push(...filtered);
@@ -62,7 +75,7 @@ export async function mediaSetup() {
62
75
  const provider = VIDEO_PROVIDERS[idx];
63
76
  const apiKey = await ask(rl, ` ${provider.keyHint}`);
64
77
  if (apiKey) {
65
- const filtered = generators.filter(g => g.provider !== provider.id);
78
+ const filtered = generators.filter((g) => g.provider !== provider.id);
66
79
  filtered.push({ provider: provider.id, apiKey });
67
80
  generators.length = 0;
68
81
  generators.push(...filtered);
@@ -82,7 +95,7 @@ export async function mediaSetup() {
82
95
  const provider = AUDIO_PROVIDERS[idx];
83
96
  const apiKey = await ask(rl, ` ${provider.keyHint}`);
84
97
  if (apiKey) {
85
- const filtered = generators.filter(g => g.provider !== provider.id);
98
+ const filtered = generators.filter((g) => g.provider !== provider.id);
86
99
  filtered.push({ provider: provider.id, apiKey });
87
100
  generators.length = 0;
88
101
  generators.push(...filtered);
package/dist/cli/setup.js CHANGED
@@ -34,11 +34,11 @@ export async function setupWizard() {
34
34
  console.log('Checking prerequisites...\n');
35
35
  try {
36
36
  const version = execSync('claude --version 2>&1', { encoding: 'utf-8' }).trim();
37
- console.log(` \u2713 Claude Code found: ${version}`);
37
+ console.log(` Claude Code found: ${version}`);
38
38
  }
39
39
  catch {
40
40
  claudeCodeMissing = true;
41
- console.log(' \u2717 Claude Code is not installed yet.');
41
+ console.log(' Claude Code is not installed yet.');
42
42
  console.log('');
43
43
  console.log(' Claude Code is the AI brain that Beecork connects to.');
44
44
  console.log(' You need a Claude Pro or Max subscription ($20/month) from anthropic.com');
@@ -74,13 +74,15 @@ export async function setupWizard() {
74
74
  }
75
75
  // Validate token by calling getMe
76
76
  try {
77
- const resp = await fetch(`https://api.telegram.org/bot${token}/getMe`, { signal: AbortSignal.timeout(10000) });
77
+ const resp = await fetch(`https://api.telegram.org/bot${token}/getMe`, {
78
+ signal: AbortSignal.timeout(10000),
79
+ });
78
80
  if (resp.ok) {
79
- const data = await resp.json();
80
- console.log(` \u2713 Connected to bot: @${data.result.username}\n`);
81
+ const data = (await resp.json());
82
+ console.log(` Connected to bot: @${data.result.username}\n`);
81
83
  }
82
84
  else {
83
- console.log(' \u2717 Invalid token. Please check and try again.\n');
85
+ console.log(' Invalid token. Please check and try again.\n');
84
86
  token = '';
85
87
  }
86
88
  }
@@ -128,7 +130,6 @@ export async function setupWizard() {
128
130
  },
129
131
  memory: {
130
132
  dbPath: '~/.beecork/memory.db',
131
- maxLongTermEntries: 1000,
132
133
  },
133
134
  projectScanPaths: scanPaths,
134
135
  deployment: 'local',
@@ -234,5 +235,5 @@ function generateMcpConfig() {
234
235
  },
235
236
  },
236
237
  };
237
- fs.writeFileSync(getMcpConfigPath(), JSON.stringify(mcpConfig, null, 2) + '\n');
238
+ fs.writeFileSync(getMcpConfigPath(), JSON.stringify(mcpConfig, null, 2) + '\n', { mode: 0o600 });
238
239
  }
package/dist/cli/store.js CHANGED
@@ -1,5 +1,11 @@
1
- import { execSync } from 'node:child_process';
2
- const BEECORK_PREFIXES = ['beecork-capability-', 'beecork-media-', 'beecork-channel-', 'beecork-watcher-'];
1
+ import { execFileSync } from 'node:child_process';
2
+ const BEECORK_PREFIXES = [
3
+ 'beecork-capability-',
4
+ 'beecork-media-',
5
+ 'beecork-channel-',
6
+ 'beecork-watcher-',
7
+ ];
8
+ const SAFE_NPM_PACKAGE = /^[@a-zA-Z0-9_/.-]+$/;
3
9
  export async function storeSearch(query) {
4
10
  console.log(`\nSearching for "${query}"...\n`);
5
11
  try {
@@ -9,8 +15,8 @@ export async function storeSearch(query) {
9
15
  console.log('Failed to search npm registry. Try: npm search beecork');
10
16
  return;
11
17
  }
12
- const data = await response.json();
13
- const packages = data.objects.filter(o => BEECORK_PREFIXES.some(p => o.package.name.startsWith(p)));
18
+ const data = (await response.json());
19
+ const packages = data.objects.filter((o) => BEECORK_PREFIXES.some((p) => o.package.name.startsWith(p)));
14
20
  if (packages.length === 0) {
15
21
  console.log(`No beecork packages found for "${query}".`);
16
22
  console.log('Community packages use naming convention: beecork-capability-*, beecork-media-*, beecork-channel-*');
@@ -19,7 +25,9 @@ export async function storeSearch(query) {
19
25
  console.log(`${packages.length} package(s) found:\n`);
20
26
  for (const pkg of packages) {
21
27
  const p = pkg.package;
22
- const type = BEECORK_PREFIXES.find(prefix => p.name.startsWith(prefix))?.replace('beecork-', '').replace('-', '') || '';
28
+ const type = BEECORK_PREFIXES.find((prefix) => p.name.startsWith(prefix))
29
+ ?.replace('beecork-', '')
30
+ .replace('-', '') || '';
23
31
  console.log(` ${p.name}@${p.version}`);
24
32
  console.log(` ${p.description || 'No description'}`);
25
33
  console.log(` Type: ${type}`);
@@ -35,13 +43,18 @@ export async function storeSearch(query) {
35
43
  export function storeInstall(packageName) {
36
44
  // Normalize: if user types "shopify", try "beecork-capability-shopify" first
37
45
  let fullName = packageName;
38
- if (!BEECORK_PREFIXES.some(p => packageName.startsWith(p)) && !packageName.startsWith('beecork-')) {
46
+ if (!BEECORK_PREFIXES.some((p) => packageName.startsWith(p)) &&
47
+ !packageName.startsWith('beecork-')) {
39
48
  // Try capability first, then media, then channel
40
49
  fullName = `beecork-capability-${packageName}`;
41
50
  }
51
+ if (!SAFE_NPM_PACKAGE.test(fullName)) {
52
+ console.error(`Invalid package name: ${fullName}`);
53
+ return;
54
+ }
42
55
  console.log(`\nInstalling ${fullName}...\n`);
43
56
  try {
44
- execSync(`npm install -g ${fullName}`, { stdio: 'inherit' });
57
+ execFileSync('npm', ['install', '-g', fullName], { stdio: 'inherit' });
45
58
  console.log(`\n${fullName} installed.`);
46
59
  console.log('Restart daemon to activate: beecork stop && beecork start\n');
47
60
  }
@@ -51,8 +64,10 @@ export function storeInstall(packageName) {
51
64
  const baseName = packageName;
52
65
  for (const prefix of ['beecork-media-', 'beecork-channel-', 'beecork-']) {
53
66
  const altName = prefix + baseName;
67
+ if (!SAFE_NPM_PACKAGE.test(altName))
68
+ continue;
54
69
  try {
55
- execSync(`npm install -g ${altName}`, { stdio: 'inherit' });
70
+ execFileSync('npm', ['install', '-g', altName], { stdio: 'inherit' });
56
71
  console.log(`\n${altName} installed.`);
57
72
  console.log('Restart daemon to activate: beecork stop && beecork start\n');
58
73
  return;
@@ -68,14 +83,16 @@ export function storeInstall(packageName) {
68
83
  }
69
84
  export async function storeInfo(packageName) {
70
85
  try {
71
- const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}`, { signal: AbortSignal.timeout(10000) });
86
+ const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}`, {
87
+ signal: AbortSignal.timeout(10000),
88
+ });
72
89
  if (!response.ok) {
73
90
  console.log(`Package "${packageName}" not found on npm.`);
74
91
  return;
75
92
  }
76
- const data = await response.json();
77
- const latest = data['dist-tags']?.latest;
78
- const info = data.versions?.[latest];
93
+ const data = (await response.json());
94
+ const latest = data['dist-tags']?.latest ?? '';
95
+ const info = latest ? data.versions?.[latest] : undefined;
79
96
  console.log(`\n${data.name}@${latest}`);
80
97
  console.log(` ${data.description || 'No description'}`);
81
98
  if (info?.homepage)
package/dist/config.d.ts CHANGED
@@ -3,5 +3,9 @@ export declare function getConfig(): BeecorkConfig;
3
3
  export declare function saveConfig(config: BeecorkConfig): void;
4
4
  export declare function getTabConfig(tabName: string): TabConfig;
5
5
  export declare function resolveWorkingDir(tabName: string): string;
6
- export declare function getAdminUserId(): number;
7
6
  export declare function validateTabName(name: string): string | null;
7
+ /**
8
+ * Like validateTabName but allows the literal name "default" (used by send/update
9
+ * endpoints that reference an existing tab rather than creating one).
10
+ */
11
+ export declare function validateTabNameOrDefault(name: string): string | null;