@vibetasks/core 0.2.1 → 0.4.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 +154 -6
- package/dist/index.js +293 -6
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -125,20 +125,39 @@ 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';
|
|
135
|
+
interface Subtask {
|
|
136
|
+
id: string;
|
|
137
|
+
title: string;
|
|
138
|
+
done: boolean;
|
|
139
|
+
notes?: string;
|
|
140
|
+
}
|
|
141
|
+
interface Attachment {
|
|
142
|
+
id: string;
|
|
143
|
+
task_id: string;
|
|
144
|
+
file_name: string;
|
|
145
|
+
file_type: string;
|
|
146
|
+
storage_path: string;
|
|
147
|
+
is_image?: boolean;
|
|
148
|
+
alt_text?: string;
|
|
149
|
+
ai_description?: string;
|
|
150
|
+
}
|
|
134
151
|
interface Task {
|
|
135
152
|
id: string;
|
|
136
153
|
user_id?: string;
|
|
137
154
|
title: string;
|
|
155
|
+
description?: string;
|
|
138
156
|
notes?: string;
|
|
139
157
|
notes_format?: 'html' | 'markdown';
|
|
140
158
|
completed?: boolean;
|
|
141
159
|
completed_at?: string;
|
|
160
|
+
archived_at?: string;
|
|
142
161
|
due_date?: string;
|
|
143
162
|
start_date?: string;
|
|
144
163
|
duration_minutes?: number;
|
|
@@ -151,8 +170,9 @@ interface Task {
|
|
|
151
170
|
created_at?: string;
|
|
152
171
|
updated_at?: string;
|
|
153
172
|
tags?: Tag[];
|
|
154
|
-
attachments?:
|
|
173
|
+
attachments?: Attachment[];
|
|
155
174
|
subtasks?: Task[];
|
|
175
|
+
subtasks_json?: Subtask[];
|
|
156
176
|
status?: TaskStatus;
|
|
157
177
|
project_tag?: string;
|
|
158
178
|
created_by?: 'ai' | 'human';
|
|
@@ -170,22 +190,150 @@ declare class TaskOperations {
|
|
|
170
190
|
* Create TaskOperations from AuthManager
|
|
171
191
|
*/
|
|
172
192
|
static fromAuthManager(authManager: AuthManager): Promise<TaskOperations>;
|
|
173
|
-
getTasks(filter?: TaskFilter): Promise<Task[]>;
|
|
193
|
+
getTasks(filter?: TaskFilter, includeArchived?: boolean): Promise<Task[]>;
|
|
174
194
|
getTask(taskId: string): Promise<Task>;
|
|
175
195
|
createTask(task: Partial<Task>): Promise<Task>;
|
|
176
196
|
updateTask(taskId: string, updates: Partial<Task>): Promise<Task>;
|
|
177
197
|
deleteTask(taskId: string): Promise<void>;
|
|
178
198
|
completeTask(taskId: string): Promise<Task>;
|
|
179
199
|
uncompleteTask(taskId: string): Promise<Task>;
|
|
200
|
+
/**
|
|
201
|
+
* Archive a task - moves it to archived status
|
|
202
|
+
* Typically used for completed tasks you want to hide from views
|
|
203
|
+
*/
|
|
204
|
+
archiveTask(taskId: string): Promise<Task>;
|
|
205
|
+
/**
|
|
206
|
+
* Unarchive a task - restores it to its previous state (done by default)
|
|
207
|
+
*/
|
|
208
|
+
unarchiveTask(taskId: string, restoreStatus?: TaskStatus): Promise<Task>;
|
|
209
|
+
/**
|
|
210
|
+
* Get all archived tasks
|
|
211
|
+
*/
|
|
212
|
+
getArchivedTasks(limit?: number): Promise<Task[]>;
|
|
180
213
|
getTags(): Promise<Tag[]>;
|
|
181
214
|
createTag(name: string, color?: string): Promise<Tag>;
|
|
182
215
|
linkTaskTags(taskId: string, tagIds: string[]): Promise<void>;
|
|
183
216
|
unlinkAllTaskTags(taskId: string): Promise<void>;
|
|
184
|
-
searchTasks(query: string, limit?: number): Promise<Task[]>;
|
|
217
|
+
searchTasks(query: string, limit?: number, includeArchived?: boolean): Promise<Task[]>;
|
|
185
218
|
/**
|
|
186
219
|
* Find tag by name (case-insensitive), create if doesn't exist
|
|
187
220
|
*/
|
|
188
221
|
findOrCreateTag(name: string, color?: string): Promise<Tag>;
|
|
222
|
+
/**
|
|
223
|
+
* Get a signed URL for an attachment (valid for 1 hour)
|
|
224
|
+
*/
|
|
225
|
+
getAttachmentUrl(storagePath: string): Promise<string>;
|
|
226
|
+
/**
|
|
227
|
+
* Get all attachments for a task with signed URLs
|
|
228
|
+
*/
|
|
229
|
+
getTaskAttachments(taskId: string): Promise<(Attachment & {
|
|
230
|
+
url: string;
|
|
231
|
+
})[]>;
|
|
189
232
|
}
|
|
190
233
|
|
|
191
|
-
|
|
234
|
+
/**
|
|
235
|
+
* Claude Code TodoWrite Sync
|
|
236
|
+
*
|
|
237
|
+
* This module provides synchronization between Claude's TodoWrite state
|
|
238
|
+
* and VibeTasks task statuses.
|
|
239
|
+
*
|
|
240
|
+
* Status mapping:
|
|
241
|
+
* Claude "pending" -> VibeTasks "todo"
|
|
242
|
+
* Claude "in_progress" -> VibeTasks "vibing"
|
|
243
|
+
* Claude "completed" -> VibeTasks "done"
|
|
244
|
+
*/
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Claude Code TodoWrite item structure
|
|
248
|
+
*/
|
|
249
|
+
interface ClaudeTodoItem {
|
|
250
|
+
/** The task description/content */
|
|
251
|
+
content: string;
|
|
252
|
+
/** Current status: pending, in_progress, or completed */
|
|
253
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
254
|
+
/** Active form description (used during in_progress) */
|
|
255
|
+
activeForm?: string;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Result of syncing a single todo item
|
|
259
|
+
*/
|
|
260
|
+
interface SyncResult {
|
|
261
|
+
/** The Claude todo content */
|
|
262
|
+
content: string;
|
|
263
|
+
/** Whether a matching VibeTasks task was found */
|
|
264
|
+
matched: boolean;
|
|
265
|
+
/** The VibeTasks task ID if matched */
|
|
266
|
+
taskId?: string;
|
|
267
|
+
/** The action taken */
|
|
268
|
+
action: 'created' | 'updated' | 'skipped' | 'not_found';
|
|
269
|
+
/** Previous status (if updated) */
|
|
270
|
+
previousStatus?: TaskStatus;
|
|
271
|
+
/** New status (if updated) */
|
|
272
|
+
newStatus?: TaskStatus;
|
|
273
|
+
/** Error message if any */
|
|
274
|
+
error?: string;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Result of syncing all todos
|
|
278
|
+
*/
|
|
279
|
+
interface SyncAllResult {
|
|
280
|
+
/** Whether the overall sync was successful */
|
|
281
|
+
success: boolean;
|
|
282
|
+
/** Number of todos processed */
|
|
283
|
+
processed: number;
|
|
284
|
+
/** Number of todos synced successfully */
|
|
285
|
+
synced: number;
|
|
286
|
+
/** Number of todos that failed to sync */
|
|
287
|
+
failed: number;
|
|
288
|
+
/** Individual sync results */
|
|
289
|
+
results: SyncResult[];
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Options for the sync operation
|
|
293
|
+
*/
|
|
294
|
+
interface SyncOptions {
|
|
295
|
+
/** Create new tasks for unmatched todos (default: false) */
|
|
296
|
+
createMissing?: boolean;
|
|
297
|
+
/** Project tag to use for created tasks */
|
|
298
|
+
projectTag?: string;
|
|
299
|
+
/** Minimum similarity score for matching (0-1, default: 0.6) */
|
|
300
|
+
matchThreshold?: number;
|
|
301
|
+
/** Whether to sync 'pending' todos as 'todo' status (default: true) */
|
|
302
|
+
syncPending?: boolean;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Claude Sync class for managing TodoWrite synchronization
|
|
306
|
+
*/
|
|
307
|
+
declare class ClaudeSync {
|
|
308
|
+
private taskOps;
|
|
309
|
+
constructor(taskOps: TaskOperations);
|
|
310
|
+
/**
|
|
311
|
+
* Sync a single Claude todo item with VibeTasks
|
|
312
|
+
*/
|
|
313
|
+
syncTodo(todo: ClaudeTodoItem, existingTasks?: Task[], options?: SyncOptions): Promise<SyncResult>;
|
|
314
|
+
/**
|
|
315
|
+
* Sync all Claude todos with VibeTasks
|
|
316
|
+
*/
|
|
317
|
+
syncAllTodos(todos: ClaudeTodoItem[], options?: SyncOptions): Promise<SyncAllResult>;
|
|
318
|
+
/**
|
|
319
|
+
* Get current vibing tasks (for comparison with Claude's in_progress)
|
|
320
|
+
*/
|
|
321
|
+
getVibingTasks(): Promise<Task[]>;
|
|
322
|
+
/**
|
|
323
|
+
* Auto-start vibing on a task when Claude marks it as in_progress
|
|
324
|
+
*/
|
|
325
|
+
startVibing(taskId: string): Promise<Task>;
|
|
326
|
+
/**
|
|
327
|
+
* Complete a task when Claude marks it as completed
|
|
328
|
+
*/
|
|
329
|
+
completeTask(taskId: string): Promise<Task>;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* MCP Tool: sync_claude_todos
|
|
333
|
+
*
|
|
334
|
+
* This function is designed to be called as an MCP tool.
|
|
335
|
+
* It accepts Claude's current todo list and syncs it with VibeTasks.
|
|
336
|
+
*/
|
|
337
|
+
declare function syncClaudeTodos(taskOps: TaskOperations, todos: ClaudeTodoItem[], options?: SyncOptions): Promise<SyncAllResult>;
|
|
338
|
+
|
|
339
|
+
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);
|
|
@@ -318,12 +321,16 @@ var TaskOperations = class _TaskOperations {
|
|
|
318
321
|
async getTask(taskId) {
|
|
319
322
|
const { data, error } = await this.supabase.from("tasks").select(`
|
|
320
323
|
*,
|
|
321
|
-
tags:task_tags(tag:tags(*))
|
|
324
|
+
tags:task_tags(tag:tags(*)),
|
|
325
|
+
attachments:task_attachments(*)
|
|
322
326
|
`).eq("id", taskId).single();
|
|
323
327
|
if (error) throw error;
|
|
324
328
|
return {
|
|
325
329
|
...data,
|
|
326
|
-
tags: data.tags?.map((t) => t.tag).filter(Boolean) || []
|
|
330
|
+
tags: data.tags?.map((t) => t.tag).filter(Boolean) || [],
|
|
331
|
+
attachments: data.attachments || [],
|
|
332
|
+
subtasks_json: data.subtasks || []
|
|
333
|
+
// DB column is 'subtasks', maps to subtasks_json
|
|
327
334
|
};
|
|
328
335
|
}
|
|
329
336
|
async createTask(task) {
|
|
@@ -332,6 +339,7 @@ var TaskOperations = class _TaskOperations {
|
|
|
332
339
|
const { data, error } = await this.supabase.from("tasks").insert({
|
|
333
340
|
user_id: user.id,
|
|
334
341
|
title: task.title,
|
|
342
|
+
description: task.description,
|
|
335
343
|
notes: task.notes,
|
|
336
344
|
notes_format: task.notes_format || "markdown",
|
|
337
345
|
due_date: task.due_date,
|
|
@@ -343,6 +351,8 @@ var TaskOperations = class _TaskOperations {
|
|
|
343
351
|
priority: task.priority || "none",
|
|
344
352
|
parent_task_id: task.parent_task_id,
|
|
345
353
|
position: task.position || 0,
|
|
354
|
+
subtasks: task.subtasks_json || [],
|
|
355
|
+
// Inline subtasks as JSON
|
|
346
356
|
// VibeTasks-specific fields
|
|
347
357
|
status: task.status || "todo",
|
|
348
358
|
project_tag: task.project_tag,
|
|
@@ -359,6 +369,7 @@ var TaskOperations = class _TaskOperations {
|
|
|
359
369
|
async updateTask(taskId, updates) {
|
|
360
370
|
const updateData = {};
|
|
361
371
|
if (updates.title !== void 0) updateData.title = updates.title;
|
|
372
|
+
if (updates.description !== void 0) updateData.description = updates.description;
|
|
362
373
|
if (updates.notes !== void 0) updateData.notes = updates.notes;
|
|
363
374
|
if (updates.notes_format !== void 0) updateData.notes_format = updates.notes_format;
|
|
364
375
|
if (updates.completed !== void 0) {
|
|
@@ -373,6 +384,7 @@ var TaskOperations = class _TaskOperations {
|
|
|
373
384
|
if (updates.recurrence_end !== void 0) updateData.recurrence_end = updates.recurrence_end;
|
|
374
385
|
if (updates.priority !== void 0) updateData.priority = updates.priority;
|
|
375
386
|
if (updates.position !== void 0) updateData.position = updates.position;
|
|
387
|
+
if (updates.subtasks_json !== void 0) updateData.subtasks = updates.subtasks_json;
|
|
376
388
|
if (updates.status !== void 0) updateData.status = updates.status;
|
|
377
389
|
if (updates.project_tag !== void 0) updateData.project_tag = updates.project_tag;
|
|
378
390
|
if (updates.created_by !== void 0) updateData.created_by = updates.created_by;
|
|
@@ -409,6 +421,46 @@ var TaskOperations = class _TaskOperations {
|
|
|
409
421
|
return data;
|
|
410
422
|
}
|
|
411
423
|
// ============================================
|
|
424
|
+
// ARCHIVE OPERATIONS
|
|
425
|
+
// ============================================
|
|
426
|
+
/**
|
|
427
|
+
* Archive a task - moves it to archived status
|
|
428
|
+
* Typically used for completed tasks you want to hide from views
|
|
429
|
+
*/
|
|
430
|
+
async archiveTask(taskId) {
|
|
431
|
+
const { data, error } = await this.supabase.from("tasks").update({
|
|
432
|
+
status: "archived",
|
|
433
|
+
archived_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
434
|
+
}).eq("id", taskId).select().single();
|
|
435
|
+
if (error) throw error;
|
|
436
|
+
return data;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Unarchive a task - restores it to its previous state (done by default)
|
|
440
|
+
*/
|
|
441
|
+
async unarchiveTask(taskId, restoreStatus = "done") {
|
|
442
|
+
const { data, error } = await this.supabase.from("tasks").update({
|
|
443
|
+
status: restoreStatus,
|
|
444
|
+
archived_at: null
|
|
445
|
+
}).eq("id", taskId).select().single();
|
|
446
|
+
if (error) throw error;
|
|
447
|
+
return data;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Get all archived tasks
|
|
451
|
+
*/
|
|
452
|
+
async getArchivedTasks(limit = 50) {
|
|
453
|
+
const { data, error } = await this.supabase.from("tasks").select(`
|
|
454
|
+
*,
|
|
455
|
+
tags:task_tags(tag:tags(*))
|
|
456
|
+
`).eq("status", "archived").is("parent_task_id", null).order("archived_at", { ascending: false }).limit(limit);
|
|
457
|
+
if (error) throw error;
|
|
458
|
+
return (data || []).map((task) => ({
|
|
459
|
+
...task,
|
|
460
|
+
tags: task.tags?.map((t) => t.tag).filter(Boolean) || []
|
|
461
|
+
}));
|
|
462
|
+
}
|
|
463
|
+
// ============================================
|
|
412
464
|
// TAG OPERATIONS
|
|
413
465
|
// ============================================
|
|
414
466
|
async getTags() {
|
|
@@ -435,11 +487,15 @@ var TaskOperations = class _TaskOperations {
|
|
|
435
487
|
// ============================================
|
|
436
488
|
// SEARCH & UTILITY
|
|
437
489
|
// ============================================
|
|
438
|
-
async searchTasks(query, limit = 20) {
|
|
439
|
-
|
|
490
|
+
async searchTasks(query, limit = 20, includeArchived = false) {
|
|
491
|
+
let dbQuery = this.supabase.from("tasks").select(`
|
|
440
492
|
*,
|
|
441
493
|
tags:task_tags(tag:tags(*))
|
|
442
494
|
`).ilike("title", `%${query}%`).is("parent_task_id", null).eq("completed", false).order("position", { ascending: true }).limit(limit);
|
|
495
|
+
if (!includeArchived) {
|
|
496
|
+
dbQuery = dbQuery.neq("status", "archived");
|
|
497
|
+
}
|
|
498
|
+
const { data, error } = await dbQuery;
|
|
443
499
|
if (error) throw error;
|
|
444
500
|
return (data || []).map((task) => ({
|
|
445
501
|
...task,
|
|
@@ -457,11 +513,242 @@ var TaskOperations = class _TaskOperations {
|
|
|
457
513
|
}
|
|
458
514
|
return await this.createTag(name, color);
|
|
459
515
|
}
|
|
516
|
+
// ============================================
|
|
517
|
+
// ATTACHMENT OPERATIONS
|
|
518
|
+
// ============================================
|
|
519
|
+
/**
|
|
520
|
+
* Get a signed URL for an attachment (valid for 1 hour)
|
|
521
|
+
*/
|
|
522
|
+
async getAttachmentUrl(storagePath) {
|
|
523
|
+
const { data, error } = await this.supabase.storage.from("task-attachments").createSignedUrl(storagePath, 3600);
|
|
524
|
+
if (error) throw error;
|
|
525
|
+
return data.signedUrl;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Get all attachments for a task with signed URLs
|
|
529
|
+
*/
|
|
530
|
+
async getTaskAttachments(taskId) {
|
|
531
|
+
const { data, error } = await this.supabase.from("task_attachments").select("*").eq("task_id", taskId);
|
|
532
|
+
if (error) throw error;
|
|
533
|
+
const attachmentsWithUrls = await Promise.all(
|
|
534
|
+
(data || []).map(async (attachment) => {
|
|
535
|
+
const url = await this.getAttachmentUrl(attachment.storage_path);
|
|
536
|
+
return { ...attachment, url };
|
|
537
|
+
})
|
|
538
|
+
);
|
|
539
|
+
return attachmentsWithUrls;
|
|
540
|
+
}
|
|
460
541
|
};
|
|
542
|
+
|
|
543
|
+
// src/claude-sync.ts
|
|
544
|
+
function mapStatus(claudeStatus) {
|
|
545
|
+
switch (claudeStatus) {
|
|
546
|
+
case "pending":
|
|
547
|
+
return "todo";
|
|
548
|
+
case "in_progress":
|
|
549
|
+
return "vibing";
|
|
550
|
+
case "completed":
|
|
551
|
+
return "done";
|
|
552
|
+
default:
|
|
553
|
+
return "todo";
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
function calculateSimilarity(str1, str2) {
|
|
557
|
+
const s1 = str1.toLowerCase().trim();
|
|
558
|
+
const s2 = str2.toLowerCase().trim();
|
|
559
|
+
if (s1 === s2) return 1;
|
|
560
|
+
if (s1.length === 0 || s2.length === 0) return 0;
|
|
561
|
+
if (s1.includes(s2) || s2.includes(s1)) {
|
|
562
|
+
return 0.8;
|
|
563
|
+
}
|
|
564
|
+
const words1 = new Set(s1.split(/\s+/));
|
|
565
|
+
const words2 = new Set(s2.split(/\s+/));
|
|
566
|
+
let commonWords = 0;
|
|
567
|
+
for (const word of words1) {
|
|
568
|
+
if (words2.has(word)) {
|
|
569
|
+
commonWords++;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
const totalWords = Math.max(words1.size, words2.size);
|
|
573
|
+
if (totalWords === 0) return 0;
|
|
574
|
+
return commonWords / totalWords;
|
|
575
|
+
}
|
|
576
|
+
function findBestMatch(todoContent, tasks, threshold = 0.6) {
|
|
577
|
+
let bestMatch = null;
|
|
578
|
+
for (const task of tasks) {
|
|
579
|
+
const similarity = calculateSimilarity(todoContent, task.title);
|
|
580
|
+
if (similarity >= threshold) {
|
|
581
|
+
if (!bestMatch || similarity > bestMatch.similarity) {
|
|
582
|
+
bestMatch = { task, similarity };
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (task.context_notes) {
|
|
586
|
+
const noteSimilarity = calculateSimilarity(todoContent, task.context_notes);
|
|
587
|
+
if (noteSimilarity >= threshold && noteSimilarity > (bestMatch?.similarity || 0)) {
|
|
588
|
+
bestMatch = { task, similarity: noteSimilarity };
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return bestMatch;
|
|
593
|
+
}
|
|
594
|
+
var ClaudeSync = class {
|
|
595
|
+
taskOps;
|
|
596
|
+
constructor(taskOps) {
|
|
597
|
+
this.taskOps = taskOps;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Sync a single Claude todo item with VibeTasks
|
|
601
|
+
*/
|
|
602
|
+
async syncTodo(todo, existingTasks, options = {}) {
|
|
603
|
+
const {
|
|
604
|
+
createMissing = false,
|
|
605
|
+
projectTag,
|
|
606
|
+
matchThreshold = 0.6,
|
|
607
|
+
syncPending = true
|
|
608
|
+
} = options;
|
|
609
|
+
try {
|
|
610
|
+
if (todo.status === "pending" && !syncPending) {
|
|
611
|
+
return {
|
|
612
|
+
content: todo.content,
|
|
613
|
+
matched: false,
|
|
614
|
+
action: "skipped"
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
const tasks = existingTasks || await this.taskOps.getTasks("all");
|
|
618
|
+
const match = findBestMatch(todo.content, tasks, matchThreshold);
|
|
619
|
+
if (match) {
|
|
620
|
+
const vibeStatus = mapStatus(todo.status);
|
|
621
|
+
const currentStatus = match.task.status || "todo";
|
|
622
|
+
if (currentStatus !== vibeStatus) {
|
|
623
|
+
if (vibeStatus === "done" && !match.task.completed) {
|
|
624
|
+
await this.taskOps.completeTask(match.task.id);
|
|
625
|
+
} else {
|
|
626
|
+
await this.taskOps.updateTask(match.task.id, {
|
|
627
|
+
status: vibeStatus,
|
|
628
|
+
completed: vibeStatus === "done"
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
content: todo.content,
|
|
633
|
+
matched: true,
|
|
634
|
+
taskId: match.task.id,
|
|
635
|
+
action: "updated",
|
|
636
|
+
previousStatus: currentStatus,
|
|
637
|
+
newStatus: vibeStatus
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
return {
|
|
641
|
+
content: todo.content,
|
|
642
|
+
matched: true,
|
|
643
|
+
taskId: match.task.id,
|
|
644
|
+
action: "skipped"
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
if (createMissing) {
|
|
648
|
+
const vibeStatus = mapStatus(todo.status);
|
|
649
|
+
const newTask = await this.taskOps.createTask({
|
|
650
|
+
title: todo.content,
|
|
651
|
+
status: vibeStatus,
|
|
652
|
+
project_tag: projectTag,
|
|
653
|
+
created_by: "ai",
|
|
654
|
+
context_notes: todo.activeForm ? `Started: ${todo.activeForm}` : void 0,
|
|
655
|
+
completed: vibeStatus === "done"
|
|
656
|
+
});
|
|
657
|
+
return {
|
|
658
|
+
content: todo.content,
|
|
659
|
+
matched: false,
|
|
660
|
+
taskId: newTask.id,
|
|
661
|
+
action: "created",
|
|
662
|
+
newStatus: vibeStatus
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
return {
|
|
666
|
+
content: todo.content,
|
|
667
|
+
matched: false,
|
|
668
|
+
action: "not_found"
|
|
669
|
+
};
|
|
670
|
+
} catch (error) {
|
|
671
|
+
return {
|
|
672
|
+
content: todo.content,
|
|
673
|
+
matched: false,
|
|
674
|
+
action: "skipped",
|
|
675
|
+
error: error.message
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Sync all Claude todos with VibeTasks
|
|
681
|
+
*/
|
|
682
|
+
async syncAllTodos(todos, options = {}) {
|
|
683
|
+
const results = [];
|
|
684
|
+
let synced = 0;
|
|
685
|
+
let failed = 0;
|
|
686
|
+
try {
|
|
687
|
+
const tasks = await this.taskOps.getTasks("all");
|
|
688
|
+
for (const todo of todos) {
|
|
689
|
+
const result = await this.syncTodo(todo, tasks, options);
|
|
690
|
+
results.push(result);
|
|
691
|
+
if (result.action === "updated" || result.action === "created") {
|
|
692
|
+
synced++;
|
|
693
|
+
} else if (result.error) {
|
|
694
|
+
failed++;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return {
|
|
698
|
+
success: failed === 0,
|
|
699
|
+
processed: todos.length,
|
|
700
|
+
synced,
|
|
701
|
+
failed,
|
|
702
|
+
results
|
|
703
|
+
};
|
|
704
|
+
} catch (error) {
|
|
705
|
+
return {
|
|
706
|
+
success: false,
|
|
707
|
+
processed: 0,
|
|
708
|
+
synced: 0,
|
|
709
|
+
failed: todos.length,
|
|
710
|
+
results: [{
|
|
711
|
+
content: "All todos",
|
|
712
|
+
matched: false,
|
|
713
|
+
action: "skipped",
|
|
714
|
+
error: error.message
|
|
715
|
+
}]
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Get current vibing tasks (for comparison with Claude's in_progress)
|
|
721
|
+
*/
|
|
722
|
+
async getVibingTasks() {
|
|
723
|
+
const allTasks = await this.taskOps.getTasks("all");
|
|
724
|
+
return allTasks.filter((t) => t.status === "vibing" && !t.completed);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Auto-start vibing on a task when Claude marks it as in_progress
|
|
728
|
+
*/
|
|
729
|
+
async startVibing(taskId) {
|
|
730
|
+
return await this.taskOps.updateTask(taskId, {
|
|
731
|
+
status: "vibing",
|
|
732
|
+
completed: false
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Complete a task when Claude marks it as completed
|
|
737
|
+
*/
|
|
738
|
+
async completeTask(taskId) {
|
|
739
|
+
return await this.taskOps.completeTask(taskId);
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
async function syncClaudeTodos(taskOps, todos, options = {}) {
|
|
743
|
+
const sync = new ClaudeSync(taskOps);
|
|
744
|
+
return await sync.syncAllTodos(todos, options);
|
|
745
|
+
}
|
|
461
746
|
export {
|
|
462
747
|
AuthManager,
|
|
748
|
+
ClaudeSync,
|
|
463
749
|
ConfigManager,
|
|
464
750
|
TaskOperations,
|
|
465
751
|
createSupabaseClient,
|
|
466
|
-
createSupabaseClientFromEnv
|
|
752
|
+
createSupabaseClientFromEnv,
|
|
753
|
+
syncClaudeTodos
|
|
467
754
|
};
|
package/package.json
CHANGED