@siftd/connect-agent 0.1.0 → 0.2.2

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.
@@ -0,0 +1,311 @@
1
+ /**
2
+ * Task Scheduler
3
+ * Schedule tasks for future execution with notifications
4
+ */
5
+ import * as cron from 'node-cron';
6
+ import * as fs from 'fs';
7
+ export class TaskScheduler {
8
+ tasks = new Map();
9
+ activeTimers = new Map();
10
+ notifyCallback;
11
+ taskExecutor;
12
+ dataPath;
13
+ constructor(dataPath = 'scheduled_tasks.json') {
14
+ this.dataPath = dataPath;
15
+ this.loadTasks();
16
+ }
17
+ /**
18
+ * Set the notification callback for sending messages
19
+ */
20
+ setNotifyCallback(callback) {
21
+ this.notifyCallback = callback;
22
+ }
23
+ /**
24
+ * Set the task executor callback
25
+ */
26
+ setTaskExecutor(executor) {
27
+ this.taskExecutor = executor;
28
+ }
29
+ /**
30
+ * Parse natural language schedule
31
+ */
32
+ parseSchedule(when) {
33
+ const lower = when.toLowerCase().trim();
34
+ // "in X minutes/hours/days"
35
+ const delayMatch = lower.match(/^in\s+(\d+)\s+(minute|hour|day|second)s?$/);
36
+ if (delayMatch) {
37
+ const amount = parseInt(delayMatch[1]);
38
+ const unit = delayMatch[2];
39
+ const multipliers = {
40
+ second: 1000,
41
+ minute: 60000,
42
+ hour: 3600000,
43
+ day: 86400000
44
+ };
45
+ return { type: 'delay', ms: amount * multipliers[unit] };
46
+ }
47
+ // "every X minutes/hours"
48
+ const intervalMatch = lower.match(/^every\s+(\d+)\s+(minute|hour|second)s?$/);
49
+ if (intervalMatch) {
50
+ const amount = parseInt(intervalMatch[1]);
51
+ const unit = intervalMatch[2];
52
+ const multipliers = {
53
+ second: 1000,
54
+ minute: 60000,
55
+ hour: 3600000
56
+ };
57
+ return { type: 'interval', ms: amount * multipliers[unit] };
58
+ }
59
+ // "daily at HH:MM"
60
+ const dailyMatch = lower.match(/^daily\s+at\s+(\d{1,2}):(\d{2})$/);
61
+ if (dailyMatch) {
62
+ const hour = parseInt(dailyMatch[1]);
63
+ const minute = parseInt(dailyMatch[2]);
64
+ return { type: 'cron', pattern: `${minute} ${hour} * * *` };
65
+ }
66
+ // "every day at HH:MM"
67
+ const everyDayMatch = lower.match(/^every\s+day\s+at\s+(\d{1,2}):(\d{2})$/);
68
+ if (everyDayMatch) {
69
+ const hour = parseInt(everyDayMatch[1]);
70
+ const minute = parseInt(everyDayMatch[2]);
71
+ return { type: 'cron', pattern: `${minute} ${hour} * * *` };
72
+ }
73
+ // "tomorrow at HH:MM"
74
+ const tomorrowMatch = lower.match(/^tomorrow\s+at\s+(\d{1,2}):(\d{2})$/);
75
+ if (tomorrowMatch) {
76
+ const hour = parseInt(tomorrowMatch[1]);
77
+ const minute = parseInt(tomorrowMatch[2]);
78
+ const tomorrow = new Date();
79
+ tomorrow.setDate(tomorrow.getDate() + 1);
80
+ tomorrow.setHours(hour, minute, 0, 0);
81
+ return { type: 'once', at: tomorrow.toISOString() };
82
+ }
83
+ // Raw cron pattern (5 parts)
84
+ if (/^\S+\s+\S+\s+\S+\s+\S+\s+\S+$/.test(lower)) {
85
+ if (cron.validate(lower)) {
86
+ return { type: 'cron', pattern: lower };
87
+ }
88
+ }
89
+ return null;
90
+ }
91
+ /**
92
+ * Schedule a task
93
+ */
94
+ schedule(task, when, chatId) {
95
+ const schedule = typeof when === 'string' ? this.parseSchedule(when) : when;
96
+ if (!schedule) {
97
+ throw new Error(`Could not parse schedule: "${when}". Try formats like "in 30 minutes", "daily at 9:00", or "every 2 hours".`);
98
+ }
99
+ const id = `task_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`;
100
+ const now = new Date();
101
+ const scheduledTask = {
102
+ id,
103
+ task,
104
+ schedule,
105
+ chatId,
106
+ enabled: true,
107
+ created: now.toISOString(),
108
+ runCount: 0,
109
+ nextRun: this.calculateNextRun(schedule)
110
+ };
111
+ this.tasks.set(id, scheduledTask);
112
+ this.activateTask(scheduledTask);
113
+ this.saveTasks();
114
+ return id;
115
+ }
116
+ /**
117
+ * Calculate next run time
118
+ */
119
+ calculateNextRun(schedule) {
120
+ const now = new Date();
121
+ switch (schedule.type) {
122
+ case 'once':
123
+ return schedule.at;
124
+ case 'delay':
125
+ return new Date(now.getTime() + schedule.ms).toISOString();
126
+ case 'interval':
127
+ return new Date(now.getTime() + schedule.ms).toISOString();
128
+ case 'cron':
129
+ return undefined;
130
+ }
131
+ }
132
+ /**
133
+ * Activate a task's timer
134
+ */
135
+ activateTask(task) {
136
+ this.deactivateTask(task.id);
137
+ if (!task.enabled)
138
+ return;
139
+ const executeAndNotify = async () => {
140
+ task.lastRun = new Date().toISOString();
141
+ task.runCount++;
142
+ let result = `Scheduled task "${task.task}" executed.`;
143
+ if (this.taskExecutor) {
144
+ try {
145
+ result = await this.taskExecutor(task.task, task.chatId);
146
+ }
147
+ catch (error) {
148
+ result = `Task failed: ${error instanceof Error ? error.message : String(error)}`;
149
+ }
150
+ }
151
+ if (this.notifyCallback) {
152
+ try {
153
+ await this.notifyCallback(task.chatId, `Scheduled task completed:\n\n${result}`);
154
+ }
155
+ catch (error) {
156
+ console.error('Failed to send notification:', error);
157
+ }
158
+ }
159
+ // For one-time tasks, disable after execution
160
+ if (task.schedule.type === 'once' || task.schedule.type === 'delay') {
161
+ task.enabled = false;
162
+ this.deactivateTask(task.id);
163
+ }
164
+ else {
165
+ task.nextRun = this.calculateNextRun(task.schedule);
166
+ }
167
+ this.saveTasks();
168
+ };
169
+ switch (task.schedule.type) {
170
+ case 'once': {
171
+ const runAt = new Date(task.schedule.at).getTime();
172
+ const delay = Math.max(0, runAt - Date.now());
173
+ const handle = setTimeout(executeAndNotify, delay);
174
+ this.activeTimers.set(task.id, { type: 'timeout', handle });
175
+ break;
176
+ }
177
+ case 'delay': {
178
+ const handle = setTimeout(executeAndNotify, task.schedule.ms);
179
+ this.activeTimers.set(task.id, { type: 'timeout', handle });
180
+ break;
181
+ }
182
+ case 'interval': {
183
+ const handle = setInterval(executeAndNotify, task.schedule.ms);
184
+ this.activeTimers.set(task.id, { type: 'interval', handle });
185
+ break;
186
+ }
187
+ case 'cron': {
188
+ const handle = cron.schedule(task.schedule.pattern, executeAndNotify);
189
+ this.activeTimers.set(task.id, { type: 'cron', handle });
190
+ break;
191
+ }
192
+ }
193
+ }
194
+ /**
195
+ * Deactivate a task's timer
196
+ */
197
+ deactivateTask(taskId) {
198
+ const timer = this.activeTimers.get(taskId);
199
+ if (!timer)
200
+ return;
201
+ switch (timer.type) {
202
+ case 'timeout':
203
+ case 'interval':
204
+ clearTimeout(timer.handle);
205
+ break;
206
+ case 'cron':
207
+ timer.handle.stop();
208
+ break;
209
+ }
210
+ this.activeTimers.delete(taskId);
211
+ }
212
+ /**
213
+ * Cancel a task
214
+ */
215
+ cancel(taskId) {
216
+ const task = this.tasks.get(taskId);
217
+ if (!task)
218
+ return false;
219
+ this.deactivateTask(taskId);
220
+ this.tasks.delete(taskId);
221
+ this.saveTasks();
222
+ return true;
223
+ }
224
+ /**
225
+ * Pause a task
226
+ */
227
+ pause(taskId) {
228
+ const task = this.tasks.get(taskId);
229
+ if (!task)
230
+ return false;
231
+ task.enabled = false;
232
+ this.deactivateTask(taskId);
233
+ this.saveTasks();
234
+ return true;
235
+ }
236
+ /**
237
+ * Resume a task
238
+ */
239
+ resume(taskId) {
240
+ const task = this.tasks.get(taskId);
241
+ if (!task)
242
+ return false;
243
+ task.enabled = true;
244
+ this.activateTask(task);
245
+ this.saveTasks();
246
+ return true;
247
+ }
248
+ /**
249
+ * List all tasks
250
+ */
251
+ list() {
252
+ return Array.from(this.tasks.values()).sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime());
253
+ }
254
+ /**
255
+ * Get a specific task
256
+ */
257
+ get(taskId) {
258
+ return this.tasks.get(taskId);
259
+ }
260
+ /**
261
+ * Format schedule for display
262
+ */
263
+ formatSchedule(schedule) {
264
+ switch (schedule.type) {
265
+ case 'once':
266
+ return `once at ${new Date(schedule.at).toLocaleString()}`;
267
+ case 'delay':
268
+ const minutes = Math.round(schedule.ms / 60000);
269
+ return `in ${minutes} minute${minutes !== 1 ? 's' : ''}`;
270
+ case 'interval':
271
+ const intervalMins = Math.round(schedule.ms / 60000);
272
+ return `every ${intervalMins} minute${intervalMins !== 1 ? 's' : ''}`;
273
+ case 'cron':
274
+ return `cron: ${schedule.pattern}`;
275
+ }
276
+ }
277
+ /**
278
+ * Save tasks to disk
279
+ */
280
+ saveTasks() {
281
+ const data = Array.from(this.tasks.values());
282
+ fs.writeFileSync(this.dataPath, JSON.stringify(data, null, 2));
283
+ }
284
+ /**
285
+ * Load tasks from disk
286
+ */
287
+ loadTasks() {
288
+ if (!fs.existsSync(this.dataPath))
289
+ return;
290
+ try {
291
+ const data = JSON.parse(fs.readFileSync(this.dataPath, 'utf8'));
292
+ for (const task of data) {
293
+ this.tasks.set(task.id, task);
294
+ if (task.enabled) {
295
+ this.activateTask(task);
296
+ }
297
+ }
298
+ }
299
+ catch (error) {
300
+ console.error('Failed to load scheduled tasks:', error);
301
+ }
302
+ }
303
+ /**
304
+ * Stop all timers (for shutdown)
305
+ */
306
+ shutdown() {
307
+ for (const taskId of this.activeTimers.keys()) {
308
+ this.deactivateTask(taskId);
309
+ }
310
+ }
311
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * System Indexer
3
+ *
4
+ * Indexes the user's filesystem INTO the memory system.
5
+ * When user says "that project" or "save in downloads",
6
+ * semantic search finds the relevant paths.
7
+ *
8
+ * No MCP. No new protocols. Just smart memory use.
9
+ */
10
+ import { AdvancedMemoryStore } from './memory-advanced.js';
11
+ export declare class SystemIndexer {
12
+ private memory;
13
+ private home;
14
+ private indexed;
15
+ constructor(memory: AdvancedMemoryStore);
16
+ /**
17
+ * Index the user's home directory structure into memory
18
+ */
19
+ indexHome(): Promise<{
20
+ projects: number;
21
+ directories: number;
22
+ total: number;
23
+ }>;
24
+ /**
25
+ * Index a directory and its subdirectories
26
+ */
27
+ private indexDirectory;
28
+ /**
29
+ * Find and index git repositories as projects
30
+ */
31
+ private indexGitRepos;
32
+ /**
33
+ * Get detailed info about a git project
34
+ */
35
+ private getProjectInfo;
36
+ /**
37
+ * Detect project type by looking at files
38
+ */
39
+ private detectProjectType;
40
+ /**
41
+ * Remember a key directory (Downloads, Documents, Desktop, etc.)
42
+ * These get high importance and multiple aliases for better search
43
+ */
44
+ private rememberKeyDirectory;
45
+ /**
46
+ * Store a project in memory
47
+ */
48
+ private rememberProject;
49
+ /**
50
+ * Generate a description for a directory
51
+ */
52
+ private describeDirectory;
53
+ /**
54
+ * Extract relevant tags from directory contents
55
+ */
56
+ private extractTags;
57
+ /**
58
+ * Re-index (clear old filesystem memories and re-scan)
59
+ */
60
+ reindex(): Promise<{
61
+ projects: number;
62
+ directories: number;
63
+ total: number;
64
+ }>;
65
+ }