daybrain 0.3.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/bin/daybrain.js +18 -0
- package/dist/aw.d.ts +30 -0
- package/dist/aw.d.ts.map +1 -0
- package/dist/aw.js +145 -0
- package/dist/aw.js.map +1 -0
- package/dist/baseline.d.ts +31 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +130 -0
- package/dist/baseline.js.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +139 -0
- package/dist/config.js.map +1 -0
- package/dist/db.d.ts +72 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +261 -0
- package/dist/db.js.map +1 -0
- package/dist/http.d.ts +5 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +411 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +908 -0
- package/dist/index.js.map +1 -0
- package/dist/insights.d.ts +68 -0
- package/dist/insights.d.ts.map +1 -0
- package/dist/insights.js +523 -0
- package/dist/insights.js.map +1 -0
- package/dist/onboard.d.ts +2 -0
- package/dist/onboard.d.ts.map +1 -0
- package/dist/onboard.js +121 -0
- package/dist/onboard.js.map +1 -0
- package/dist/scheduler.d.ts +8 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +214 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/screenpipe.d.ts +25 -0
- package/dist/screenpipe.d.ts.map +1 -0
- package/dist/screenpipe.js +71 -0
- package/dist/screenpipe.js.map +1 -0
- package/dist/transport.d.ts +12 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +152 -0
- package/dist/transport.js.map +1 -0
- package/dist/watcher.d.ts +15 -0
- package/dist/watcher.d.ts.map +1 -0
- package/dist/watcher.js +173 -0
- package/dist/watcher.js.map +1 -0
- package/package.json +58 -0
- package/watcher.py +139 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,908 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.toolHandlers = void 0;
|
|
5
|
+
exports.main = main;
|
|
6
|
+
exports.buildTools = buildTools;
|
|
7
|
+
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
8
|
+
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
9
|
+
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
10
|
+
const config_1 = require("./config");
|
|
11
|
+
const db_1 = require("./db");
|
|
12
|
+
const aw_1 = require("./aw");
|
|
13
|
+
const screenpipe_1 = require("./screenpipe");
|
|
14
|
+
const insights_1 = require("./insights");
|
|
15
|
+
const transport_1 = require("./transport");
|
|
16
|
+
const scheduler_1 = require("./scheduler");
|
|
17
|
+
const watcher_1 = require("./watcher");
|
|
18
|
+
const http_1 = require("./http");
|
|
19
|
+
const SERVER_NAME = 'daybrain';
|
|
20
|
+
const SERVER_VERSION = '0.3.0';
|
|
21
|
+
function buildTools() {
|
|
22
|
+
return [
|
|
23
|
+
{
|
|
24
|
+
name: 'get_activity_summary',
|
|
25
|
+
description: 'Get a summary of your digital activity. Shows what apps you used, for how long, categorized by type. Ask "What did I do today?" or "How productive was I?" to use this.',
|
|
26
|
+
inputSchema: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
date: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'Date to summarize (YYYY-MM-DD). Defaults to today.',
|
|
32
|
+
},
|
|
33
|
+
period: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'Alternative: time period string. E.g. "today", "yesterday", "last 7 days". Overrides date.',
|
|
36
|
+
},
|
|
37
|
+
include_hourly: {
|
|
38
|
+
type: 'boolean',
|
|
39
|
+
description: 'Include hourly breakdown of activity.',
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'get_context_switching',
|
|
46
|
+
description: 'Analyze how often you switch between different apps and identify distraction patterns. Ask "How much did I context switch today?"',
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
date: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: 'Date to analyze (YYYY-MM-DD). Defaults to today.',
|
|
53
|
+
},
|
|
54
|
+
period: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: 'Alternative: time period string.',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'get_insights',
|
|
63
|
+
description: 'Extract insights from your activity: commitments you made, things you might be avoiding, focus patterns, and unusual behavior. Ask "What am I avoiding?" or "What promises did I make?"',
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: 'object',
|
|
66
|
+
properties: {
|
|
67
|
+
type_filter: {
|
|
68
|
+
type: 'string',
|
|
69
|
+
description: 'Filter by insight type: commitment, avoidance, pattern, context_switch, focus, deadline',
|
|
70
|
+
},
|
|
71
|
+
min_confidence: {
|
|
72
|
+
type: 'number',
|
|
73
|
+
description: 'Minimum confidence threshold (0.0-1.0). Default 0.4.',
|
|
74
|
+
},
|
|
75
|
+
date: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
description: 'Date to analyze (YYYY-MM-DD). Defaults to today.',
|
|
78
|
+
},
|
|
79
|
+
limit: {
|
|
80
|
+
type: 'number',
|
|
81
|
+
description: 'Maximum insights to return (default 20).',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'get_project_context',
|
|
88
|
+
description: 'Get activity related to a specific project. Finds all windows and apps connected to a project keyword. Ask "Summarize my work on project X."',
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
project_keyword: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
description: 'Keyword or project name to search for in window titles.',
|
|
95
|
+
},
|
|
96
|
+
date: {
|
|
97
|
+
type: 'string',
|
|
98
|
+
description: 'Date to analyze (YYYY-MM-DD). Defaults to today.',
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
required: ['project_keyword'],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'get_raw_events',
|
|
106
|
+
description: 'Get raw activity events from the local database. Useful for custom analysis or debugging.',
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
date: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
description: 'Date to query (YYYY-MM-DD). Defaults to today.',
|
|
113
|
+
},
|
|
114
|
+
source: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: 'Filter by source: activitywatch or screenpipe.',
|
|
117
|
+
},
|
|
118
|
+
app_filter: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
description: 'Filter events by app name (partial match).',
|
|
121
|
+
},
|
|
122
|
+
limit: {
|
|
123
|
+
type: 'number',
|
|
124
|
+
description: 'Maximum events to return (default 500, max 5000).',
|
|
125
|
+
},
|
|
126
|
+
offset: {
|
|
127
|
+
type: 'number',
|
|
128
|
+
description: 'Offset for pagination.',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'run_insight_loop',
|
|
135
|
+
description: 'Manually trigger the insight extraction loop. Pulls fresh data from ActivityWatch, extracts insights, and stores them.',
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: {},
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'push_insight',
|
|
143
|
+
description: 'Push a specific insight or summary to configured transports (Slack, Telegram, webhook).',
|
|
144
|
+
inputSchema: {
|
|
145
|
+
type: 'object',
|
|
146
|
+
properties: {
|
|
147
|
+
insight_id: {
|
|
148
|
+
type: 'number',
|
|
149
|
+
description: 'ID of the insight to push. If omitted, pushes the latest daily summary.',
|
|
150
|
+
},
|
|
151
|
+
targets: {
|
|
152
|
+
type: 'array',
|
|
153
|
+
items: { type: 'string' },
|
|
154
|
+
description: 'Transport targets: slack, telegram, webhook. If omitted, uses all configured transports.',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: 'health_check',
|
|
161
|
+
description: 'Check the status of OpenContext and all connected data sources (ActivityWatch, Screenpipe).',
|
|
162
|
+
inputSchema: {
|
|
163
|
+
type: 'object',
|
|
164
|
+
properties: {},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'get_configuration',
|
|
169
|
+
description: 'View current OpenContext configuration.',
|
|
170
|
+
inputSchema: {
|
|
171
|
+
type: 'object',
|
|
172
|
+
properties: {
|
|
173
|
+
section: {
|
|
174
|
+
type: 'string',
|
|
175
|
+
description: 'Config section to view: activitywatch, screenpipe, transports, insights, or all.',
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'create_linear_task',
|
|
182
|
+
description: 'Create a Linear issue from a detected commitment or insight. Requires a Linear API key in config.',
|
|
183
|
+
inputSchema: {
|
|
184
|
+
type: 'object',
|
|
185
|
+
properties: {
|
|
186
|
+
title: { type: 'string', description: 'Task title (e.g., "Send demo to Sarah by Friday")' },
|
|
187
|
+
description: { type: 'string', description: 'Optional task description' },
|
|
188
|
+
team_id: { type: 'string', description: 'Linear team ID (e.g., "ENG")' },
|
|
189
|
+
},
|
|
190
|
+
required: ['title'],
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'create_github_issue',
|
|
195
|
+
description: 'Create a GitHub issue from a detected commitment or insight. Requires a GitHub token and repo in config.',
|
|
196
|
+
inputSchema: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
properties: {
|
|
199
|
+
title: { type: 'string', description: 'Issue title' },
|
|
200
|
+
body: { type: 'string', description: 'Optional issue body' },
|
|
201
|
+
repo: { type: 'string', description: 'Repository (e.g., "owner/repo"). Defaults to configured repo.' },
|
|
202
|
+
labels: { type: 'array', items: { type: 'string' }, description: 'Labels to apply' },
|
|
203
|
+
},
|
|
204
|
+
required: ['title'],
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
];
|
|
208
|
+
}
|
|
209
|
+
function getDateRange(args) {
|
|
210
|
+
let date;
|
|
211
|
+
if (args.period) {
|
|
212
|
+
date = String(args.period);
|
|
213
|
+
}
|
|
214
|
+
else if (args.date) {
|
|
215
|
+
date = String(args.date);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
date = 'today';
|
|
219
|
+
}
|
|
220
|
+
const start = new Date();
|
|
221
|
+
const end = new Date();
|
|
222
|
+
if (date === 'today') {
|
|
223
|
+
start.setHours(0, 0, 0, 0);
|
|
224
|
+
}
|
|
225
|
+
else if (date === 'yesterday') {
|
|
226
|
+
start.setDate(start.getDate() - 1);
|
|
227
|
+
start.setHours(0, 0, 0, 0);
|
|
228
|
+
end.setDate(end.getDate() - 1);
|
|
229
|
+
end.setHours(23, 59, 59, 999);
|
|
230
|
+
}
|
|
231
|
+
else if (/^\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
232
|
+
const [y, m, d] = date.split('-').map(Number);
|
|
233
|
+
start.setFullYear(y, m - 1, d);
|
|
234
|
+
start.setHours(0, 0, 0, 0);
|
|
235
|
+
end.setFullYear(y, m - 1, d);
|
|
236
|
+
end.setHours(23, 59, 59, 999);
|
|
237
|
+
}
|
|
238
|
+
else if (/^\d{4}-\d{2}-\d{2},\d{4}-\d{2}-\d{2}$/.test(date)) {
|
|
239
|
+
const [startStr, endStr] = date.split(',');
|
|
240
|
+
start.setTime(new Date(startStr).getTime());
|
|
241
|
+
start.setHours(0, 0, 0, 0);
|
|
242
|
+
end.setTime(new Date(endStr).getTime());
|
|
243
|
+
end.setHours(23, 59, 59, 999);
|
|
244
|
+
}
|
|
245
|
+
else if (/^last\s+(\d+)\s+days?$/i.test(date)) {
|
|
246
|
+
const match = date.match(/^last\s+(\d+)\s+days?$/i);
|
|
247
|
+
const days = parseInt(match[1], 10);
|
|
248
|
+
start.setDate(start.getDate() - days);
|
|
249
|
+
start.setHours(0, 0, 0, 0);
|
|
250
|
+
}
|
|
251
|
+
else if (date === 'this week') {
|
|
252
|
+
const dayOfWeek = start.getDay();
|
|
253
|
+
const diffToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
254
|
+
start.setDate(start.getDate() - diffToMonday);
|
|
255
|
+
start.setHours(0, 0, 0, 0);
|
|
256
|
+
}
|
|
257
|
+
else if (date === 'last week') {
|
|
258
|
+
const dayOfWeek = start.getDay();
|
|
259
|
+
const diffToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
|
|
260
|
+
start.setDate(start.getDate() - diffToMonday - 7);
|
|
261
|
+
start.setHours(0, 0, 0, 0);
|
|
262
|
+
end.setTime(start.getTime());
|
|
263
|
+
end.setDate(start.getDate() + 6);
|
|
264
|
+
end.setHours(23, 59, 59, 999);
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
start: start.toISOString(),
|
|
268
|
+
end: end.toISOString(),
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
async function fetchEvents(args) {
|
|
272
|
+
const { start, end } = getDateRange(args);
|
|
273
|
+
const cachedEvents = (0, db_1.getRawEvents)({
|
|
274
|
+
periodStart: start,
|
|
275
|
+
periodEnd: end,
|
|
276
|
+
limit: 10000,
|
|
277
|
+
});
|
|
278
|
+
const isToday = new Date(start).toDateString() === new Date().toDateString();
|
|
279
|
+
const cacheAge = cachedEvents.length > 0
|
|
280
|
+
? Date.now() - new Date(cachedEvents[cachedEvents.length - 1].timestamp).getTime()
|
|
281
|
+
: Infinity;
|
|
282
|
+
const isStale = isToday && (cachedEvents.length === 0 || cacheAge > 15 * 60 * 1000);
|
|
283
|
+
if (cachedEvents.length > 0 && !isStale) {
|
|
284
|
+
return {
|
|
285
|
+
events: cachedEvents.map(e => ({
|
|
286
|
+
timestamp: e.timestamp,
|
|
287
|
+
duration: e.duration,
|
|
288
|
+
data: { app: e.app, title: e.title, url: e.url },
|
|
289
|
+
})),
|
|
290
|
+
periodStart: start,
|
|
291
|
+
periodEnd: end,
|
|
292
|
+
fromCache: true,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const aw = (0, aw_1.createAWClient)();
|
|
296
|
+
try {
|
|
297
|
+
const windowEvents = await aw.getWindowEventsByRange(start, end);
|
|
298
|
+
const rawEvts = windowEvents.map((evt) => ({
|
|
299
|
+
source: 'activitywatch',
|
|
300
|
+
bucket_id: 'aw-watcher-window',
|
|
301
|
+
timestamp: evt.timestamp,
|
|
302
|
+
duration: evt.duration,
|
|
303
|
+
app: String(evt.data?.app || ''),
|
|
304
|
+
title: String(evt.data?.title || ''),
|
|
305
|
+
url: String(evt.data?.url || ''),
|
|
306
|
+
raw_data: JSON.stringify(evt.data),
|
|
307
|
+
}));
|
|
308
|
+
if (rawEvts.length > 0) {
|
|
309
|
+
try {
|
|
310
|
+
(0, db_1.insertRawEvents)(rawEvts);
|
|
311
|
+
}
|
|
312
|
+
catch { }
|
|
313
|
+
}
|
|
314
|
+
return {
|
|
315
|
+
events: windowEvents,
|
|
316
|
+
periodStart: start,
|
|
317
|
+
periodEnd: end,
|
|
318
|
+
fromCache: false,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
catch (err) {
|
|
322
|
+
console.error('[daybrain] Failed to fetch AW events:', err instanceof Error ? err.message : String(err));
|
|
323
|
+
return {
|
|
324
|
+
events: [],
|
|
325
|
+
periodStart: start,
|
|
326
|
+
periodEnd: end,
|
|
327
|
+
fromCache: false,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
const toolHandlers = {
|
|
332
|
+
async get_activity_summary(args) {
|
|
333
|
+
const { events, periodStart, periodEnd, fromCache } = await fetchEvents(args);
|
|
334
|
+
if (events.length === 0) {
|
|
335
|
+
return {
|
|
336
|
+
content: [{
|
|
337
|
+
type: 'text',
|
|
338
|
+
text: 'No activity data found for this period. Make sure ActivityWatch is running (https://activitywatch.net).',
|
|
339
|
+
}],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
const engine = (0, insights_1.createInsightEngine)();
|
|
343
|
+
const { appSummaries, totalActiveTime, uniqueApps, hourlyBreakdown } = engine.analyzeWindowEvents(events);
|
|
344
|
+
const summary = engine.generateDailySummary(events);
|
|
345
|
+
const focusBlocks = engine.detectFocusBlocks(events);
|
|
346
|
+
let result = summary;
|
|
347
|
+
result += `\nData source: ${fromCache ? 'local cache' : 'fresh from ActivityWatch'}`;
|
|
348
|
+
if (args.include_hourly && hourlyBreakdown.length > 0) {
|
|
349
|
+
result += `\n\nHourly Breakdown:\n`;
|
|
350
|
+
for (const h of hourlyBreakdown) {
|
|
351
|
+
const timeLabel = h.hour.slice(11, 16);
|
|
352
|
+
result += ` ${timeLabel} ${h.topApp.padEnd(20)} ${h.activeMinutes}min active ${h.switchCount} switches\n`;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
if (focusBlocks.length > 0) {
|
|
356
|
+
result += `\n\nFocus Blocks (>10 min uninterrupted):\n`;
|
|
357
|
+
for (const b of focusBlocks.slice(0, 5)) {
|
|
358
|
+
result += ` ${b.app} — ${b.durationMinutes} minutes\n`;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
return {
|
|
362
|
+
content: [{ type: 'text', text: result }],
|
|
363
|
+
};
|
|
364
|
+
},
|
|
365
|
+
async get_context_switching(args) {
|
|
366
|
+
const { events, fromCache } = await fetchEvents(args);
|
|
367
|
+
if (events.length === 0) {
|
|
368
|
+
return {
|
|
369
|
+
content: [{ type: 'text', text: 'No activity data found. Make sure ActivityWatch is running.' }],
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const engine = (0, insights_1.createInsightEngine)();
|
|
373
|
+
const { contextSwitches, hourlyBreakdown, totalActiveTime } = engine.analyzeWindowEvents(events);
|
|
374
|
+
let result = `Context Switching Report\n`;
|
|
375
|
+
result += `━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
376
|
+
result += `Data source: ${fromCache ? 'local cache' : 'fresh from AW'}\n\n`;
|
|
377
|
+
const totalSwitches = contextSwitches.reduce((s, c) => s + c.count, 0);
|
|
378
|
+
const activeHours = totalActiveTime / 3600;
|
|
379
|
+
const switchesPerHour = activeHours > 0 ? Math.round(totalSwitches / activeHours) : 0;
|
|
380
|
+
result += `Total switches: ${totalSwitches}\n`;
|
|
381
|
+
result += `Switches per active hour: ${switchesPerHour}\n`;
|
|
382
|
+
result += `Verdict: ${switchesPerHour > 30 ? 'High fragmentation — consider batching tasks' : switchesPerHour > 15 ? 'Moderate switching — room for improvement' : 'Good focus levels'}\n\n`;
|
|
383
|
+
result += `Top App Transitions:\n`;
|
|
384
|
+
for (const sw of contextSwitches.slice(0, 15)) {
|
|
385
|
+
result += ` ${sw.fromApp} → ${sw.toApp}: ${sw.count}x\n`;
|
|
386
|
+
}
|
|
387
|
+
result += `\nHourly Switching:\n`;
|
|
388
|
+
for (const h of hourlyBreakdown) {
|
|
389
|
+
const timeLabel = h.hour.slice(11, 16);
|
|
390
|
+
const indicator = h.switchCount > 25 ? '🔴' : h.switchCount > 15 ? '🟡' : '🟢';
|
|
391
|
+
result += ` ${timeLabel} ${indicator} ${h.switchCount} switches (${h.activeMinutes}min active, primary: ${h.topApp})\n`;
|
|
392
|
+
}
|
|
393
|
+
const switchingInsights = engine.detectContextSwitchingProblems(events);
|
|
394
|
+
if (switchingInsights.length > 0) {
|
|
395
|
+
result += `\nDetected Issues:\n`;
|
|
396
|
+
for (const insight of switchingInsights) {
|
|
397
|
+
result += ` ⚠ ${insight.title}\n`;
|
|
398
|
+
result += ` ${insight.description}\n`;
|
|
399
|
+
result += ` → ${insight.action_text}\n\n`;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
content: [{ type: 'text', text: result }],
|
|
404
|
+
};
|
|
405
|
+
},
|
|
406
|
+
async get_insights(args) {
|
|
407
|
+
const storedInsights = (0, db_1.getInsights)({
|
|
408
|
+
type: args.type_filter ? String(args.type_filter) : undefined,
|
|
409
|
+
minConfidence: args.min_confidence ? Number(args.min_confidence) : undefined,
|
|
410
|
+
limit: args.limit ? Number(args.limit) : 20,
|
|
411
|
+
});
|
|
412
|
+
if (storedInsights.length === 0) {
|
|
413
|
+
const { events } = await fetchEvents(args);
|
|
414
|
+
if (events.length === 0) {
|
|
415
|
+
return {
|
|
416
|
+
content: [{ type: 'text', text: 'No activity data and no stored insights found. Make sure ActivityWatch is running and the insight loop has run (use `run_insight_loop` tool).' }],
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
const engine = (0, insights_1.createInsightEngine)();
|
|
420
|
+
const analysis = engine.runFullAnalysis(events);
|
|
421
|
+
if (analysis.insights.length > 0) {
|
|
422
|
+
(0, db_1.insertInsights)(analysis.insights.map(i => ({
|
|
423
|
+
type: i.type,
|
|
424
|
+
period_start: i.period_start,
|
|
425
|
+
period_end: i.period_end,
|
|
426
|
+
title: i.title,
|
|
427
|
+
description: i.description,
|
|
428
|
+
confidence: i.confidence,
|
|
429
|
+
evidence: i.evidence,
|
|
430
|
+
action_text: i.action_text,
|
|
431
|
+
})));
|
|
432
|
+
}
|
|
433
|
+
if (analysis.insights.length === 0) {
|
|
434
|
+
return {
|
|
435
|
+
content: [{ type: 'text', text: 'No significant insights detected in this period. Your activity patterns look normal.' }],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
let result = `Extracted Insights (fresh analysis)\n`;
|
|
439
|
+
result += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
440
|
+
for (const insight of analysis.insights) {
|
|
441
|
+
result += `${insightEmoji(insight.type)} ${insight.title} [${Math.round(insight.confidence * 100)}%]\n`;
|
|
442
|
+
result += ` ${insight.description}\n`;
|
|
443
|
+
if (insight.action_text) {
|
|
444
|
+
result += ` → ${insight.action_text}\n`;
|
|
445
|
+
}
|
|
446
|
+
result += `\n`;
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
content: [{ type: 'text', text: result }],
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
let result = `Stored Insights\n`;
|
|
453
|
+
result += `━━━━━━━━━━━━━━\n\n`;
|
|
454
|
+
for (const insight of storedInsights) {
|
|
455
|
+
result += `${insightEmoji(insight.type)} ${insight.title} [${Math.round(insight.confidence * 100)}%]\n`;
|
|
456
|
+
result += ` ${insight.description}\n`;
|
|
457
|
+
if (insight.action_text) {
|
|
458
|
+
result += ` → ${insight.action_text}\n`;
|
|
459
|
+
}
|
|
460
|
+
if (insight.pushed_to) {
|
|
461
|
+
result += ` 📤 Pushed to: ${insight.pushed_to}\n`;
|
|
462
|
+
}
|
|
463
|
+
result += `\n`;
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
content: [{ type: 'text', text: result }],
|
|
467
|
+
};
|
|
468
|
+
},
|
|
469
|
+
async get_project_context(args) {
|
|
470
|
+
const keyword = String(args.project_keyword || '');
|
|
471
|
+
if (!keyword) {
|
|
472
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'project_keyword is required');
|
|
473
|
+
}
|
|
474
|
+
const { events, fromCache } = await fetchEvents(args);
|
|
475
|
+
if (events.length === 0) {
|
|
476
|
+
return {
|
|
477
|
+
content: [{ type: 'text', text: 'No activity data found. Make sure ActivityWatch is running.' }],
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
const engine = (0, insights_1.createInsightEngine)();
|
|
481
|
+
const projects = engine.detectProjectContext(events, [keyword]);
|
|
482
|
+
if (projects.length === 0 || projects[0].eventCount === 0) {
|
|
483
|
+
return {
|
|
484
|
+
content: [{ type: 'text', text: `No activity found related to "${keyword}". Try a different keyword.` }],
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const p = projects[0];
|
|
488
|
+
const totalMinutes = Math.round(p.totalDuration / 6) / 10;
|
|
489
|
+
const totalHours = Math.round(p.totalDuration / 360) / 10;
|
|
490
|
+
let result = `Project Context: "${p.project}"\n`;
|
|
491
|
+
result += `━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
492
|
+
result += `Total time: ${totalHours >= 1 ? `${totalHours} hours` : `${totalMinutes} minutes`}\n`;
|
|
493
|
+
result += `Events found: ${p.eventCount}\n`;
|
|
494
|
+
result += `Related apps: ${p.relatedApps.join(', ')}\n`;
|
|
495
|
+
result += `Data source: ${fromCache ? 'local cache' : 'fresh from AW'}\n\n`;
|
|
496
|
+
if (p.relevantEvents.length > 0) {
|
|
497
|
+
result += `Recent activity:\n`;
|
|
498
|
+
for (const evt of p.relevantEvents.slice(0, 10)) {
|
|
499
|
+
const time = new Date(evt.timestamp).toLocaleTimeString();
|
|
500
|
+
result += ` [${time}] ${evt.app}: ${evt.title}\n`;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
content: [{ type: 'text', text: result }],
|
|
505
|
+
};
|
|
506
|
+
},
|
|
507
|
+
async get_raw_events(args) {
|
|
508
|
+
const limit = Math.min(args.limit ? Number(args.limit) : 500, 5000);
|
|
509
|
+
const offset = args.offset ? Number(args.offset) : 0;
|
|
510
|
+
const { start, end } = getDateRange(args);
|
|
511
|
+
const events = (0, db_1.getRawEvents)({
|
|
512
|
+
periodStart: start,
|
|
513
|
+
periodEnd: end,
|
|
514
|
+
source: args.source ? String(args.source) : undefined,
|
|
515
|
+
app: args.app_filter ? String(args.app_filter) : undefined,
|
|
516
|
+
limit,
|
|
517
|
+
offset,
|
|
518
|
+
});
|
|
519
|
+
if (events.length === 0) {
|
|
520
|
+
return {
|
|
521
|
+
content: [{ type: 'text', text: 'No raw events found for this query. Make sure ActivityWatch is running.' }],
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
let result = `Raw Events (${events.length} results)\n`;
|
|
525
|
+
result += `━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n`;
|
|
526
|
+
for (const evt of events.slice(0, 50)) {
|
|
527
|
+
const time = new Date(evt.timestamp).toLocaleTimeString();
|
|
528
|
+
const dur = Math.round(evt.duration);
|
|
529
|
+
result += `[${time}] ${evt.app} | ${evt.title.slice(0, 80)} | ${dur}s\n`;
|
|
530
|
+
}
|
|
531
|
+
if (events.length > 50) {
|
|
532
|
+
result += `\n... and ${events.length - 50} more events. Use offset parameter to paginate.`;
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
content: [{ type: 'text', text: result }],
|
|
536
|
+
};
|
|
537
|
+
},
|
|
538
|
+
async run_insight_loop(_args) {
|
|
539
|
+
try {
|
|
540
|
+
const result = await (0, scheduler_1.runInsightLoop)();
|
|
541
|
+
let text = `Insight loop completed.\n`;
|
|
542
|
+
text += `━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
543
|
+
text += `Events stored: ${result.eventsStored}\n`;
|
|
544
|
+
text += `Insights generated: ${result.insightsGenerated}\n`;
|
|
545
|
+
if (result.errors.length > 0) {
|
|
546
|
+
text += `\nErrors:\n`;
|
|
547
|
+
for (const err of result.errors) {
|
|
548
|
+
text += ` - ${err}\n`;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
if (result.insightsGenerated > 0) {
|
|
552
|
+
text += `\nUse get_insights to see the generated insights.`;
|
|
553
|
+
}
|
|
554
|
+
return {
|
|
555
|
+
content: [{ type: 'text', text }],
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
catch (err) {
|
|
559
|
+
return {
|
|
560
|
+
content: [{ type: 'text', text: `Insight loop failed: ${err instanceof Error ? err.message : String(err)}` }],
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
},
|
|
564
|
+
async push_insight(args) {
|
|
565
|
+
const config = (0, config_1.loadConfig)();
|
|
566
|
+
let targets = [];
|
|
567
|
+
if (args.targets && Array.isArray(args.targets)) {
|
|
568
|
+
targets = args.targets.map(t => String(t));
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
if (config.transports.slack.webhookUrl)
|
|
572
|
+
targets.push('slack');
|
|
573
|
+
if (config.transports.telegram.botToken && config.transports.telegram.chatId)
|
|
574
|
+
targets.push('telegram');
|
|
575
|
+
if (config.transports.webhook.url)
|
|
576
|
+
targets.push('webhook');
|
|
577
|
+
}
|
|
578
|
+
if (targets.length === 0) {
|
|
579
|
+
return {
|
|
580
|
+
content: [{ type: 'text', text: 'No transport targets configured. Use get_configuration to set up Slack, Telegram, or webhook.' }],
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
if (args.insight_id) {
|
|
584
|
+
const insight = (0, db_1.getInsights)({ includePushed: true, limit: 100 })
|
|
585
|
+
.find(i => i.id === Number(args.insight_id));
|
|
586
|
+
if (!insight) {
|
|
587
|
+
return {
|
|
588
|
+
content: [{ type: 'text', text: `Insight #${args.insight_id} not found.` }],
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
const results = await (0, transport_1.pushInsightToAll)(insight.title, insight.description, insight.action_text, targets);
|
|
592
|
+
for (const r of results) {
|
|
593
|
+
if (r.ok)
|
|
594
|
+
(0, db_1.markInsightPushed)(insight.id, r.target);
|
|
595
|
+
}
|
|
596
|
+
let text = `Push results:\n`;
|
|
597
|
+
for (const r of results) {
|
|
598
|
+
text += ` ${r.target}: ${r.ok ? 'sent' : 'failed — ' + r.error}\n`;
|
|
599
|
+
}
|
|
600
|
+
return { content: [{ type: 'text', text }] };
|
|
601
|
+
}
|
|
602
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
603
|
+
const summary = (0, db_1.getDailySummary)(today);
|
|
604
|
+
if (!summary) {
|
|
605
|
+
return {
|
|
606
|
+
content: [{ type: 'text', text: 'No daily summary available. Run insight_loop first.' }],
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
const results = await (0, transport_1.pushSummary)(summary.raw_summary, targets);
|
|
610
|
+
let text = `Push results:\n`;
|
|
611
|
+
for (const r of results) {
|
|
612
|
+
text += ` ${r.target}: ${r.ok ? 'sent' : 'failed — ' + r.error}\n`;
|
|
613
|
+
}
|
|
614
|
+
return { content: [{ type: 'text', text }] };
|
|
615
|
+
},
|
|
616
|
+
async health_check(_args) {
|
|
617
|
+
const config = (0, config_1.loadConfig)();
|
|
618
|
+
const db = (0, db_1.getDb)();
|
|
619
|
+
let text = `OpenContext Health Check\n`;
|
|
620
|
+
text += `━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
621
|
+
text += `Version: ${SERVER_VERSION}\n`;
|
|
622
|
+
text += `Config: ${(0, config_1.getConfigPath)()}\n`;
|
|
623
|
+
text += `Data dir: ${(0, config_1.getDataDir)()}\n`;
|
|
624
|
+
text += `Database: connected (WAL mode)\n\n`;
|
|
625
|
+
text += `ActivityWatch:\n`;
|
|
626
|
+
if (config.activitywatch.enabled) {
|
|
627
|
+
const aw = (0, aw_1.createAWClient)();
|
|
628
|
+
const health = await aw.healthCheck();
|
|
629
|
+
text += ` Status: ${health.ok ? 'connected' : 'unreachable'}\n`;
|
|
630
|
+
if (health.version)
|
|
631
|
+
text += ` Version: ${health.version}\n`;
|
|
632
|
+
if (health.error)
|
|
633
|
+
text += ` Error: ${health.error}\n`;
|
|
634
|
+
if (health.ok) {
|
|
635
|
+
const bucketsResult = await aw.getBuckets();
|
|
636
|
+
if (bucketsResult.ok) {
|
|
637
|
+
const bucketIds = Object.keys(bucketsResult.buckets);
|
|
638
|
+
text += ` Buckets found: ${bucketIds.length}\n`;
|
|
639
|
+
for (const id of bucketIds.slice(0, 10)) {
|
|
640
|
+
text += ` - ${id} (${bucketsResult.buckets[id]})\n`;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
text += ` Buckets: error — ${bucketsResult.error}\n`;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
const watcherStatus = (0, watcher_1.getWatcherStatus)();
|
|
649
|
+
if (watcherStatus.running) {
|
|
650
|
+
text += ` Fallback: native watcher active (${watcherStatus.backend}, permissions: ${watcherStatus.permissions})\n`;
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
text += ` Fallback: native watcher not running. Install ActivityWatch or run: pip3 install pyobjc-framework-Quartz\n`;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
else {
|
|
658
|
+
text += ` Disabled in config\n`;
|
|
659
|
+
}
|
|
660
|
+
text += `\nScreenpipe:\n`;
|
|
661
|
+
if (config.screenpipe.enabled) {
|
|
662
|
+
const sp = (0, screenpipe_1.createScreenpipeClient)();
|
|
663
|
+
const health = await sp.healthCheck();
|
|
664
|
+
text += ` Status: ${health.ok ? 'connected' : 'unreachable'}\n`;
|
|
665
|
+
if (health.error)
|
|
666
|
+
text += ` Error: ${health.error}\n`;
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
text += ` Disabled in config\n`;
|
|
670
|
+
}
|
|
671
|
+
text += `\nTransports:\n`;
|
|
672
|
+
text += ` Slack: ${config.transports.slack.webhookUrl ? 'configured' : 'not configured'}\n`;
|
|
673
|
+
text += ` Telegram: ${config.transports.telegram.botToken && config.transports.telegram.chatId ? 'configured' : 'not configured'}\n`;
|
|
674
|
+
text += ` Webhook: ${config.transports.webhook.url ? 'configured' : 'not configured'}\n`;
|
|
675
|
+
text += `\nData:\n`;
|
|
676
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
677
|
+
const summary = (0, db_1.getDailySummary)(today);
|
|
678
|
+
if (summary) {
|
|
679
|
+
text += ` Today's active time: ${Math.round(summary.total_active_time / 6) / 10} minutes\n`;
|
|
680
|
+
text += ` Today's insights: ${summary.insight_count}\n`;
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
text += ` No data for today yet\n`;
|
|
684
|
+
}
|
|
685
|
+
const totalEvents = db.prepare('SELECT COUNT(*) as count FROM raw_events').get().count;
|
|
686
|
+
const totalInsights = db.prepare('SELECT COUNT(*) as count FROM insights').get().count;
|
|
687
|
+
text += ` Total events stored: ${totalEvents}\n`;
|
|
688
|
+
text += ` Total insights: ${totalInsights}\n`;
|
|
689
|
+
return {
|
|
690
|
+
content: [{ type: 'text', text }],
|
|
691
|
+
};
|
|
692
|
+
},
|
|
693
|
+
async get_configuration(args) {
|
|
694
|
+
const config = (0, config_1.loadConfig)();
|
|
695
|
+
const section = args.section ? String(args.section) : 'all';
|
|
696
|
+
let text = `OpenContext Configuration\n`;
|
|
697
|
+
text += `━━━━━━━━━━━━━━━━━━━━━━\n`;
|
|
698
|
+
text += `Config file: ${(0, config_1.getConfigPath)()}\n\n`;
|
|
699
|
+
const printSection = (name, data) => {
|
|
700
|
+
text += `${name}:\n`;
|
|
701
|
+
for (const [key, value] of Object.entries(data)) {
|
|
702
|
+
if (typeof value === 'object' && value !== null) {
|
|
703
|
+
text += ` ${key}:\n`;
|
|
704
|
+
for (const [k, v] of Object.entries(value)) {
|
|
705
|
+
const displayVal = typeof v === 'string' && v.length > 0 ? (v.length > 40 ? v.slice(0, 37) + '...' : v) : v;
|
|
706
|
+
text += ` ${k}: ${displayVal}\n`;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
text += ` ${key}: ${value}\n`;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
text += `\n`;
|
|
714
|
+
};
|
|
715
|
+
if (section === 'all' || section === 'activitywatch')
|
|
716
|
+
printSection('ActivityWatch', config.activitywatch);
|
|
717
|
+
if (section === 'all' || section === 'screenpipe')
|
|
718
|
+
printSection('Screenpipe', config.screenpipe);
|
|
719
|
+
if (section === 'all' || section === 'insights')
|
|
720
|
+
printSection('Insights', config.insights);
|
|
721
|
+
if (section === 'all' || section === 'transports') {
|
|
722
|
+
text += `Transports:\n`;
|
|
723
|
+
text += ` slack.webhookUrl: ${config.transports.slack.webhookUrl ? '***configured***' : 'not set'}\n`;
|
|
724
|
+
text += ` telegram.botToken: ${config.transports.telegram.botToken ? '***configured***' : 'not set'}\n`;
|
|
725
|
+
text += ` telegram.chatId: ${config.transports.telegram.chatId || 'not set'}\n`;
|
|
726
|
+
text += ` webhook.url: ${config.transports.webhook.url || 'not set'}\n\n`;
|
|
727
|
+
}
|
|
728
|
+
text += `To change configuration, edit: ${(0, config_1.getConfigPath)()}`;
|
|
729
|
+
return {
|
|
730
|
+
content: [{ type: 'text', text }],
|
|
731
|
+
};
|
|
732
|
+
},
|
|
733
|
+
async create_linear_task(args) {
|
|
734
|
+
const title = String(args.title || '');
|
|
735
|
+
if (!title)
|
|
736
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'title is required');
|
|
737
|
+
const config = (0, config_1.loadConfig)();
|
|
738
|
+
const apiKey = process.env.LINEAR_API_KEY || '';
|
|
739
|
+
if (!apiKey) {
|
|
740
|
+
return { content: [{ type: 'text', text: 'Linear API key not set. Export LINEA_API_KEY or add it to ~/.daybrain/config.json under "integrations.linear.apiKey".' }] };
|
|
741
|
+
}
|
|
742
|
+
try {
|
|
743
|
+
const res = await fetch('https://api.linear.app/graphql', {
|
|
744
|
+
method: 'POST',
|
|
745
|
+
headers: { 'Content-Type': 'application/json', 'Authorization': apiKey },
|
|
746
|
+
body: JSON.stringify({
|
|
747
|
+
query: `mutation CreateIssue($title: String!, $description: String, $teamId: String) {
|
|
748
|
+
issueCreate(input: { title: $title, description: $description, teamId: $teamId }) {
|
|
749
|
+
success
|
|
750
|
+
issue { id title url }
|
|
751
|
+
}
|
|
752
|
+
}`,
|
|
753
|
+
variables: {
|
|
754
|
+
title,
|
|
755
|
+
description: String(args.description || 'Created by DayBrain from detected commitment.'),
|
|
756
|
+
teamId: args.team_id || undefined,
|
|
757
|
+
},
|
|
758
|
+
}),
|
|
759
|
+
});
|
|
760
|
+
const json = await res.json();
|
|
761
|
+
const issue = json?.data?.issueCreate?.issue;
|
|
762
|
+
if (issue) {
|
|
763
|
+
return { content: [{ type: 'text', text: `Created Linear issue: ${issue.title}\n${issue.url}` }] };
|
|
764
|
+
}
|
|
765
|
+
return { content: [{ type: 'text', text: `Linear API error: ${JSON.stringify(json.errors || json)}` }] };
|
|
766
|
+
}
|
|
767
|
+
catch (err) {
|
|
768
|
+
return { content: [{ type: 'text', text: `Linear API call failed: ${err instanceof Error ? err.message : String(err)}` }] };
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
async create_github_issue(args) {
|
|
772
|
+
const title = String(args.title || '');
|
|
773
|
+
if (!title)
|
|
774
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, 'title is required');
|
|
775
|
+
const config = (0, config_1.loadConfig)();
|
|
776
|
+
const token = process.env.GITHUB_TOKEN || '';
|
|
777
|
+
const repo = String(args.repo || config.transports.webhook.headers?.['x-github-repo'] || '');
|
|
778
|
+
if (!token) {
|
|
779
|
+
return { content: [{ type: 'text', text: 'GitHub token not set. Export GITHUB_TOKEN or create a personal access token at https://github.com/settings/tokens.' }] };
|
|
780
|
+
}
|
|
781
|
+
if (!repo) {
|
|
782
|
+
return { content: [{ type: 'text', text: 'Repository not specified. Pass "repo" argument or set default in config.' }] };
|
|
783
|
+
}
|
|
784
|
+
try {
|
|
785
|
+
const res = await fetch(`https://api.github.com/repos/${repo}/issues`, {
|
|
786
|
+
method: 'POST',
|
|
787
|
+
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', 'Accept': 'application/vnd.github+json' },
|
|
788
|
+
body: JSON.stringify({
|
|
789
|
+
title,
|
|
790
|
+
body: String(args.body || 'Created by DayBrain from detected commitment.'),
|
|
791
|
+
labels: Array.isArray(args.labels) ? args.labels : [],
|
|
792
|
+
}),
|
|
793
|
+
});
|
|
794
|
+
const json = await res.json();
|
|
795
|
+
if (json.html_url) {
|
|
796
|
+
return { content: [{ type: 'text', text: `Created GitHub issue: ${json.title}\n${json.html_url}` }] };
|
|
797
|
+
}
|
|
798
|
+
return { content: [{ type: 'text', text: `GitHub API error: ${JSON.stringify(json)}` }] };
|
|
799
|
+
}
|
|
800
|
+
catch (err) {
|
|
801
|
+
return { content: [{ type: 'text', text: `GitHub API call failed: ${err instanceof Error ? err.message : String(err)}` }] };
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
};
|
|
805
|
+
exports.toolHandlers = toolHandlers;
|
|
806
|
+
function insightEmoji(type) {
|
|
807
|
+
switch (type) {
|
|
808
|
+
case 'commitment': return '🤝';
|
|
809
|
+
case 'avoidance': return '👀';
|
|
810
|
+
case 'context_switch': return '🔄';
|
|
811
|
+
case 'focus': return '🎯';
|
|
812
|
+
case 'deadline': return '⏰';
|
|
813
|
+
case 'pattern': return '📊';
|
|
814
|
+
case 'summary': return '📋';
|
|
815
|
+
default: return '💡';
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
async function main() {
|
|
819
|
+
const config = (0, config_1.loadConfig)();
|
|
820
|
+
const server = new index_js_1.Server({ name: SERVER_NAME, version: SERVER_VERSION }, { capabilities: { tools: {} } });
|
|
821
|
+
const tools = buildTools();
|
|
822
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({ tools }));
|
|
823
|
+
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
824
|
+
const { name, arguments: args } = request.params;
|
|
825
|
+
const handler = toolHandlers[name];
|
|
826
|
+
if (!handler) {
|
|
827
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
828
|
+
}
|
|
829
|
+
try {
|
|
830
|
+
return await handler(args ?? {});
|
|
831
|
+
}
|
|
832
|
+
catch (err) {
|
|
833
|
+
if (err instanceof types_js_1.McpError)
|
|
834
|
+
throw err;
|
|
835
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
836
|
+
console.error(`[daybrain] Tool error (${name}):`, message);
|
|
837
|
+
return {
|
|
838
|
+
content: [{ type: 'text', text: `Error: ${message}` }],
|
|
839
|
+
isError: true,
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
const transport = new stdio_js_1.StdioServerTransport();
|
|
844
|
+
server.onerror = (err) => {
|
|
845
|
+
console.error('[daybrain] Server error:', err instanceof Error ? err.message : String(err));
|
|
846
|
+
};
|
|
847
|
+
process.on('SIGINT', () => {
|
|
848
|
+
(0, scheduler_1.stopScheduler)();
|
|
849
|
+
(0, watcher_1.stopNativeWatcher)();
|
|
850
|
+
(0, http_1.stopHttpServer)();
|
|
851
|
+
(0, db_1.closeDb)();
|
|
852
|
+
process.exit(0);
|
|
853
|
+
});
|
|
854
|
+
process.on('SIGTERM', () => {
|
|
855
|
+
(0, scheduler_1.stopScheduler)();
|
|
856
|
+
(0, watcher_1.stopNativeWatcher)();
|
|
857
|
+
(0, http_1.stopHttpServer)();
|
|
858
|
+
(0, db_1.closeDb)();
|
|
859
|
+
process.exit(0);
|
|
860
|
+
});
|
|
861
|
+
process.on('uncaughtException', (err) => {
|
|
862
|
+
console.error('[daybrain] Uncaught exception:', err instanceof Error ? err.message : String(err));
|
|
863
|
+
(0, scheduler_1.stopScheduler)();
|
|
864
|
+
(0, watcher_1.stopNativeWatcher)();
|
|
865
|
+
(0, http_1.stopHttpServer)();
|
|
866
|
+
try {
|
|
867
|
+
(0, db_1.closeDb)();
|
|
868
|
+
}
|
|
869
|
+
catch { }
|
|
870
|
+
process.exit(1);
|
|
871
|
+
});
|
|
872
|
+
process.on('unhandledRejection', (reason) => {
|
|
873
|
+
console.error('[daybrain] Unhandled rejection:', reason instanceof Error ? reason.message : String(reason));
|
|
874
|
+
});
|
|
875
|
+
await server.connect(transport);
|
|
876
|
+
console.error(`[daybrain] v${SERVER_VERSION} started`);
|
|
877
|
+
console.error(`[daybrain] Data dir: ${(0, config_1.getDataDir)()}`);
|
|
878
|
+
console.error(`[daybrain] Config: ${(0, config_1.getConfigPath)()}`);
|
|
879
|
+
console.error(`[daybrain] ${tools.length} tools registered`);
|
|
880
|
+
if (config.activitywatch.enabled) {
|
|
881
|
+
const aw = (0, aw_1.createAWClient)();
|
|
882
|
+
const awHealth = await aw.healthCheck();
|
|
883
|
+
if (awHealth.ok) {
|
|
884
|
+
console.error(`[daybrain] ActivityWatch connected (v${awHealth.version})`);
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
console.error(`[daybrain] ActivityWatch not found, starting native watcher...`);
|
|
888
|
+
const watcherResult = await (0, watcher_1.startNativeWatcher)();
|
|
889
|
+
if (watcherResult.ok) {
|
|
890
|
+
console.error(`[daybrain] Native watcher active (${watcherResult.backend})`);
|
|
891
|
+
}
|
|
892
|
+
else {
|
|
893
|
+
console.error(`[daybrain] No activity source available: ${watcherResult.error}`);
|
|
894
|
+
console.error(`[daybrain] Install ActivityWatch (https://activitywatch.net) or run: pip3 install pyobjc-framework-Quartz`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
(0, scheduler_1.startScheduler)();
|
|
898
|
+
}
|
|
899
|
+
(0, http_1.startHttpServer)();
|
|
900
|
+
console.error(`[daybrain] Browser extension API on http://127.0.0.1:19840`);
|
|
901
|
+
}
|
|
902
|
+
if (require.main === module) {
|
|
903
|
+
main().catch((err) => {
|
|
904
|
+
console.error('[daybrain] Fatal error:', err instanceof Error ? err.message : String(err));
|
|
905
|
+
process.exit(1);
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
//# sourceMappingURL=index.js.map
|