beecork 1.7.1 → 2.0.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 (253) hide show
  1. package/README.md +52 -111
  2. package/dist/index.js +1394 -581
  3. package/package.json +26 -54
  4. package/dist/capabilities/index.d.ts +0 -3
  5. package/dist/capabilities/index.js +0 -2
  6. package/dist/capabilities/manager.d.ts +0 -13
  7. package/dist/capabilities/manager.js +0 -115
  8. package/dist/capabilities/packs.d.ts +0 -2
  9. package/dist/capabilities/packs.js +0 -54
  10. package/dist/capabilities/types.d.ts +0 -21
  11. package/dist/capabilities/types.js +0 -1
  12. package/dist/channels/admin.d.ts +0 -10
  13. package/dist/channels/admin.js +0 -20
  14. package/dist/channels/command-handler.d.ts +0 -19
  15. package/dist/channels/command-handler.js +0 -235
  16. package/dist/channels/discord.d.ts +0 -17
  17. package/dist/channels/discord.js +0 -266
  18. package/dist/channels/index.d.ts +0 -7
  19. package/dist/channels/index.js +0 -6
  20. package/dist/channels/loader.d.ts +0 -7
  21. package/dist/channels/loader.js +0 -87
  22. package/dist/channels/pipeline.d.ts +0 -50
  23. package/dist/channels/pipeline.js +0 -92
  24. package/dist/channels/registry.d.ts +0 -36
  25. package/dist/channels/registry.js +0 -80
  26. package/dist/channels/send-helpers.d.ts +0 -19
  27. package/dist/channels/send-helpers.js +0 -21
  28. package/dist/channels/telegram.d.ts +0 -47
  29. package/dist/channels/telegram.js +0 -649
  30. package/dist/channels/types.d.ts +0 -41
  31. package/dist/channels/types.js +0 -1
  32. package/dist/channels/voice-state.d.ts +0 -29
  33. package/dist/channels/voice-state.js +0 -45
  34. package/dist/channels/webhook.d.ts +0 -16
  35. package/dist/channels/webhook.js +0 -222
  36. package/dist/channels/whatsapp.d.ts +0 -28
  37. package/dist/channels/whatsapp.js +0 -323
  38. package/dist/cli/capabilities.d.ts +0 -3
  39. package/dist/cli/capabilities.js +0 -72
  40. package/dist/cli/channel.d.ts +0 -3
  41. package/dist/cli/channel.js +0 -202
  42. package/dist/cli/commands.d.ts +0 -15
  43. package/dist/cli/commands.js +0 -279
  44. package/dist/cli/doctor.d.ts +0 -1
  45. package/dist/cli/doctor.js +0 -208
  46. package/dist/cli/handoff.d.ts +0 -8
  47. package/dist/cli/handoff.js +0 -33
  48. package/dist/cli/helpers.d.ts +0 -2
  49. package/dist/cli/helpers.js +0 -21
  50. package/dist/cli/mcp.d.ts +0 -3
  51. package/dist/cli/mcp.js +0 -47
  52. package/dist/cli/media.d.ts +0 -2
  53. package/dist/cli/media.js +0 -125
  54. package/dist/cli/setup.d.ts +0 -1
  55. package/dist/cli/setup.js +0 -239
  56. package/dist/cli/store.d.ts +0 -3
  57. package/dist/cli/store.js +0 -108
  58. package/dist/config.d.ts +0 -11
  59. package/dist/config.js +0 -115
  60. package/dist/cron/scheduler.d.ts +0 -22
  61. package/dist/cron/scheduler.js +0 -220
  62. package/dist/cron/store.d.ts +0 -12
  63. package/dist/cron/store.js +0 -83
  64. package/dist/daemon.d.ts +0 -1
  65. package/dist/daemon.js +0 -345
  66. package/dist/dashboard/html.d.ts +0 -1
  67. package/dist/dashboard/html.js +0 -849
  68. package/dist/dashboard/routes.d.ts +0 -17
  69. package/dist/dashboard/routes.js +0 -623
  70. package/dist/dashboard/server.d.ts +0 -1
  71. package/dist/dashboard/server.js +0 -104
  72. package/dist/db/connection.d.ts +0 -29
  73. package/dist/db/connection.js +0 -37
  74. package/dist/db/index.d.ts +0 -14
  75. package/dist/db/index.js +0 -127
  76. package/dist/db/migrations.d.ts +0 -2
  77. package/dist/db/migrations.js +0 -384
  78. package/dist/delegation/manager.d.ts +0 -18
  79. package/dist/delegation/manager.js +0 -89
  80. package/dist/index.d.ts +0 -2
  81. package/dist/knowledge/index.d.ts +0 -2
  82. package/dist/knowledge/index.js +0 -1
  83. package/dist/knowledge/manager.d.ts +0 -17
  84. package/dist/knowledge/manager.js +0 -160
  85. package/dist/knowledge/types.d.ts +0 -7
  86. package/dist/knowledge/types.js +0 -1
  87. package/dist/machines/forwarder.d.ts +0 -7
  88. package/dist/machines/forwarder.js +0 -35
  89. package/dist/machines/index.d.ts +0 -1
  90. package/dist/machines/index.js +0 -1
  91. package/dist/machines/registry.d.ts +0 -15
  92. package/dist/machines/registry.js +0 -46
  93. package/dist/mcp/handlers.d.ts +0 -37
  94. package/dist/mcp/handlers.js +0 -520
  95. package/dist/mcp/server.d.ts +0 -2
  96. package/dist/mcp/server.js +0 -86
  97. package/dist/mcp/tool-definitions.d.ts +0 -1225
  98. package/dist/mcp/tool-definitions.js +0 -412
  99. package/dist/mcp/validate.d.ts +0 -23
  100. package/dist/mcp/validate.js +0 -65
  101. package/dist/media/factory.d.ts +0 -6
  102. package/dist/media/factory.js +0 -36
  103. package/dist/media/generators/dall-e.d.ts +0 -10
  104. package/dist/media/generators/dall-e.js +0 -41
  105. package/dist/media/generators/elevenlabs-music.d.ts +0 -9
  106. package/dist/media/generators/elevenlabs-music.js +0 -34
  107. package/dist/media/generators/elevenlabs-sfx.d.ts +0 -9
  108. package/dist/media/generators/elevenlabs-sfx.js +0 -33
  109. package/dist/media/generators/kling.d.ts +0 -9
  110. package/dist/media/generators/kling.js +0 -47
  111. package/dist/media/generators/lyria.d.ts +0 -10
  112. package/dist/media/generators/lyria.js +0 -41
  113. package/dist/media/generators/nano-banana.d.ts +0 -10
  114. package/dist/media/generators/nano-banana.js +0 -45
  115. package/dist/media/generators/poll-util.d.ts +0 -12
  116. package/dist/media/generators/poll-util.js +0 -22
  117. package/dist/media/generators/recraft.d.ts +0 -9
  118. package/dist/media/generators/recraft.js +0 -52
  119. package/dist/media/generators/runway.d.ts +0 -9
  120. package/dist/media/generators/runway.js +0 -49
  121. package/dist/media/generators/stable-diffusion.d.ts +0 -9
  122. package/dist/media/generators/stable-diffusion.js +0 -35
  123. package/dist/media/generators/veo.d.ts +0 -9
  124. package/dist/media/generators/veo.js +0 -32
  125. package/dist/media/index.d.ts +0 -6
  126. package/dist/media/index.js +0 -8
  127. package/dist/media/loader.d.ts +0 -8
  128. package/dist/media/loader.js +0 -46
  129. package/dist/media/store.d.ts +0 -17
  130. package/dist/media/store.js +0 -69
  131. package/dist/media/types.d.ts +0 -43
  132. package/dist/media/types.js +0 -1
  133. package/dist/memory/extractor.d.ts +0 -5
  134. package/dist/memory/extractor.js +0 -157
  135. package/dist/notifications/index.d.ts +0 -4
  136. package/dist/notifications/index.js +0 -21
  137. package/dist/notifications/ntfy.d.ts +0 -9
  138. package/dist/notifications/ntfy.js +0 -25
  139. package/dist/notifications/pushover.d.ts +0 -9
  140. package/dist/notifications/pushover.js +0 -26
  141. package/dist/notifications/types.d.ts +0 -5
  142. package/dist/notifications/types.js +0 -1
  143. package/dist/notifications/webhook-provider.d.ts +0 -9
  144. package/dist/notifications/webhook-provider.js +0 -24
  145. package/dist/observability/analytics.d.ts +0 -30
  146. package/dist/observability/analytics.js +0 -118
  147. package/dist/pipe/anthropic-client.d.ts +0 -9
  148. package/dist/pipe/anthropic-client.js +0 -50
  149. package/dist/pipe/brain.d.ts +0 -16
  150. package/dist/pipe/brain.js +0 -89
  151. package/dist/pipe/memory-store.d.ts +0 -8
  152. package/dist/pipe/memory-store.js +0 -39
  153. package/dist/pipe/project-scanner.d.ts +0 -6
  154. package/dist/pipe/project-scanner.js +0 -26
  155. package/dist/pipe/types.d.ts +0 -34
  156. package/dist/pipe/types.js +0 -1
  157. package/dist/projects/index.d.ts +0 -4
  158. package/dist/projects/index.js +0 -2
  159. package/dist/projects/manager.d.ts +0 -13
  160. package/dist/projects/manager.js +0 -166
  161. package/dist/projects/router.d.ts +0 -17
  162. package/dist/projects/router.js +0 -246
  163. package/dist/projects/types.d.ts +0 -19
  164. package/dist/projects/types.js +0 -1
  165. package/dist/service/install.d.ts +0 -4
  166. package/dist/service/install.js +0 -146
  167. package/dist/service/templates.d.ts +0 -4
  168. package/dist/service/templates.js +0 -52
  169. package/dist/service/windows.d.ts +0 -8
  170. package/dist/service/windows.js +0 -59
  171. package/dist/session/budget-guard.d.ts +0 -20
  172. package/dist/session/budget-guard.js +0 -31
  173. package/dist/session/circuit-breaker.d.ts +0 -19
  174. package/dist/session/circuit-breaker.js +0 -91
  175. package/dist/session/context-compactor.d.ts +0 -32
  176. package/dist/session/context-compactor.js +0 -45
  177. package/dist/session/context-monitor.d.ts +0 -15
  178. package/dist/session/context-monitor.js +0 -44
  179. package/dist/session/handoff.d.ts +0 -21
  180. package/dist/session/handoff.js +0 -50
  181. package/dist/session/manager.d.ts +0 -60
  182. package/dist/session/manager.js +0 -416
  183. package/dist/session/memory-store.d.ts +0 -29
  184. package/dist/session/memory-store.js +0 -45
  185. package/dist/session/message-queue.d.ts +0 -28
  186. package/dist/session/message-queue.js +0 -52
  187. package/dist/session/pending-dispatcher.d.ts +0 -31
  188. package/dist/session/pending-dispatcher.js +0 -120
  189. package/dist/session/pending-store.d.ts +0 -60
  190. package/dist/session/pending-store.js +0 -118
  191. package/dist/session/stale-session.d.ts +0 -31
  192. package/dist/session/stale-session.js +0 -45
  193. package/dist/session/subprocess.d.ts +0 -32
  194. package/dist/session/subprocess.js +0 -230
  195. package/dist/session/tab-store.d.ts +0 -28
  196. package/dist/session/tab-store.js +0 -78
  197. package/dist/session/tool-classifier.d.ts +0 -4
  198. package/dist/session/tool-classifier.js +0 -56
  199. package/dist/tasks/scheduler.d.ts +0 -45
  200. package/dist/tasks/scheduler.js +0 -329
  201. package/dist/tasks/store.d.ts +0 -11
  202. package/dist/tasks/store.js +0 -103
  203. package/dist/timeline/index.d.ts +0 -3
  204. package/dist/timeline/index.js +0 -2
  205. package/dist/timeline/logger.d.ts +0 -8
  206. package/dist/timeline/logger.js +0 -11
  207. package/dist/timeline/query.d.ts +0 -11
  208. package/dist/timeline/query.js +0 -67
  209. package/dist/timeline/types.d.ts +0 -12
  210. package/dist/timeline/types.js +0 -1
  211. package/dist/types.d.ts +0 -225
  212. package/dist/types.js +0 -3
  213. package/dist/users/index.d.ts +0 -2
  214. package/dist/users/index.js +0 -1
  215. package/dist/users/service.d.ts +0 -17
  216. package/dist/users/service.js +0 -46
  217. package/dist/util/auto-heal.d.ts +0 -43
  218. package/dist/util/auto-heal.js +0 -107
  219. package/dist/util/install-info.d.ts +0 -37
  220. package/dist/util/install-info.js +0 -80
  221. package/dist/util/logger.d.ts +0 -18
  222. package/dist/util/logger.js +0 -118
  223. package/dist/util/paths.d.ts +0 -13
  224. package/dist/util/paths.js +0 -55
  225. package/dist/util/platform.d.ts +0 -2
  226. package/dist/util/platform.js +0 -11
  227. package/dist/util/progress.d.ts +0 -34
  228. package/dist/util/progress.js +0 -133
  229. package/dist/util/rate-limiter.d.ts +0 -14
  230. package/dist/util/rate-limiter.js +0 -50
  231. package/dist/util/retry.d.ts +0 -2
  232. package/dist/util/retry.js +0 -18
  233. package/dist/util/text.d.ts +0 -32
  234. package/dist/util/text.js +0 -101
  235. package/dist/version.d.ts +0 -1
  236. package/dist/version.js +0 -4
  237. package/dist/voice/index.d.ts +0 -17
  238. package/dist/voice/index.js +0 -42
  239. package/dist/voice/stt.d.ts +0 -16
  240. package/dist/voice/stt.js +0 -57
  241. package/dist/voice/tts.d.ts +0 -23
  242. package/dist/voice/tts.js +0 -84
  243. package/dist/watchers/evaluator.d.ts +0 -5
  244. package/dist/watchers/evaluator.js +0 -45
  245. package/dist/watchers/index.d.ts +0 -4
  246. package/dist/watchers/index.js +0 -3
  247. package/dist/watchers/scheduler.d.ts +0 -20
  248. package/dist/watchers/scheduler.js +0 -183
  249. package/dist/watchers/store.d.ts +0 -10
  250. package/dist/watchers/store.js +0 -56
  251. package/dist/watchers/types.d.ts +0 -15
  252. package/dist/watchers/types.js +0 -1
  253. package/scripts/postinstall.mjs +0 -50
@@ -1,89 +0,0 @@
1
- import { PipeAnthropicClient } from './anthropic-client.js';
2
- import { PipeMemoryStore } from './memory-store.js';
3
- import { scanForProjects } from './project-scanner.js';
4
- import { parseTabMessage } from '../util/text.js';
5
- import { logger } from '../util/logger.js';
6
- export class PipeBrain {
7
- client;
8
- memory;
9
- config;
10
- tabManager;
11
- constructor(config, tabManager) {
12
- this.config = config;
13
- this.tabManager = tabManager;
14
- this.client = new PipeAnthropicClient(config.pipe.anthropicApiKey, config.pipe.routingModel);
15
- this.memory = new PipeMemoryStore();
16
- }
17
- /** Main entry point: route a message to the right tab and send it to Claude. */
18
- async process(message, _context) {
19
- const decisions = [];
20
- // Step 1: Route the message to the right tab/project
21
- const route = await this.route(message, decisions);
22
- // Step 2: Ensure the tab exists with the right working directory
23
- if (route.projectPath) {
24
- this.tabManager.ensureTab(route.tabName, route.projectPath);
25
- this.memory.updateProjectLastUsed(route.projectPath);
26
- }
27
- // Step 3: Send to Claude Code
28
- let result;
29
- try {
30
- result = await this.tabManager.sendMessage(route.tabName, message, { projectPath: route.projectPath ?? undefined });
31
- }
32
- catch (err) {
33
- return {
34
- tabName: route.tabName,
35
- response: { text: `Error: ${err instanceof Error ? err.message : err}`, error: true, costUsd: 0, durationMs: 0 },
36
- decisions,
37
- };
38
- }
39
- return {
40
- tabName: route.tabName,
41
- response: { text: result.text, error: result.error, costUsd: result.costUsd, durationMs: result.durationMs },
42
- decisions,
43
- };
44
- }
45
- /** Route a message to the right project/tab */
46
- async route(message, decisions) {
47
- // Check for manual /tab override first
48
- if (message.startsWith('/tab ')) {
49
- const parsed = parseTabMessage(message);
50
- if (parsed.tabName !== 'default') {
51
- decisions.push(`📌 Manual routing to "${parsed.tabName}"`);
52
- return { tabName: parsed.tabName, projectPath: null, confidence: 1.0, reason: 'Manual override', needsConfirmation: false };
53
- }
54
- }
55
- const projects = this.memory.getProjects();
56
- const recentRouting = this.memory.getRecentRouting(5);
57
- // If no projects and no API key, just use default
58
- if (projects.length === 0 || !this.config.pipe.anthropicApiKey) {
59
- decisions.push('📍 Routing to default tab (no projects discovered)');
60
- return { tabName: 'default', projectPath: null, confidence: 1.0, reason: 'No projects', needsConfirmation: false };
61
- }
62
- try {
63
- const route = await this.client.route(message, projects, recentRouting);
64
- // Record the routing decision
65
- this.memory.recordRouting(message, route.tabName, route.projectPath, route.confidence);
66
- if (route.confidence >= this.config.pipe.confidenceThreshold) {
67
- decisions.push(`🧠 Routing to "${route.tabName}" (${Math.round(route.confidence * 100)}% confidence) — ${route.reason}`);
68
- }
69
- else {
70
- decisions.push(`🤔 Low confidence routing to "${route.tabName}" (${Math.round(route.confidence * 100)}%) — ${route.reason}. Using default.`);
71
- route.tabName = 'default';
72
- }
73
- return route;
74
- }
75
- catch (err) {
76
- logger.error('Pipe routing failed, using default:', err);
77
- decisions.push('⚠️ Routing failed, using default tab');
78
- return { tabName: 'default', projectPath: null, confidence: 0.5, reason: 'Routing error', needsConfirmation: false };
79
- }
80
- }
81
- /** Discover projects on the filesystem */
82
- async discoverProjects() {
83
- const projects = scanForProjects(this.config.pipe.projectScanPaths);
84
- for (const project of projects) {
85
- this.memory.upsertProject(project);
86
- }
87
- return projects.length;
88
- }
89
- }
@@ -1,8 +0,0 @@
1
- import type { Project } from './types.js';
2
- export declare class PipeMemoryStore {
3
- getProjects(): Project[];
4
- upsertProject(project: Project): void;
5
- updateProjectLastUsed(path: string): void;
6
- getRecentRouting(limit?: number): string[];
7
- recordRouting(messagePreview: string, tabName: string, projectPath: string | null, confidence: number): void;
8
- }
@@ -1,39 +0,0 @@
1
- import { v4 as uuidv4 } from 'uuid';
2
- import { getDb } from '../db/index.js';
3
- export class PipeMemoryStore {
4
- // ─── Projects ───
5
- getProjects() {
6
- const db = getDb();
7
- const rows = db.prepare('SELECT * FROM projects ORDER BY last_used_at DESC NULLS LAST').all();
8
- return rows.map(r => ({
9
- id: r.id,
10
- name: r.name,
11
- path: r.path,
12
- type: r.type || 'user-project',
13
- lastUsedAt: r.last_used_at,
14
- createdAt: r.created_at,
15
- }));
16
- }
17
- upsertProject(project) {
18
- const db = getDb();
19
- db.prepare(`INSERT INTO projects (id, name, path, type)
20
- VALUES (?, ?, ?, ?)
21
- ON CONFLICT(name) DO UPDATE SET
22
- path=excluded.path, type=excluded.type, last_used_at=datetime('now')
23
- `).run(project.id || uuidv4(), project.name, project.path, project.type || 'user-project');
24
- }
25
- updateProjectLastUsed(path) {
26
- const db = getDb();
27
- db.prepare('UPDATE projects SET last_used_at = datetime("now") WHERE path = ?').run(path);
28
- }
29
- // ─── Routing History ───
30
- getRecentRouting(limit = 10) {
31
- const db = getDb();
32
- const rows = db.prepare('SELECT message_preview, tab_name, confidence FROM routing_history ORDER BY created_at DESC LIMIT ?').all(limit);
33
- return rows.map(r => `"${r.message_preview}" → ${r.tab_name} (${Math.round(r.confidence * 100)}%)`);
34
- }
35
- recordRouting(messagePreview, tabName, projectPath, confidence) {
36
- const db = getDb();
37
- db.prepare('INSERT INTO routing_history (message_preview, tab_name, project_path, confidence) VALUES (?, ?, ?, ?)').run(messagePreview.slice(0, 200), tabName, projectPath, confidence);
38
- }
39
- }
@@ -1,6 +0,0 @@
1
- import type { Project } from './types.js';
2
- /**
3
- * Read projects from the database (populated by discoverProjects() at daemon startup).
4
- * Falls back to an empty list if the DB is not yet initialized.
5
- */
6
- export declare function scanForProjects(_scanPaths: string[]): Project[];
@@ -1,26 +0,0 @@
1
- import { getDb } from '../db/index.js';
2
- import { logger } from '../util/logger.js';
3
- /**
4
- * Read projects from the database (populated by discoverProjects() at daemon startup).
5
- * Falls back to an empty list if the DB is not yet initialized.
6
- */
7
- export function scanForProjects(_scanPaths) {
8
- try {
9
- const db = getDb();
10
- const rows = db.prepare('SELECT * FROM projects ORDER BY last_used_at DESC').all();
11
- const projects = rows.map(r => ({
12
- id: r.id,
13
- name: r.name,
14
- path: r.path,
15
- type: r.type,
16
- lastUsedAt: r.last_used_at,
17
- createdAt: r.created_at,
18
- }));
19
- logger.info(`Project scanner: found ${projects.length} projects from DB`);
20
- return projects;
21
- }
22
- catch (err) {
23
- logger.warn('Project scanner: failed to read from DB, returning empty list:', err);
24
- return [];
25
- }
26
- }
@@ -1,34 +0,0 @@
1
- export interface Project {
2
- id: string;
3
- name: string;
4
- path: string;
5
- type: string;
6
- lastUsedAt: string;
7
- createdAt: string;
8
- /** Populated at scan time, not persisted in DB */
9
- description?: string;
10
- /** Populated at scan time, not persisted in DB */
11
- languages?: string[];
12
- }
13
- export interface RouteDecision {
14
- tabName: string;
15
- projectPath: string | null;
16
- confidence: number;
17
- reason: string;
18
- needsConfirmation: boolean;
19
- }
20
- export interface ChatContext {
21
- chatId: number;
22
- userId: number;
23
- messageId: number;
24
- }
25
- export interface PipeResult {
26
- tabName: string;
27
- response: {
28
- text: string;
29
- error: boolean;
30
- costUsd: number;
31
- durationMs: number;
32
- };
33
- decisions: string[];
34
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,4 +0,0 @@
1
- export type { Project, RouteDecision, RoutingContext } from './types.js';
2
- export { discoverProjects, createProject, listProjects, getProject, ensureCategory, touchProject, } from './manager.js';
3
- export { routeMessage, setUserContext, resolveProjectRoute } from './router.js';
4
- export type { RouteResult } from './router.js';
@@ -1,2 +0,0 @@
1
- export { discoverProjects, createProject, listProjects, getProject, ensureCategory, touchProject, } from './manager.js';
2
- export { routeMessage, setUserContext, resolveProjectRoute } from './router.js';
@@ -1,13 +0,0 @@
1
- import type { Project } from './types.js';
2
- /** Discover projects in scan paths (look for git repos, package.json, etc.) */
3
- export declare function discoverProjects(scanPaths?: string[]): Project[];
4
- /** Create a new project. parentDir must resolve under an allowed root. */
5
- export declare function createProject(name: string, parentDir?: string): Project;
6
- /** Ensure a managed category exists (lazy creation) */
7
- export declare function ensureCategory(name: string): Project;
8
- /** List all projects */
9
- export declare function listProjects(): Project[];
10
- /** Get a project by name */
11
- export declare function getProject(name: string): Project | null;
12
- /** Update last used timestamp */
13
- export declare function touchProject(name: string): void;
@@ -1,166 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { v4 as uuidv4 } from 'uuid';
4
- import { getDb } from '../db/index.js';
5
- import { getConfig } from '../config.js';
6
- import { expandHome } from '../util/paths.js';
7
- import { logger } from '../util/logger.js';
8
- import { invalidateProjectCache } from './router.js';
9
- function rowToProject(r) {
10
- return {
11
- id: r.id,
12
- name: r.name,
13
- path: r.path,
14
- type: r.type,
15
- lastUsedAt: r.last_used_at,
16
- createdAt: r.created_at,
17
- };
18
- }
19
- /** Get the workspace root from config (module-local) */
20
- function getWorkspaceRoot() {
21
- const config = getConfig();
22
- // Use the default tab's workingDir as workspace root
23
- const root = config.tabs?.default?.workingDir || process.env.HOME || '';
24
- return expandHome(root);
25
- }
26
- /** Get the managed workspace path (.beecork/ under workspace root) (module-local) */
27
- function getManagedWorkspace() {
28
- return path.join(getWorkspaceRoot(), '.beecork');
29
- }
30
- /** Discover projects in scan paths (look for git repos, package.json, etc.) */
31
- export function discoverProjects(scanPaths) {
32
- const paths = scanPaths || [getWorkspaceRoot()];
33
- const projects = [];
34
- const db = getDb();
35
- for (let scanPath of paths) {
36
- scanPath = expandHome(scanPath);
37
- if (!fs.existsSync(scanPath))
38
- continue;
39
- try {
40
- const entries = fs.readdirSync(scanPath, { withFileTypes: true });
41
- for (const entry of entries) {
42
- if (!entry.isDirectory())
43
- continue;
44
- if (entry.name.startsWith('.'))
45
- continue; // Skip hidden dirs
46
- if (entry.name === 'node_modules')
47
- continue;
48
- const dirPath = path.join(scanPath, entry.name);
49
- // Check if it looks like a project (has .git, package.json, or similar)
50
- const isProject = fs.existsSync(path.join(dirPath, '.git')) ||
51
- fs.existsSync(path.join(dirPath, 'package.json')) ||
52
- fs.existsSync(path.join(dirPath, 'Cargo.toml')) ||
53
- fs.existsSync(path.join(dirPath, 'go.mod')) ||
54
- fs.existsSync(path.join(dirPath, 'requirements.txt')) ||
55
- fs.existsSync(path.join(dirPath, 'pyproject.toml')) ||
56
- fs.existsSync(path.join(dirPath, 'CLAUDE.md'));
57
- if (isProject) {
58
- projects.push({
59
- id: uuidv4(),
60
- name: entry.name,
61
- path: dirPath,
62
- type: 'user-project',
63
- lastUsedAt: new Date().toISOString(),
64
- createdAt: new Date().toISOString(),
65
- });
66
- }
67
- }
68
- }
69
- catch (err) {
70
- logger.warn(`Failed to scan ${scanPath}:`, err);
71
- }
72
- }
73
- // Upsert into database
74
- for (const project of projects) {
75
- db.prepare(`
76
- INSERT INTO projects (id, name, path, type) VALUES (?, ?, ?, ?)
77
- ON CONFLICT(name) DO UPDATE SET path = excluded.path, last_used_at = datetime('now')
78
- `).run(project.id, project.name, project.path, project.type);
79
- }
80
- invalidateProjectCache();
81
- return projects;
82
- }
83
- /** Create a new project. parentDir must resolve under an allowed root. */
84
- export function createProject(name, parentDir) {
85
- const requestedParent = parentDir || getWorkspaceRoot();
86
- const resolvedParent = path.resolve(expandHome(requestedParent));
87
- // Allowlist: parent must resolve under workspace root or one of the configured scan paths.
88
- const config = getConfig();
89
- const allowedRoots = [getWorkspaceRoot(), ...(config.projectScanPaths ?? [])].map((r) => path.resolve(expandHome(r)));
90
- const isAllowed = allowedRoots.some((root) => resolvedParent === root || resolvedParent.startsWith(root + path.sep));
91
- if (!isAllowed) {
92
- throw new Error(`Project parent directory must be under workspace root or a configured scan path. Allowed: ${allowedRoots.join(', ')}`);
93
- }
94
- // Sanitize name to prevent path traversal
95
- const safeName = path.basename(name.replace(/\.\./g, ''));
96
- if (!safeName)
97
- throw new Error('Invalid project name');
98
- const projectPath = path.resolve(resolvedParent, safeName);
99
- // Ensure resolved path is within the parent directory
100
- if (!projectPath.startsWith(resolvedParent)) {
101
- throw new Error('Project path must be within the parent directory');
102
- }
103
- if (fs.existsSync(projectPath)) {
104
- // Folder already exists — just register it
105
- logger.info(`Project folder already exists: ${projectPath}`);
106
- }
107
- else {
108
- fs.mkdirSync(projectPath, { recursive: true });
109
- logger.info(`Created project folder: ${projectPath}`);
110
- }
111
- const db = getDb();
112
- const id = uuidv4();
113
- db.prepare(`
114
- INSERT INTO projects (id, name, path, type) VALUES (?, ?, ?, ?)
115
- ON CONFLICT(name) DO UPDATE SET path = excluded.path
116
- `).run(id, name, projectPath, 'user-project');
117
- invalidateProjectCache();
118
- return {
119
- id,
120
- name,
121
- path: projectPath,
122
- type: 'user-project',
123
- lastUsedAt: new Date().toISOString(),
124
- createdAt: new Date().toISOString(),
125
- };
126
- }
127
- /** Ensure a managed category exists (lazy creation) */
128
- export function ensureCategory(name) {
129
- const categoryPath = path.join(getManagedWorkspace(), name);
130
- fs.mkdirSync(categoryPath, { recursive: true });
131
- const db = getDb();
132
- const existing = db
133
- .prepare('SELECT * FROM projects WHERE name = ? AND type = ?')
134
- .get(name, 'category');
135
- if (existing)
136
- return rowToProject(existing);
137
- const id = uuidv4();
138
- db.prepare('INSERT INTO projects (id, name, path, type) VALUES (?, ?, ?, ?)').run(id, name, categoryPath, 'category');
139
- invalidateProjectCache();
140
- return {
141
- id,
142
- name,
143
- path: categoryPath,
144
- type: 'category',
145
- lastUsedAt: new Date().toISOString(),
146
- createdAt: new Date().toISOString(),
147
- };
148
- }
149
- /** List all projects */
150
- export function listProjects() {
151
- const db = getDb();
152
- const rows = db
153
- .prepare('SELECT * FROM projects ORDER BY type, last_used_at DESC')
154
- .all();
155
- return rows.map(rowToProject);
156
- }
157
- /** Get a project by name */
158
- export function getProject(name) {
159
- const db = getDb();
160
- const row = db.prepare('SELECT * FROM projects WHERE name = ?').get(name);
161
- return row ? rowToProject(row) : null;
162
- }
163
- /** Update last used timestamp */
164
- export function touchProject(name) {
165
- getDb().prepare("UPDATE projects SET last_used_at = datetime('now') WHERE name = ?").run(name);
166
- }
@@ -1,17 +0,0 @@
1
- import type { RouteDecision, RoutingContext } from './types.js';
2
- /** Invalidate the project cache. Call after createProject/discoverProjects/etc. */
3
- export declare function invalidateProjectCache(): void;
4
- /** Route a message to the right project and tab */
5
- export declare function routeMessage(message: string, context?: RoutingContext): RouteDecision;
6
- /** Update current context for a user */
7
- export declare function setUserContext(userId: string, projectName: string, tabName: string): void;
8
- export interface RouteResult {
9
- effectiveTabName: string;
10
- projectPath?: string;
11
- confirmationMessage?: string;
12
- }
13
- /**
14
- * Shared project routing logic — resolves which tab/project to use for a message.
15
- * Pulled out of channels/ so all routing decisions live in one place.
16
- */
17
- export declare function resolveProjectRoute(rawPrompt: string, tabName: string, text: string, userId: string): Promise<RouteResult>;
@@ -1,246 +0,0 @@
1
- import { getDb } from '../db/index.js';
2
- import { listProjects, getProject, ensureCategory, touchProject } from './manager.js';
3
- // Category keywords for non-project routing
4
- const CATEGORY_KEYWORDS = {
5
- research: ['research', 'find out', 'look into', 'compare', 'investigate', 'analyze', 'study'],
6
- planning: ['plan', 'schedule', 'organize', 'roadmap', 'strategy', 'outline', 'agenda'],
7
- };
8
- // Per-user current context tracking (in-memory, resets on daemon restart)
9
- const userContext = new Map();
10
- // Project list cache. listProjects() ran on every inbound message and re-did a
11
- // full SELECT + ORDER BY across the projects table; with N projects each call
12
- // then re-lowercased every name. The cache is invalidated explicitly via
13
- // invalidateProjectCache() at the few sites that mutate the table.
14
- let projectsCache = null;
15
- const PROJECTS_CACHE_TTL_MS = 30_000;
16
- function getUserProjectsCached() {
17
- const now = Date.now();
18
- if (projectsCache && projectsCache.expiresAt > now)
19
- return projectsCache.user;
20
- const user = listProjects()
21
- .filter((p) => p.type === 'user-project')
22
- .map((p) => ({ ...p, nameLower: p.name.toLowerCase() }));
23
- projectsCache = { user, expiresAt: now + PROJECTS_CACHE_TTL_MS };
24
- return user;
25
- }
26
- /** Invalidate the project cache. Call after createProject/discoverProjects/etc. */
27
- export function invalidateProjectCache() {
28
- projectsCache = null;
29
- }
30
- /** Route a message to the right project and tab */
31
- export function routeMessage(message, context) {
32
- const projects = getUserProjectsCached();
33
- const userId = context?.userId || 'default';
34
- const messageLower = message.toLowerCase();
35
- // 1. Check for explicit project mention by name
36
- const mentionedProject = findMentionedProject(messageLower, projects);
37
- if (mentionedProject) {
38
- const { tabName, exists } = resolveTabInProject(mentionedProject);
39
- touchProject(mentionedProject.name);
40
- recordRouting(message, mentionedProject.name);
41
- return {
42
- project: mentionedProject,
43
- tabName,
44
- isNewTab: !exists,
45
- confidence: 0.95,
46
- needsConfirmation: false,
47
- reason: `Message mentions project "${mentionedProject.name}"`,
48
- };
49
- }
50
- // 2. Check if continuing current context
51
- const currentCtx = userContext.get(userId);
52
- if (currentCtx && Date.now() - currentCtx.updatedAt < 10 * 60 * 1000) {
53
- // 10 min window
54
- const project = getProject(currentCtx.projectName);
55
- if (project) {
56
- touchProject(project.name);
57
- return {
58
- project,
59
- tabName: currentCtx.tabName,
60
- isNewTab: false,
61
- confidence: 0.7,
62
- needsConfirmation: false,
63
- reason: `Continuing in "${project.name}" (recent context)`,
64
- };
65
- }
66
- }
67
- // 3. Check routing preferences (learned patterns)
68
- const learned = checkLearnedRouting(messageLower);
69
- if (learned) {
70
- const project = getProject(learned.projectName);
71
- if (project && learned.confidence >= 0.9) {
72
- const { tabName, exists } = resolveTabInProject(project);
73
- touchProject(project.name);
74
- return {
75
- project,
76
- tabName,
77
- isNewTab: !exists,
78
- confidence: learned.confidence,
79
- needsConfirmation: false,
80
- reason: `Learned pattern → "${project.name}"`,
81
- };
82
- }
83
- }
84
- // 4. Multiple projects could match — ask user
85
- const possibleMatches = findPossibleMatches(messageLower, projects);
86
- if (possibleMatches.length > 1) {
87
- return {
88
- project: possibleMatches[0],
89
- tabName: possibleMatches[0].name,
90
- isNewTab: false,
91
- confidence: 0.5,
92
- needsConfirmation: true,
93
- reason: `Multiple projects could match: ${possibleMatches.map((p) => p.name).join(', ')}`,
94
- };
95
- }
96
- // 5. Check for category keywords (non-project)
97
- const category = detectCategory(messageLower);
98
- if (category) {
99
- const project = ensureCategory(category);
100
- const tabName = category;
101
- return {
102
- project,
103
- tabName,
104
- isNewTab: !tabExists(tabName),
105
- confidence: 0.8,
106
- needsConfirmation: false,
107
- reason: `Detected category: ${category}`,
108
- };
109
- }
110
- // 6. Default to general
111
- const general = ensureCategory('general');
112
- return {
113
- project: general,
114
- tabName: 'general',
115
- isNewTab: !tabExists('general'),
116
- confidence: 0.6,
117
- needsConfirmation: false,
118
- reason: 'Default routing to general',
119
- };
120
- }
121
- /** Update current context for a user */
122
- export function setUserContext(userId, projectName, tabName) {
123
- userContext.set(userId, { projectName, tabName, updatedAt: Date.now() });
124
- // Evict oldest entry when map grows too large
125
- if (userContext.size > 1000) {
126
- let oldestKey = '';
127
- let oldestTime = Infinity;
128
- for (const [key, val] of userContext) {
129
- if (val.updatedAt < oldestTime) {
130
- oldestTime = val.updatedAt;
131
- oldestKey = key;
132
- }
133
- }
134
- if (oldestKey)
135
- userContext.delete(oldestKey);
136
- }
137
- }
138
- /** Find a project explicitly mentioned by name in the message (messageLower already lower-cased) */
139
- function findMentionedProject(messageLower, projects) {
140
- for (const project of projects) {
141
- if (messageLower.includes(project.nameLower))
142
- return project;
143
- }
144
- return null;
145
- }
146
- /** Find possible project matches (for ambiguity) */
147
- function findPossibleMatches(messageLower, projects) {
148
- const words = messageLower.split(/\s+/);
149
- return projects.filter((p) => {
150
- const nameParts = p.nameLower.split(/[-_]/);
151
- return nameParts.some((part) => words.includes(part));
152
- });
153
- }
154
- /**
155
- * Resolve which tab to use within a project. Coalesces the previous
156
- * "find tabs + tabExists" into one query — the caller used to fire both
157
- * back-to-back, and the working_dir scan had no covering index.
158
- * (Index added by migration v28; see src/db/migrations.ts.)
159
- */
160
- function resolveTabInProject(project) {
161
- const db = getDb();
162
- const row = db
163
- .prepare('SELECT name FROM tabs WHERE working_dir = ? ORDER BY last_activity_at DESC LIMIT 1')
164
- .get(project.path);
165
- return row ? { tabName: row.name, exists: true } : { tabName: project.name, exists: false };
166
- }
167
- /** Check if a tab exists in the database */
168
- function tabExists(tabName) {
169
- const db = getDb();
170
- const row = db.prepare('SELECT 1 FROM tabs WHERE name = ?').get(tabName);
171
- return !!row;
172
- }
173
- /** Detect a category from keywords (messageLower already lower-cased) */
174
- function detectCategory(messageLower) {
175
- for (const [category, keywords] of Object.entries(CATEGORY_KEYWORDS)) {
176
- if (keywords.some((kw) => messageLower.includes(kw)))
177
- return category;
178
- }
179
- return null;
180
- }
181
- /** Record a successful routing for learning */
182
- function recordRouting(message, projectName) {
183
- const db = getDb();
184
- // Extract key words from message for pattern matching
185
- const words = message
186
- .toLowerCase()
187
- .split(/\s+/)
188
- .filter((w) => w.length > 3)
189
- .slice(0, 5);
190
- const pattern = words.join(' ');
191
- if (!pattern)
192
- return;
193
- db.prepare(`
194
- INSERT INTO routing_preferences (pattern, project_name)
195
- VALUES (?, ?)
196
- ON CONFLICT(pattern, project_name) DO UPDATE SET hit_count = hit_count + 1
197
- `).run(pattern, projectName);
198
- }
199
- /** Check learned routing preferences (messageLower already lower-cased) */
200
- function checkLearnedRouting(messageLower) {
201
- const db = getDb();
202
- const words = messageLower.split(/\s+/).filter((w) => w.length > 3);
203
- if (words.length === 0)
204
- return null;
205
- // Fetch all preferences in a single query and match in JS
206
- const allPrefs = db
207
- .prepare('SELECT pattern, project_name, confidence, hit_count FROM routing_preferences WHERE hit_count >= 3 ORDER BY hit_count DESC LIMIT 200')
208
- .all();
209
- for (const pref of allPrefs) {
210
- // pref.pattern was written via .toLowerCase() in recordRouting(), so no
211
- // re-lowercase here (saves N allocations per inbound message).
212
- const patternLower = pref.pattern;
213
- if (words.some((word) => patternLower.includes(word) || word.includes(patternLower))) {
214
- return { projectName: pref.project_name, confidence: Math.min(pref.confidence, 0.9) };
215
- }
216
- }
217
- return null;
218
- }
219
- /**
220
- * Shared project routing logic — resolves which tab/project to use for a message.
221
- * Pulled out of channels/ so all routing decisions live in one place.
222
- */
223
- export async function resolveProjectRoute(rawPrompt, tabName, text, userId) {
224
- if (tabName !== 'default' || text.startsWith('/tab ')) {
225
- return { effectiveTabName: tabName };
226
- }
227
- try {
228
- const decision = routeMessage(rawPrompt, { userId });
229
- if (decision.needsConfirmation) {
230
- const projects = listProjects().filter((p) => p.type === 'user-project');
231
- const options = projects.map((p, i) => `${i + 1}) ${p.name}`).join('\n');
232
- return {
233
- effectiveTabName: tabName,
234
- confirmationMessage: `Which project?\n${options}\n\nReply with the number, or just send your message with /project <name> first.`,
235
- };
236
- }
237
- setUserContext(userId, decision.project.name, decision.tabName);
238
- return {
239
- effectiveTabName: decision.tabName,
240
- projectPath: decision.project.path,
241
- };
242
- }
243
- catch {
244
- return { effectiveTabName: tabName };
245
- }
246
- }