@vibetasks/core 0.2.0 → 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/dist/index.d.ts CHANGED
@@ -125,12 +125,13 @@ interface Tag {
125
125
  created_at?: string;
126
126
  }
127
127
  /**
128
- * Task status - simple 3-state flow for VibeTasks
128
+ * Task status - simple 3-state flow + archive for VibeTasks
129
129
  * - todo: Not started yet, waiting in backlog
130
130
  * - vibing: Currently in progress (actively working on it)
131
131
  * - done: Completed
132
+ * - archived: Archived (hidden from default views)
132
133
  */
133
- type TaskStatus = 'todo' | 'vibing' | 'done';
134
+ type TaskStatus = 'todo' | 'vibing' | 'done' | 'archived';
134
135
  interface Task {
135
136
  id: string;
136
137
  user_id?: string;
@@ -139,6 +140,7 @@ interface Task {
139
140
  notes_format?: 'html' | 'markdown';
140
141
  completed?: boolean;
141
142
  completed_at?: string;
143
+ archived_at?: string;
142
144
  due_date?: string;
143
145
  start_date?: string;
144
146
  duration_minutes?: number;
@@ -170,22 +172,140 @@ declare class TaskOperations {
170
172
  * Create TaskOperations from AuthManager
171
173
  */
172
174
  static fromAuthManager(authManager: AuthManager): Promise<TaskOperations>;
173
- getTasks(filter?: TaskFilter): Promise<Task[]>;
175
+ getTasks(filter?: TaskFilter, includeArchived?: boolean): Promise<Task[]>;
174
176
  getTask(taskId: string): Promise<Task>;
175
177
  createTask(task: Partial<Task>): Promise<Task>;
176
178
  updateTask(taskId: string, updates: Partial<Task>): Promise<Task>;
177
179
  deleteTask(taskId: string): Promise<void>;
178
180
  completeTask(taskId: string): Promise<Task>;
179
181
  uncompleteTask(taskId: string): Promise<Task>;
182
+ /**
183
+ * Archive a task - moves it to archived status
184
+ * Typically used for completed tasks you want to hide from views
185
+ */
186
+ archiveTask(taskId: string): Promise<Task>;
187
+ /**
188
+ * Unarchive a task - restores it to its previous state (done by default)
189
+ */
190
+ unarchiveTask(taskId: string, restoreStatus?: TaskStatus): Promise<Task>;
191
+ /**
192
+ * Get all archived tasks
193
+ */
194
+ getArchivedTasks(limit?: number): Promise<Task[]>;
180
195
  getTags(): Promise<Tag[]>;
181
196
  createTag(name: string, color?: string): Promise<Tag>;
182
197
  linkTaskTags(taskId: string, tagIds: string[]): Promise<void>;
183
198
  unlinkAllTaskTags(taskId: string): Promise<void>;
184
- searchTasks(query: string, limit?: number): Promise<Task[]>;
199
+ searchTasks(query: string, limit?: number, includeArchived?: boolean): Promise<Task[]>;
185
200
  /**
186
201
  * Find tag by name (case-insensitive), create if doesn't exist
187
202
  */
188
203
  findOrCreateTag(name: string, color?: string): Promise<Tag>;
189
204
  }
190
205
 
191
- export { AuthManager, ConfigManager, type SupabaseConfig, type Tag, type Task, type TaskFilter, type TaskFlowConfig, TaskOperations, type TaskPriority, type TaskStatus, createSupabaseClient, createSupabaseClientFromEnv };
206
+ /**
207
+ * Claude Code TodoWrite Sync
208
+ *
209
+ * This module provides synchronization between Claude's TodoWrite state
210
+ * and VibeTasks task statuses.
211
+ *
212
+ * Status mapping:
213
+ * Claude "pending" -> VibeTasks "todo"
214
+ * Claude "in_progress" -> VibeTasks "vibing"
215
+ * Claude "completed" -> VibeTasks "done"
216
+ */
217
+
218
+ /**
219
+ * Claude Code TodoWrite item structure
220
+ */
221
+ interface ClaudeTodoItem {
222
+ /** The task description/content */
223
+ content: string;
224
+ /** Current status: pending, in_progress, or completed */
225
+ status: 'pending' | 'in_progress' | 'completed';
226
+ /** Active form description (used during in_progress) */
227
+ activeForm?: string;
228
+ }
229
+ /**
230
+ * Result of syncing a single todo item
231
+ */
232
+ interface SyncResult {
233
+ /** The Claude todo content */
234
+ content: string;
235
+ /** Whether a matching VibeTasks task was found */
236
+ matched: boolean;
237
+ /** The VibeTasks task ID if matched */
238
+ taskId?: string;
239
+ /** The action taken */
240
+ action: 'created' | 'updated' | 'skipped' | 'not_found';
241
+ /** Previous status (if updated) */
242
+ previousStatus?: TaskStatus;
243
+ /** New status (if updated) */
244
+ newStatus?: TaskStatus;
245
+ /** Error message if any */
246
+ error?: string;
247
+ }
248
+ /**
249
+ * Result of syncing all todos
250
+ */
251
+ interface SyncAllResult {
252
+ /** Whether the overall sync was successful */
253
+ success: boolean;
254
+ /** Number of todos processed */
255
+ processed: number;
256
+ /** Number of todos synced successfully */
257
+ synced: number;
258
+ /** Number of todos that failed to sync */
259
+ failed: number;
260
+ /** Individual sync results */
261
+ results: SyncResult[];
262
+ }
263
+ /**
264
+ * Options for the sync operation
265
+ */
266
+ interface SyncOptions {
267
+ /** Create new tasks for unmatched todos (default: false) */
268
+ createMissing?: boolean;
269
+ /** Project tag to use for created tasks */
270
+ projectTag?: string;
271
+ /** Minimum similarity score for matching (0-1, default: 0.6) */
272
+ matchThreshold?: number;
273
+ /** Whether to sync 'pending' todos as 'todo' status (default: true) */
274
+ syncPending?: boolean;
275
+ }
276
+ /**
277
+ * Claude Sync class for managing TodoWrite synchronization
278
+ */
279
+ declare class ClaudeSync {
280
+ private taskOps;
281
+ constructor(taskOps: TaskOperations);
282
+ /**
283
+ * Sync a single Claude todo item with VibeTasks
284
+ */
285
+ syncTodo(todo: ClaudeTodoItem, existingTasks?: Task[], options?: SyncOptions): Promise<SyncResult>;
286
+ /**
287
+ * Sync all Claude todos with VibeTasks
288
+ */
289
+ syncAllTodos(todos: ClaudeTodoItem[], options?: SyncOptions): Promise<SyncAllResult>;
290
+ /**
291
+ * Get current vibing tasks (for comparison with Claude's in_progress)
292
+ */
293
+ getVibingTasks(): Promise<Task[]>;
294
+ /**
295
+ * Auto-start vibing on a task when Claude marks it as in_progress
296
+ */
297
+ startVibing(taskId: string): Promise<Task>;
298
+ /**
299
+ * Complete a task when Claude marks it as completed
300
+ */
301
+ completeTask(taskId: string): Promise<Task>;
302
+ }
303
+ /**
304
+ * MCP Tool: sync_claude_todos
305
+ *
306
+ * This function is designed to be called as an MCP tool.
307
+ * It accepts Claude's current todo list and syncs it with VibeTasks.
308
+ */
309
+ declare function syncClaudeTodos(taskOps: TaskOperations, todos: ClaudeTodoItem[], options?: SyncOptions): Promise<SyncAllResult>;
310
+
311
+ export { AuthManager, ClaudeSync, type ClaudeTodoItem, ConfigManager, type SupabaseConfig, type SyncAllResult, type SyncOptions, type SyncResult, type Tag, type Task, type TaskFilter, type TaskFlowConfig, TaskOperations, type TaskPriority, type TaskStatus, createSupabaseClient, createSupabaseClientFromEnv, syncClaudeTodos };
package/dist/index.js CHANGED
@@ -287,11 +287,14 @@ var TaskOperations = class _TaskOperations {
287
287
  // ============================================
288
288
  // TASK CRUD OPERATIONS
289
289
  // ============================================
290
- async getTasks(filter = "all") {
290
+ async getTasks(filter = "all", includeArchived = false) {
291
291
  let query = this.supabase.from("tasks").select(`
292
292
  *,
293
293
  tags:task_tags(tag:tags(*))
294
294
  `).is("parent_task_id", null).order("position", { ascending: true });
295
+ if (!includeArchived) {
296
+ query = query.neq("status", "archived");
297
+ }
295
298
  if (filter === "today") {
296
299
  const today = /* @__PURE__ */ new Date();
297
300
  today.setHours(0, 0, 0, 0);
@@ -409,6 +412,46 @@ var TaskOperations = class _TaskOperations {
409
412
  return data;
410
413
  }
411
414
  // ============================================
415
+ // ARCHIVE OPERATIONS
416
+ // ============================================
417
+ /**
418
+ * Archive a task - moves it to archived status
419
+ * Typically used for completed tasks you want to hide from views
420
+ */
421
+ async archiveTask(taskId) {
422
+ const { data, error } = await this.supabase.from("tasks").update({
423
+ status: "archived",
424
+ archived_at: (/* @__PURE__ */ new Date()).toISOString()
425
+ }).eq("id", taskId).select().single();
426
+ if (error) throw error;
427
+ return data;
428
+ }
429
+ /**
430
+ * Unarchive a task - restores it to its previous state (done by default)
431
+ */
432
+ async unarchiveTask(taskId, restoreStatus = "done") {
433
+ const { data, error } = await this.supabase.from("tasks").update({
434
+ status: restoreStatus,
435
+ archived_at: null
436
+ }).eq("id", taskId).select().single();
437
+ if (error) throw error;
438
+ return data;
439
+ }
440
+ /**
441
+ * Get all archived tasks
442
+ */
443
+ async getArchivedTasks(limit = 50) {
444
+ const { data, error } = await this.supabase.from("tasks").select(`
445
+ *,
446
+ tags:task_tags(tag:tags(*))
447
+ `).eq("status", "archived").is("parent_task_id", null).order("archived_at", { ascending: false }).limit(limit);
448
+ if (error) throw error;
449
+ return (data || []).map((task) => ({
450
+ ...task,
451
+ tags: task.tags?.map((t) => t.tag).filter(Boolean) || []
452
+ }));
453
+ }
454
+ // ============================================
412
455
  // TAG OPERATIONS
413
456
  // ============================================
414
457
  async getTags() {
@@ -435,11 +478,15 @@ var TaskOperations = class _TaskOperations {
435
478
  // ============================================
436
479
  // SEARCH & UTILITY
437
480
  // ============================================
438
- async searchTasks(query, limit = 20) {
439
- const { data, error } = await this.supabase.from("tasks").select(`
481
+ async searchTasks(query, limit = 20, includeArchived = false) {
482
+ let dbQuery = this.supabase.from("tasks").select(`
440
483
  *,
441
484
  tags:task_tags(tag:tags(*))
442
485
  `).ilike("title", `%${query}%`).is("parent_task_id", null).eq("completed", false).order("position", { ascending: true }).limit(limit);
486
+ if (!includeArchived) {
487
+ dbQuery = dbQuery.neq("status", "archived");
488
+ }
489
+ const { data, error } = await dbQuery;
443
490
  if (error) throw error;
444
491
  return (data || []).map((task) => ({
445
492
  ...task,
@@ -458,10 +505,216 @@ var TaskOperations = class _TaskOperations {
458
505
  return await this.createTag(name, color);
459
506
  }
460
507
  };
508
+
509
+ // src/claude-sync.ts
510
+ function mapStatus(claudeStatus) {
511
+ switch (claudeStatus) {
512
+ case "pending":
513
+ return "todo";
514
+ case "in_progress":
515
+ return "vibing";
516
+ case "completed":
517
+ return "done";
518
+ default:
519
+ return "todo";
520
+ }
521
+ }
522
+ function calculateSimilarity(str1, str2) {
523
+ const s1 = str1.toLowerCase().trim();
524
+ const s2 = str2.toLowerCase().trim();
525
+ if (s1 === s2) return 1;
526
+ if (s1.length === 0 || s2.length === 0) return 0;
527
+ if (s1.includes(s2) || s2.includes(s1)) {
528
+ return 0.8;
529
+ }
530
+ const words1 = new Set(s1.split(/\s+/));
531
+ const words2 = new Set(s2.split(/\s+/));
532
+ let commonWords = 0;
533
+ for (const word of words1) {
534
+ if (words2.has(word)) {
535
+ commonWords++;
536
+ }
537
+ }
538
+ const totalWords = Math.max(words1.size, words2.size);
539
+ if (totalWords === 0) return 0;
540
+ return commonWords / totalWords;
541
+ }
542
+ function findBestMatch(todoContent, tasks, threshold = 0.6) {
543
+ let bestMatch = null;
544
+ for (const task of tasks) {
545
+ const similarity = calculateSimilarity(todoContent, task.title);
546
+ if (similarity >= threshold) {
547
+ if (!bestMatch || similarity > bestMatch.similarity) {
548
+ bestMatch = { task, similarity };
549
+ }
550
+ }
551
+ if (task.context_notes) {
552
+ const noteSimilarity = calculateSimilarity(todoContent, task.context_notes);
553
+ if (noteSimilarity >= threshold && noteSimilarity > (bestMatch?.similarity || 0)) {
554
+ bestMatch = { task, similarity: noteSimilarity };
555
+ }
556
+ }
557
+ }
558
+ return bestMatch;
559
+ }
560
+ var ClaudeSync = class {
561
+ taskOps;
562
+ constructor(taskOps) {
563
+ this.taskOps = taskOps;
564
+ }
565
+ /**
566
+ * Sync a single Claude todo item with VibeTasks
567
+ */
568
+ async syncTodo(todo, existingTasks, options = {}) {
569
+ const {
570
+ createMissing = false,
571
+ projectTag,
572
+ matchThreshold = 0.6,
573
+ syncPending = true
574
+ } = options;
575
+ try {
576
+ if (todo.status === "pending" && !syncPending) {
577
+ return {
578
+ content: todo.content,
579
+ matched: false,
580
+ action: "skipped"
581
+ };
582
+ }
583
+ const tasks = existingTasks || await this.taskOps.getTasks("all");
584
+ const match = findBestMatch(todo.content, tasks, matchThreshold);
585
+ if (match) {
586
+ const vibeStatus = mapStatus(todo.status);
587
+ const currentStatus = match.task.status || "todo";
588
+ if (currentStatus !== vibeStatus) {
589
+ if (vibeStatus === "done" && !match.task.completed) {
590
+ await this.taskOps.completeTask(match.task.id);
591
+ } else {
592
+ await this.taskOps.updateTask(match.task.id, {
593
+ status: vibeStatus,
594
+ completed: vibeStatus === "done"
595
+ });
596
+ }
597
+ return {
598
+ content: todo.content,
599
+ matched: true,
600
+ taskId: match.task.id,
601
+ action: "updated",
602
+ previousStatus: currentStatus,
603
+ newStatus: vibeStatus
604
+ };
605
+ }
606
+ return {
607
+ content: todo.content,
608
+ matched: true,
609
+ taskId: match.task.id,
610
+ action: "skipped"
611
+ };
612
+ }
613
+ if (createMissing) {
614
+ const vibeStatus = mapStatus(todo.status);
615
+ const newTask = await this.taskOps.createTask({
616
+ title: todo.content,
617
+ status: vibeStatus,
618
+ project_tag: projectTag,
619
+ created_by: "ai",
620
+ context_notes: todo.activeForm ? `Started: ${todo.activeForm}` : void 0,
621
+ completed: vibeStatus === "done"
622
+ });
623
+ return {
624
+ content: todo.content,
625
+ matched: false,
626
+ taskId: newTask.id,
627
+ action: "created",
628
+ newStatus: vibeStatus
629
+ };
630
+ }
631
+ return {
632
+ content: todo.content,
633
+ matched: false,
634
+ action: "not_found"
635
+ };
636
+ } catch (error) {
637
+ return {
638
+ content: todo.content,
639
+ matched: false,
640
+ action: "skipped",
641
+ error: error.message
642
+ };
643
+ }
644
+ }
645
+ /**
646
+ * Sync all Claude todos with VibeTasks
647
+ */
648
+ async syncAllTodos(todos, options = {}) {
649
+ const results = [];
650
+ let synced = 0;
651
+ let failed = 0;
652
+ try {
653
+ const tasks = await this.taskOps.getTasks("all");
654
+ for (const todo of todos) {
655
+ const result = await this.syncTodo(todo, tasks, options);
656
+ results.push(result);
657
+ if (result.action === "updated" || result.action === "created") {
658
+ synced++;
659
+ } else if (result.error) {
660
+ failed++;
661
+ }
662
+ }
663
+ return {
664
+ success: failed === 0,
665
+ processed: todos.length,
666
+ synced,
667
+ failed,
668
+ results
669
+ };
670
+ } catch (error) {
671
+ return {
672
+ success: false,
673
+ processed: 0,
674
+ synced: 0,
675
+ failed: todos.length,
676
+ results: [{
677
+ content: "All todos",
678
+ matched: false,
679
+ action: "skipped",
680
+ error: error.message
681
+ }]
682
+ };
683
+ }
684
+ }
685
+ /**
686
+ * Get current vibing tasks (for comparison with Claude's in_progress)
687
+ */
688
+ async getVibingTasks() {
689
+ const allTasks = await this.taskOps.getTasks("all");
690
+ return allTasks.filter((t) => t.status === "vibing" && !t.completed);
691
+ }
692
+ /**
693
+ * Auto-start vibing on a task when Claude marks it as in_progress
694
+ */
695
+ async startVibing(taskId) {
696
+ return await this.taskOps.updateTask(taskId, {
697
+ status: "vibing",
698
+ completed: false
699
+ });
700
+ }
701
+ /**
702
+ * Complete a task when Claude marks it as completed
703
+ */
704
+ async completeTask(taskId) {
705
+ return await this.taskOps.completeTask(taskId);
706
+ }
707
+ };
708
+ async function syncClaudeTodos(taskOps, todos, options = {}) {
709
+ const sync = new ClaudeSync(taskOps);
710
+ return await sync.syncAllTodos(todos, options);
711
+ }
461
712
  export {
462
713
  AuthManager,
714
+ ClaudeSync,
463
715
  ConfigManager,
464
716
  TaskOperations,
465
717
  createSupabaseClient,
466
- createSupabaseClientFromEnv
718
+ createSupabaseClientFromEnv,
719
+ syncClaudeTodos
467
720
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibetasks/core",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Shared core logic for VibeTasks MCP server and CLI - authentication, task operations, and config management",
5
5
  "author": "Vyas",
6
6
  "license": "MIT",