roadmap-skill 0.2.3 → 0.2.5
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.js +765 -426
- package/dist/index.js.map +1 -1
- package/dist/web/app/assets/main-BJPGJG4y.js +9 -0
- package/dist/web/app/index.html +1 -1
- package/dist/web/server.js +752 -61
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
- package/dist/web/app/assets/main-DZvNWN99.js +0 -9
package/dist/index.js
CHANGED
|
@@ -287,6 +287,58 @@ var ProjectStorage = class {
|
|
|
287
287
|
}
|
|
288
288
|
return results;
|
|
289
289
|
}
|
|
290
|
+
async exportAllData() {
|
|
291
|
+
await this.ensureDirectory();
|
|
292
|
+
const fs3 = await import("fs/promises");
|
|
293
|
+
const files = await fs3.readdir(this.storageDir);
|
|
294
|
+
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
295
|
+
const projects = [];
|
|
296
|
+
for (const file of jsonFiles) {
|
|
297
|
+
try {
|
|
298
|
+
const filePath = path3.join(this.storageDir, file);
|
|
299
|
+
const data = await readJsonFile(filePath);
|
|
300
|
+
projects.push(data);
|
|
301
|
+
} catch {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
version: 1,
|
|
307
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
308
|
+
projects
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
async importAllData(data) {
|
|
312
|
+
await this.ensureDirectory();
|
|
313
|
+
let imported = 0;
|
|
314
|
+
let errors = 0;
|
|
315
|
+
const errorDetails = [];
|
|
316
|
+
if (!data.projects || !Array.isArray(data.projects)) {
|
|
317
|
+
throw new Error("Invalid backup data: projects array is required");
|
|
318
|
+
}
|
|
319
|
+
for (const projectData of data.projects) {
|
|
320
|
+
try {
|
|
321
|
+
if (!projectData.project || !projectData.project.id) {
|
|
322
|
+
errors++;
|
|
323
|
+
errorDetails.push("Skipping invalid project: missing project or id");
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
const filePath = this.getFilePath(projectData.project.id);
|
|
327
|
+
await writeJsonFile(filePath, projectData);
|
|
328
|
+
imported++;
|
|
329
|
+
} catch (error) {
|
|
330
|
+
errors++;
|
|
331
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
332
|
+
errorDetails.push(`Failed to import project ${projectData.project?.id || "unknown"}: ${errorMessage}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
success: errors === 0,
|
|
337
|
+
imported,
|
|
338
|
+
errors,
|
|
339
|
+
errorDetails
|
|
340
|
+
};
|
|
341
|
+
}
|
|
290
342
|
};
|
|
291
343
|
var storage = new ProjectStorage();
|
|
292
344
|
|
|
@@ -443,266 +495,534 @@ var deleteProjectTool = {
|
|
|
443
495
|
// src/tools/task-tools.ts
|
|
444
496
|
init_esm_shims();
|
|
445
497
|
import { z as z2 } from "zod";
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
498
|
+
|
|
499
|
+
// src/services/index.ts
|
|
500
|
+
init_esm_shims();
|
|
501
|
+
|
|
502
|
+
// src/services/tag-service.ts
|
|
503
|
+
init_esm_shims();
|
|
504
|
+
function generateTagId() {
|
|
505
|
+
return `tag_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
450
506
|
}
|
|
451
|
-
var
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
async execute(input) {
|
|
507
|
+
var TagService = class {
|
|
508
|
+
storage;
|
|
509
|
+
constructor(storage2) {
|
|
510
|
+
this.storage = storage2;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Create a new tag in a project
|
|
514
|
+
* @param projectId - The project ID
|
|
515
|
+
* @param data - Tag creation data
|
|
516
|
+
* @returns The created tag or error
|
|
517
|
+
*/
|
|
518
|
+
async create(projectId, data) {
|
|
464
519
|
try {
|
|
465
|
-
const projectData = await storage.readProject(
|
|
520
|
+
const projectData = await this.storage.readProject(projectId);
|
|
466
521
|
if (!projectData) {
|
|
467
522
|
return {
|
|
468
523
|
success: false,
|
|
469
|
-
error: `Project with ID '${
|
|
524
|
+
error: `Project with ID '${projectId}' not found`,
|
|
525
|
+
code: "NOT_FOUND"
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
const existingTag = projectData.tags.find(
|
|
529
|
+
(t) => t.name.toLowerCase() === data.name.toLowerCase()
|
|
530
|
+
);
|
|
531
|
+
if (existingTag) {
|
|
532
|
+
return {
|
|
533
|
+
success: false,
|
|
534
|
+
error: `Tag with name '${data.name}' already exists in this project`,
|
|
535
|
+
code: "DUPLICATE_ERROR"
|
|
470
536
|
};
|
|
471
537
|
}
|
|
472
538
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
473
|
-
const
|
|
474
|
-
id:
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
description:
|
|
478
|
-
|
|
479
|
-
priority: input.priority,
|
|
480
|
-
tags: input.tags,
|
|
481
|
-
dueDate: input.dueDate ?? null,
|
|
482
|
-
assignee: input.assignee ?? null,
|
|
483
|
-
createdAt: now,
|
|
484
|
-
updatedAt: now,
|
|
485
|
-
completedAt: null
|
|
539
|
+
const tag = {
|
|
540
|
+
id: generateTagId(),
|
|
541
|
+
name: data.name,
|
|
542
|
+
color: data.color,
|
|
543
|
+
description: data.description || "",
|
|
544
|
+
createdAt: now
|
|
486
545
|
};
|
|
487
|
-
projectData.
|
|
546
|
+
projectData.tags.push(tag);
|
|
488
547
|
projectData.project.updatedAt = now;
|
|
489
|
-
|
|
490
|
-
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
491
|
-
await writeJsonFile2(filePath, projectData);
|
|
548
|
+
await this.saveProjectData(projectId, projectData);
|
|
492
549
|
return {
|
|
493
550
|
success: true,
|
|
494
|
-
data:
|
|
551
|
+
data: tag
|
|
495
552
|
};
|
|
496
553
|
} catch (error) {
|
|
497
554
|
return {
|
|
498
555
|
success: false,
|
|
499
|
-
error: error instanceof Error ? error.message : "Failed to create
|
|
556
|
+
error: error instanceof Error ? error.message : "Failed to create tag",
|
|
557
|
+
code: "INTERNAL_ERROR"
|
|
500
558
|
};
|
|
501
559
|
}
|
|
502
560
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
status: TaskStatusEnum.optional(),
|
|
510
|
-
priority: TaskPriorityEnum.optional(),
|
|
511
|
-
tags: z2.array(z2.string()).optional(),
|
|
512
|
-
assignee: z2.string().optional(),
|
|
513
|
-
dueBefore: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
514
|
-
dueAfter: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
515
|
-
includeCompleted: z2.boolean().optional()
|
|
516
|
-
}),
|
|
517
|
-
async execute(input) {
|
|
561
|
+
/**
|
|
562
|
+
* List all tags in a project
|
|
563
|
+
* @param projectId - The project ID
|
|
564
|
+
* @returns Array of tags or error
|
|
565
|
+
*/
|
|
566
|
+
async list(projectId) {
|
|
518
567
|
try {
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
includeCompleted: input.includeCompleted
|
|
528
|
-
});
|
|
568
|
+
const projectData = await this.storage.readProject(projectId);
|
|
569
|
+
if (!projectData) {
|
|
570
|
+
return {
|
|
571
|
+
success: false,
|
|
572
|
+
error: `Project with ID '${projectId}' not found`,
|
|
573
|
+
code: "NOT_FOUND"
|
|
574
|
+
};
|
|
575
|
+
}
|
|
529
576
|
return {
|
|
530
577
|
success: true,
|
|
531
|
-
data:
|
|
578
|
+
data: projectData.tags
|
|
532
579
|
};
|
|
533
580
|
} catch (error) {
|
|
534
581
|
return {
|
|
535
582
|
success: false,
|
|
536
|
-
error: error instanceof Error ? error.message : "Failed to list
|
|
583
|
+
error: error instanceof Error ? error.message : "Failed to list tags",
|
|
584
|
+
code: "INTERNAL_ERROR"
|
|
537
585
|
};
|
|
538
586
|
}
|
|
539
587
|
}
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
async execute(input) {
|
|
588
|
+
/**
|
|
589
|
+
* Update an existing tag
|
|
590
|
+
* @param projectId - The project ID
|
|
591
|
+
* @param tagId - The tag ID
|
|
592
|
+
* @param data - Tag update data
|
|
593
|
+
* @returns The updated tag or error
|
|
594
|
+
*/
|
|
595
|
+
async update(projectId, tagId, data) {
|
|
549
596
|
try {
|
|
550
|
-
const projectData = await storage.readProject(
|
|
597
|
+
const projectData = await this.storage.readProject(projectId);
|
|
551
598
|
if (!projectData) {
|
|
552
599
|
return {
|
|
553
600
|
success: false,
|
|
554
|
-
error: `Project with ID '${
|
|
601
|
+
error: `Project with ID '${projectId}' not found`,
|
|
602
|
+
code: "NOT_FOUND"
|
|
555
603
|
};
|
|
556
604
|
}
|
|
557
|
-
const
|
|
558
|
-
if (
|
|
605
|
+
const tagIndex = projectData.tags.findIndex((t) => t.id === tagId);
|
|
606
|
+
if (tagIndex === -1) {
|
|
607
|
+
return {
|
|
608
|
+
success: false,
|
|
609
|
+
error: `Tag with ID '${tagId}' not found in project '${projectId}'`,
|
|
610
|
+
code: "NOT_FOUND"
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
if (Object.keys(data).length === 0) {
|
|
559
614
|
return {
|
|
560
615
|
success: false,
|
|
561
|
-
error:
|
|
616
|
+
error: "At least one field to update is required",
|
|
617
|
+
code: "VALIDATION_ERROR"
|
|
562
618
|
};
|
|
563
619
|
}
|
|
620
|
+
if (data.name) {
|
|
621
|
+
const existingTag2 = projectData.tags.find(
|
|
622
|
+
(t) => t.name.toLowerCase() === data.name.toLowerCase() && t.id !== tagId
|
|
623
|
+
);
|
|
624
|
+
if (existingTag2) {
|
|
625
|
+
return {
|
|
626
|
+
success: false,
|
|
627
|
+
error: `Tag with name '${data.name}' already exists in this project`,
|
|
628
|
+
code: "DUPLICATE_ERROR"
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
633
|
+
const existingTag = projectData.tags[tagIndex];
|
|
634
|
+
const updatedTag = {
|
|
635
|
+
...existingTag,
|
|
636
|
+
...data,
|
|
637
|
+
id: existingTag.id,
|
|
638
|
+
createdAt: existingTag.createdAt
|
|
639
|
+
};
|
|
640
|
+
projectData.tags[tagIndex] = updatedTag;
|
|
641
|
+
projectData.project.updatedAt = now;
|
|
642
|
+
await this.saveProjectData(projectId, projectData);
|
|
564
643
|
return {
|
|
565
644
|
success: true,
|
|
566
|
-
data:
|
|
645
|
+
data: updatedTag
|
|
567
646
|
};
|
|
568
647
|
} catch (error) {
|
|
569
648
|
return {
|
|
570
649
|
success: false,
|
|
571
|
-
error: error instanceof Error ? error.message : "Failed to
|
|
650
|
+
error: error instanceof Error ? error.message : "Failed to update tag",
|
|
651
|
+
code: "INTERNAL_ERROR"
|
|
572
652
|
};
|
|
573
653
|
}
|
|
574
654
|
}
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
description: z2.string().optional(),
|
|
584
|
-
status: TaskStatusEnum.optional(),
|
|
585
|
-
priority: TaskPriorityEnum.optional(),
|
|
586
|
-
tags: z2.array(z2.string()).optional(),
|
|
587
|
-
dueDate: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
|
|
588
|
-
assignee: z2.string().optional().nullable()
|
|
589
|
-
}),
|
|
590
|
-
async execute(input) {
|
|
655
|
+
/**
|
|
656
|
+
* Delete a tag by ID
|
|
657
|
+
* Also removes the tag from all tasks that use it
|
|
658
|
+
* @param projectId - The project ID
|
|
659
|
+
* @param tagId - The tag ID
|
|
660
|
+
* @returns Delete result with tag info and count of updated tasks
|
|
661
|
+
*/
|
|
662
|
+
async delete(projectId, tagId) {
|
|
591
663
|
try {
|
|
592
|
-
const projectData = await storage.readProject(
|
|
664
|
+
const projectData = await this.storage.readProject(projectId);
|
|
593
665
|
if (!projectData) {
|
|
594
666
|
return {
|
|
595
667
|
success: false,
|
|
596
|
-
error: `Project with ID '${
|
|
597
|
-
|
|
598
|
-
}
|
|
599
|
-
const taskIndex = projectData.tasks.findIndex((t) => t.id === input.taskId);
|
|
600
|
-
if (taskIndex === -1) {
|
|
601
|
-
return {
|
|
602
|
-
success: false,
|
|
603
|
-
error: `Task with ID '${input.taskId}' not found in project '${input.projectId}'`
|
|
668
|
+
error: `Project with ID '${projectId}' not found`,
|
|
669
|
+
code: "NOT_FOUND"
|
|
604
670
|
};
|
|
605
671
|
}
|
|
606
|
-
const
|
|
607
|
-
if (
|
|
672
|
+
const tagIndex = projectData.tags.findIndex((t) => t.id === tagId);
|
|
673
|
+
if (tagIndex === -1) {
|
|
608
674
|
return {
|
|
609
675
|
success: false,
|
|
610
|
-
error:
|
|
676
|
+
error: `Tag with ID '${tagId}' not found in project '${projectId}'`,
|
|
677
|
+
code: "NOT_FOUND"
|
|
611
678
|
};
|
|
612
679
|
}
|
|
680
|
+
const tag = projectData.tags[tagIndex];
|
|
613
681
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
614
|
-
|
|
615
|
-
const
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
projectData.tasks[taskIndex] = updatedTask;
|
|
682
|
+
let tasksUpdated = 0;
|
|
683
|
+
for (const task of projectData.tasks) {
|
|
684
|
+
const tagIndexInTask = task.tags.indexOf(tagId);
|
|
685
|
+
if (tagIndexInTask !== -1) {
|
|
686
|
+
task.tags.splice(tagIndexInTask, 1);
|
|
687
|
+
task.updatedAt = now;
|
|
688
|
+
tasksUpdated++;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
projectData.tags.splice(tagIndex, 1);
|
|
625
692
|
projectData.project.updatedAt = now;
|
|
626
|
-
|
|
627
|
-
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
628
|
-
await writeJsonFile2(filePath, projectData);
|
|
693
|
+
await this.saveProjectData(projectId, projectData);
|
|
629
694
|
return {
|
|
630
695
|
success: true,
|
|
631
|
-
data:
|
|
696
|
+
data: {
|
|
697
|
+
deleted: true,
|
|
698
|
+
tag,
|
|
699
|
+
tasksUpdated
|
|
700
|
+
}
|
|
632
701
|
};
|
|
633
702
|
} catch (error) {
|
|
634
703
|
return {
|
|
635
704
|
success: false,
|
|
636
|
-
error: error instanceof Error ? error.message : "Failed to
|
|
705
|
+
error: error instanceof Error ? error.message : "Failed to delete tag",
|
|
706
|
+
code: "INTERNAL_ERROR"
|
|
637
707
|
};
|
|
638
708
|
}
|
|
639
709
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}),
|
|
648
|
-
async execute(input) {
|
|
710
|
+
/**
|
|
711
|
+
* Get all tasks that have a specific tag by tag name
|
|
712
|
+
* @param projectId - The project ID
|
|
713
|
+
* @param tagName - The tag name
|
|
714
|
+
* @returns Tag info and matching tasks
|
|
715
|
+
*/
|
|
716
|
+
async getTasksByTag(projectId, tagName) {
|
|
649
717
|
try {
|
|
650
|
-
const projectData = await storage.readProject(
|
|
718
|
+
const projectData = await this.storage.readProject(projectId);
|
|
651
719
|
if (!projectData) {
|
|
652
720
|
return {
|
|
653
721
|
success: false,
|
|
654
|
-
error: `Project with ID '${
|
|
722
|
+
error: `Project with ID '${projectId}' not found`,
|
|
723
|
+
code: "NOT_FOUND"
|
|
655
724
|
};
|
|
656
725
|
}
|
|
657
|
-
const
|
|
658
|
-
|
|
726
|
+
const tag = projectData.tags.find(
|
|
727
|
+
(t) => t.name.toLowerCase() === tagName.toLowerCase()
|
|
728
|
+
);
|
|
729
|
+
if (!tag) {
|
|
659
730
|
return {
|
|
660
731
|
success: false,
|
|
661
|
-
error: `
|
|
732
|
+
error: `Tag with name '${tagName}' not found in project '${projectId}'`,
|
|
733
|
+
code: "NOT_FOUND"
|
|
662
734
|
};
|
|
663
735
|
}
|
|
664
|
-
const
|
|
665
|
-
projectData.tasks.splice(taskIndex, 1);
|
|
666
|
-
projectData.project.updatedAt = now;
|
|
667
|
-
const filePath = storage.getFilePath(input.projectId);
|
|
668
|
-
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
669
|
-
await writeJsonFile2(filePath, projectData);
|
|
736
|
+
const tasks = projectData.tasks.filter((t) => t.tags.includes(tag.id));
|
|
670
737
|
return {
|
|
671
738
|
success: true,
|
|
672
|
-
data: {
|
|
739
|
+
data: {
|
|
740
|
+
tag,
|
|
741
|
+
tasks,
|
|
742
|
+
count: tasks.length
|
|
743
|
+
}
|
|
673
744
|
};
|
|
674
745
|
} catch (error) {
|
|
675
746
|
return {
|
|
676
747
|
success: false,
|
|
677
|
-
error: error instanceof Error ? error.message : "Failed to
|
|
748
|
+
error: error instanceof Error ? error.message : "Failed to get tasks by tag",
|
|
749
|
+
code: "INTERNAL_ERROR"
|
|
678
750
|
};
|
|
679
751
|
}
|
|
680
752
|
}
|
|
753
|
+
/**
|
|
754
|
+
* Helper method to save project data
|
|
755
|
+
* @param projectId - The project ID
|
|
756
|
+
* @param projectData - The project data to save
|
|
757
|
+
*/
|
|
758
|
+
async saveProjectData(projectId, projectData) {
|
|
759
|
+
const filePath = this.storage.getFilePath(projectId);
|
|
760
|
+
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
761
|
+
await writeJsonFile2(filePath, projectData);
|
|
762
|
+
}
|
|
681
763
|
};
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
}
|
|
693
|
-
|
|
764
|
+
|
|
765
|
+
// src/services/task-service.ts
|
|
766
|
+
init_esm_shims();
|
|
767
|
+
init_file_helpers();
|
|
768
|
+
function generateTaskId() {
|
|
769
|
+
return `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
770
|
+
}
|
|
771
|
+
function calculateCompletedAt(currentStatus, newStatus, existingCompletedAt, now) {
|
|
772
|
+
if (!newStatus) {
|
|
773
|
+
return existingCompletedAt;
|
|
774
|
+
}
|
|
775
|
+
if (newStatus === "done" && currentStatus !== "done") {
|
|
776
|
+
return now;
|
|
777
|
+
}
|
|
778
|
+
if (currentStatus === "done" && newStatus !== "done") {
|
|
779
|
+
return null;
|
|
780
|
+
}
|
|
781
|
+
return existingCompletedAt;
|
|
782
|
+
}
|
|
783
|
+
var TaskService = {
|
|
784
|
+
/**
|
|
785
|
+
* Create a new task in a project
|
|
786
|
+
* @param projectId - The project ID
|
|
787
|
+
* @param data - Task creation data
|
|
788
|
+
* @returns The created task or error
|
|
789
|
+
*/
|
|
790
|
+
async create(projectId, data) {
|
|
694
791
|
try {
|
|
695
|
-
const projectData = await storage.readProject(
|
|
792
|
+
const projectData = await storage.readProject(projectId);
|
|
696
793
|
if (!projectData) {
|
|
697
794
|
return {
|
|
698
795
|
success: false,
|
|
699
|
-
error: `Project with ID '${
|
|
796
|
+
error: `Project with ID '${projectId}' not found`,
|
|
797
|
+
code: "NOT_FOUND"
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
801
|
+
const task = {
|
|
802
|
+
id: generateTaskId(),
|
|
803
|
+
projectId,
|
|
804
|
+
title: data.title,
|
|
805
|
+
description: data.description,
|
|
806
|
+
status: "todo",
|
|
807
|
+
priority: data.priority ?? "medium",
|
|
808
|
+
tags: data.tags ?? [],
|
|
809
|
+
dueDate: data.dueDate ?? null,
|
|
810
|
+
assignee: data.assignee ?? null,
|
|
811
|
+
createdAt: now,
|
|
812
|
+
updatedAt: now,
|
|
813
|
+
completedAt: null
|
|
814
|
+
};
|
|
815
|
+
projectData.tasks.push(task);
|
|
816
|
+
projectData.project.updatedAt = now;
|
|
817
|
+
const filePath = storage.getFilePath(projectId);
|
|
818
|
+
await writeJsonFile(filePath, projectData);
|
|
819
|
+
return {
|
|
820
|
+
success: true,
|
|
821
|
+
data: task
|
|
822
|
+
};
|
|
823
|
+
} catch (error) {
|
|
824
|
+
return {
|
|
825
|
+
success: false,
|
|
826
|
+
error: error instanceof Error ? error.message : "Failed to create task",
|
|
827
|
+
code: "INTERNAL_ERROR"
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
/**
|
|
832
|
+
* Get a specific task by project ID and task ID
|
|
833
|
+
* @param projectId - The project ID
|
|
834
|
+
* @param taskId - The task ID
|
|
835
|
+
* @returns The task or error
|
|
836
|
+
*/
|
|
837
|
+
async get(projectId, taskId) {
|
|
838
|
+
try {
|
|
839
|
+
const projectData = await storage.readProject(projectId);
|
|
840
|
+
if (!projectData) {
|
|
841
|
+
return {
|
|
842
|
+
success: false,
|
|
843
|
+
error: `Project with ID '${projectId}' not found`,
|
|
844
|
+
code: "NOT_FOUND"
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
const task = projectData.tasks.find((t) => t.id === taskId);
|
|
848
|
+
if (!task) {
|
|
849
|
+
return {
|
|
850
|
+
success: false,
|
|
851
|
+
error: `Task with ID '${taskId}' not found in project '${projectId}'`,
|
|
852
|
+
code: "NOT_FOUND"
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
return {
|
|
856
|
+
success: true,
|
|
857
|
+
data: task
|
|
858
|
+
};
|
|
859
|
+
} catch (error) {
|
|
860
|
+
return {
|
|
861
|
+
success: false,
|
|
862
|
+
error: error instanceof Error ? error.message : "Failed to get task",
|
|
863
|
+
code: "INTERNAL_ERROR"
|
|
864
|
+
};
|
|
865
|
+
}
|
|
866
|
+
},
|
|
867
|
+
/**
|
|
868
|
+
* Update an existing task
|
|
869
|
+
* Handles completedAt automatically based on status changes
|
|
870
|
+
* @param projectId - The project ID
|
|
871
|
+
* @param taskId - The task ID
|
|
872
|
+
* @param data - Task update data
|
|
873
|
+
* @returns The updated task or error
|
|
874
|
+
*/
|
|
875
|
+
async update(projectId, taskId, data) {
|
|
876
|
+
try {
|
|
877
|
+
const projectData = await storage.readProject(projectId);
|
|
878
|
+
if (!projectData) {
|
|
879
|
+
return {
|
|
880
|
+
success: false,
|
|
881
|
+
error: `Project with ID '${projectId}' not found`,
|
|
882
|
+
code: "NOT_FOUND"
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
|
|
886
|
+
if (taskIndex === -1) {
|
|
887
|
+
return {
|
|
888
|
+
success: false,
|
|
889
|
+
error: `Task with ID '${taskId}' not found in project '${projectId}'`,
|
|
890
|
+
code: "NOT_FOUND"
|
|
891
|
+
};
|
|
892
|
+
}
|
|
893
|
+
const updateKeys = Object.keys(data);
|
|
894
|
+
if (updateKeys.length === 0) {
|
|
895
|
+
return {
|
|
896
|
+
success: false,
|
|
897
|
+
error: "At least one field to update is required",
|
|
898
|
+
code: "VALIDATION_ERROR"
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
902
|
+
const existingTask = projectData.tasks[taskIndex];
|
|
903
|
+
const completedAt = calculateCompletedAt(
|
|
904
|
+
existingTask.status,
|
|
905
|
+
data.status,
|
|
906
|
+
existingTask.completedAt,
|
|
907
|
+
now
|
|
908
|
+
);
|
|
909
|
+
const updatedTask = {
|
|
910
|
+
...existingTask,
|
|
911
|
+
...data,
|
|
912
|
+
id: existingTask.id,
|
|
913
|
+
projectId: existingTask.projectId,
|
|
914
|
+
createdAt: existingTask.createdAt,
|
|
915
|
+
updatedAt: now,
|
|
916
|
+
completedAt
|
|
917
|
+
};
|
|
918
|
+
projectData.tasks[taskIndex] = updatedTask;
|
|
919
|
+
projectData.project.updatedAt = now;
|
|
920
|
+
const filePath = storage.getFilePath(projectId);
|
|
921
|
+
await writeJsonFile(filePath, projectData);
|
|
922
|
+
return {
|
|
923
|
+
success: true,
|
|
924
|
+
data: updatedTask
|
|
925
|
+
};
|
|
926
|
+
} catch (error) {
|
|
927
|
+
return {
|
|
928
|
+
success: false,
|
|
929
|
+
error: error instanceof Error ? error.message : "Failed to update task",
|
|
930
|
+
code: "INTERNAL_ERROR"
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
},
|
|
934
|
+
/**
|
|
935
|
+
* Delete a task by project ID and task ID
|
|
936
|
+
* @param projectId - The project ID
|
|
937
|
+
* @param taskId - The task ID
|
|
938
|
+
* @returns Void or error
|
|
939
|
+
*/
|
|
940
|
+
async delete(projectId, taskId) {
|
|
941
|
+
try {
|
|
942
|
+
const projectData = await storage.readProject(projectId);
|
|
943
|
+
if (!projectData) {
|
|
944
|
+
return {
|
|
945
|
+
success: false,
|
|
946
|
+
error: `Project with ID '${projectId}' not found`,
|
|
947
|
+
code: "NOT_FOUND"
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
|
|
951
|
+
if (taskIndex === -1) {
|
|
952
|
+
return {
|
|
953
|
+
success: false,
|
|
954
|
+
error: `Task with ID '${taskId}' not found in project '${projectId}'`,
|
|
955
|
+
code: "NOT_FOUND"
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
959
|
+
projectData.tasks.splice(taskIndex, 1);
|
|
960
|
+
projectData.project.updatedAt = now;
|
|
961
|
+
const filePath = storage.getFilePath(projectId);
|
|
962
|
+
await writeJsonFile(filePath, projectData);
|
|
963
|
+
return {
|
|
964
|
+
success: true,
|
|
965
|
+
data: void 0
|
|
966
|
+
};
|
|
967
|
+
} catch (error) {
|
|
968
|
+
return {
|
|
969
|
+
success: false,
|
|
970
|
+
error: error instanceof Error ? error.message : "Failed to delete task",
|
|
971
|
+
code: "INTERNAL_ERROR"
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
},
|
|
975
|
+
/**
|
|
976
|
+
* List tasks with optional filters
|
|
977
|
+
* @param filters - Optional filters for the search
|
|
978
|
+
* @returns Array of tasks or error
|
|
979
|
+
*/
|
|
980
|
+
async list(filters) {
|
|
981
|
+
try {
|
|
982
|
+
const results = await storage.searchTasks({
|
|
983
|
+
projectId: filters?.projectId,
|
|
984
|
+
status: filters?.status,
|
|
985
|
+
priority: filters?.priority,
|
|
986
|
+
tags: filters?.tags,
|
|
987
|
+
assignee: filters?.assignee,
|
|
988
|
+
dueBefore: filters?.dueBefore,
|
|
989
|
+
dueAfter: filters?.dueAfter,
|
|
990
|
+
includeCompleted: filters?.includeCompleted
|
|
991
|
+
});
|
|
992
|
+
const tasks = results.map((r) => r.task);
|
|
993
|
+
return {
|
|
994
|
+
success: true,
|
|
995
|
+
data: tasks
|
|
996
|
+
};
|
|
997
|
+
} catch (error) {
|
|
998
|
+
return {
|
|
999
|
+
success: false,
|
|
1000
|
+
error: error instanceof Error ? error.message : "Failed to list tasks",
|
|
1001
|
+
code: "INTERNAL_ERROR"
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
/**
|
|
1006
|
+
* Update multiple tasks at once
|
|
1007
|
+
* @param projectId - The project ID
|
|
1008
|
+
* @param taskIds - Array of task IDs to update
|
|
1009
|
+
* @param data - Batch update data
|
|
1010
|
+
* @returns Batch update result or error
|
|
1011
|
+
*/
|
|
1012
|
+
async batchUpdate(projectId, taskIds, data) {
|
|
1013
|
+
try {
|
|
1014
|
+
const projectData = await storage.readProject(projectId);
|
|
1015
|
+
if (!projectData) {
|
|
1016
|
+
return {
|
|
1017
|
+
success: false,
|
|
1018
|
+
error: `Project with ID '${projectId}' not found`,
|
|
1019
|
+
code: "NOT_FOUND"
|
|
700
1020
|
};
|
|
701
1021
|
}
|
|
702
1022
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
703
1023
|
const updatedTasks = [];
|
|
704
1024
|
const notFoundIds = [];
|
|
705
|
-
for (const taskId of
|
|
1025
|
+
for (const taskId of taskIds) {
|
|
706
1026
|
const taskIndex = projectData.tasks.findIndex((t) => t.id === taskId);
|
|
707
1027
|
if (taskIndex === -1) {
|
|
708
1028
|
notFoundIds.push(taskId);
|
|
@@ -710,29 +1030,34 @@ var batchUpdateTasksTool = {
|
|
|
710
1030
|
}
|
|
711
1031
|
const existingTask = projectData.tasks[taskIndex];
|
|
712
1032
|
let updatedTags = existingTask.tags;
|
|
713
|
-
if (
|
|
1033
|
+
if (data.tags && data.tags.length > 0) {
|
|
714
1034
|
const existingTags = existingTask.tags || [];
|
|
715
|
-
switch (
|
|
1035
|
+
switch (data.tagOperation) {
|
|
716
1036
|
case "add":
|
|
717
|
-
updatedTags = [.../* @__PURE__ */ new Set([...existingTags, ...
|
|
1037
|
+
updatedTags = [.../* @__PURE__ */ new Set([...existingTags, ...data.tags])];
|
|
718
1038
|
break;
|
|
719
1039
|
case "remove":
|
|
720
|
-
updatedTags = existingTags.filter((tag) => !
|
|
1040
|
+
updatedTags = existingTags.filter((tag) => !data.tags.includes(tag));
|
|
721
1041
|
break;
|
|
722
1042
|
case "replace":
|
|
723
1043
|
default:
|
|
724
|
-
updatedTags =
|
|
1044
|
+
updatedTags = data.tags;
|
|
725
1045
|
break;
|
|
726
1046
|
}
|
|
727
1047
|
}
|
|
1048
|
+
const completedAt = calculateCompletedAt(
|
|
1049
|
+
existingTask.status,
|
|
1050
|
+
data.status,
|
|
1051
|
+
existingTask.completedAt,
|
|
1052
|
+
now
|
|
1053
|
+
);
|
|
728
1054
|
const updatedTask = {
|
|
729
1055
|
...existingTask,
|
|
730
|
-
...
|
|
731
|
-
...
|
|
1056
|
+
...data.status && { status: data.status },
|
|
1057
|
+
...data.priority && { priority: data.priority },
|
|
732
1058
|
tags: updatedTags,
|
|
733
1059
|
updatedAt: now,
|
|
734
|
-
|
|
735
|
-
...input.status && input.status !== "done" && { completedAt: null }
|
|
1060
|
+
completedAt
|
|
736
1061
|
};
|
|
737
1062
|
projectData.tasks[taskIndex] = updatedTask;
|
|
738
1063
|
updatedTasks.push(updatedTask);
|
|
@@ -741,13 +1066,12 @@ var batchUpdateTasksTool = {
|
|
|
741
1066
|
return {
|
|
742
1067
|
success: false,
|
|
743
1068
|
error: "No tasks were found to update",
|
|
744
|
-
|
|
1069
|
+
code: "NOT_FOUND"
|
|
745
1070
|
};
|
|
746
1071
|
}
|
|
747
1072
|
projectData.project.updatedAt = now;
|
|
748
|
-
const filePath = storage.getFilePath(
|
|
749
|
-
|
|
750
|
-
await writeJsonFile2(filePath, projectData);
|
|
1073
|
+
const filePath = storage.getFilePath(projectId);
|
|
1074
|
+
await writeJsonFile(filePath, projectData);
|
|
751
1075
|
return {
|
|
752
1076
|
success: true,
|
|
753
1077
|
data: {
|
|
@@ -759,18 +1083,155 @@ var batchUpdateTasksTool = {
|
|
|
759
1083
|
} catch (error) {
|
|
760
1084
|
return {
|
|
761
1085
|
success: false,
|
|
762
|
-
error: error instanceof Error ? error.message : "Failed to batch update tasks"
|
|
1086
|
+
error: error instanceof Error ? error.message : "Failed to batch update tasks",
|
|
1087
|
+
code: "INTERNAL_ERROR"
|
|
763
1088
|
};
|
|
764
1089
|
}
|
|
765
1090
|
}
|
|
766
1091
|
};
|
|
767
1092
|
|
|
1093
|
+
// src/services/types.ts
|
|
1094
|
+
init_esm_shims();
|
|
1095
|
+
|
|
1096
|
+
// src/tools/task-tools.ts
|
|
1097
|
+
var TaskStatusEnum = z2.enum(["todo", "in-progress", "review", "done"]);
|
|
1098
|
+
var TaskPriorityEnum = z2.enum(["low", "medium", "high", "critical"]);
|
|
1099
|
+
var createTaskTool = {
|
|
1100
|
+
name: "create_task",
|
|
1101
|
+
description: "Create a new task in a project",
|
|
1102
|
+
inputSchema: z2.object({
|
|
1103
|
+
projectId: z2.string().min(1, "Project ID is required"),
|
|
1104
|
+
title: z2.string().min(1, "Task title is required"),
|
|
1105
|
+
description: z2.string(),
|
|
1106
|
+
priority: TaskPriorityEnum.default("medium"),
|
|
1107
|
+
tags: z2.array(z2.string()).default([]),
|
|
1108
|
+
dueDate: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
1109
|
+
assignee: z2.string().optional()
|
|
1110
|
+
}),
|
|
1111
|
+
async execute(input) {
|
|
1112
|
+
const result = await TaskService.create(input.projectId, {
|
|
1113
|
+
title: input.title,
|
|
1114
|
+
description: input.description,
|
|
1115
|
+
priority: input.priority,
|
|
1116
|
+
tags: input.tags,
|
|
1117
|
+
dueDate: input.dueDate,
|
|
1118
|
+
assignee: input.assignee
|
|
1119
|
+
});
|
|
1120
|
+
return result;
|
|
1121
|
+
}
|
|
1122
|
+
};
|
|
1123
|
+
var listTasksTool = {
|
|
1124
|
+
name: "list_tasks",
|
|
1125
|
+
description: "List tasks with optional filters",
|
|
1126
|
+
inputSchema: z2.object({
|
|
1127
|
+
projectId: z2.string().optional(),
|
|
1128
|
+
status: TaskStatusEnum.optional(),
|
|
1129
|
+
priority: TaskPriorityEnum.optional(),
|
|
1130
|
+
tags: z2.array(z2.string()).optional(),
|
|
1131
|
+
assignee: z2.string().optional(),
|
|
1132
|
+
dueBefore: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
1133
|
+
dueAfter: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(),
|
|
1134
|
+
includeCompleted: z2.boolean().optional()
|
|
1135
|
+
}),
|
|
1136
|
+
async execute(input) {
|
|
1137
|
+
try {
|
|
1138
|
+
const results = await storage.searchTasks({
|
|
1139
|
+
projectId: input.projectId,
|
|
1140
|
+
status: input.status,
|
|
1141
|
+
priority: input.priority,
|
|
1142
|
+
tags: input.tags,
|
|
1143
|
+
assignee: input.assignee,
|
|
1144
|
+
dueBefore: input.dueBefore,
|
|
1145
|
+
dueAfter: input.dueAfter,
|
|
1146
|
+
includeCompleted: input.includeCompleted
|
|
1147
|
+
});
|
|
1148
|
+
return {
|
|
1149
|
+
success: true,
|
|
1150
|
+
data: results
|
|
1151
|
+
};
|
|
1152
|
+
} catch (error) {
|
|
1153
|
+
return {
|
|
1154
|
+
success: false,
|
|
1155
|
+
error: error instanceof Error ? error.message : "Failed to list tasks"
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
var getTaskTool = {
|
|
1161
|
+
name: "get_task",
|
|
1162
|
+
description: "Get a specific task by project ID and task ID",
|
|
1163
|
+
inputSchema: z2.object({
|
|
1164
|
+
projectId: z2.string().min(1, "Project ID is required"),
|
|
1165
|
+
taskId: z2.string().min(1, "Task ID is required")
|
|
1166
|
+
}),
|
|
1167
|
+
async execute(input) {
|
|
1168
|
+
const result = await TaskService.get(input.projectId, input.taskId);
|
|
1169
|
+
return result;
|
|
1170
|
+
}
|
|
1171
|
+
};
|
|
1172
|
+
var updateTaskTool = {
|
|
1173
|
+
name: "update_task",
|
|
1174
|
+
description: "Update an existing task",
|
|
1175
|
+
inputSchema: z2.object({
|
|
1176
|
+
projectId: z2.string().min(1, "Project ID is required"),
|
|
1177
|
+
taskId: z2.string().min(1, "Task ID is required"),
|
|
1178
|
+
title: z2.string().min(1).optional(),
|
|
1179
|
+
description: z2.string().optional(),
|
|
1180
|
+
status: TaskStatusEnum.optional(),
|
|
1181
|
+
priority: TaskPriorityEnum.optional(),
|
|
1182
|
+
tags: z2.array(z2.string()).optional(),
|
|
1183
|
+
dueDate: z2.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional().nullable(),
|
|
1184
|
+
assignee: z2.string().optional().nullable()
|
|
1185
|
+
}),
|
|
1186
|
+
async execute(input) {
|
|
1187
|
+
const { projectId, taskId, ...updateData } = input;
|
|
1188
|
+
const result = await TaskService.update(projectId, taskId, updateData);
|
|
1189
|
+
return result;
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
var deleteTaskTool = {
|
|
1193
|
+
name: "delete_task",
|
|
1194
|
+
description: "Delete a task by project ID and task ID",
|
|
1195
|
+
inputSchema: z2.object({
|
|
1196
|
+
projectId: z2.string().min(1, "Project ID is required"),
|
|
1197
|
+
taskId: z2.string().min(1, "Task ID is required")
|
|
1198
|
+
}),
|
|
1199
|
+
async execute(input) {
|
|
1200
|
+
const result = await TaskService.delete(input.projectId, input.taskId);
|
|
1201
|
+
if (result.success) {
|
|
1202
|
+
return {
|
|
1203
|
+
success: true,
|
|
1204
|
+
data: { deleted: true }
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
return result;
|
|
1208
|
+
}
|
|
1209
|
+
};
|
|
1210
|
+
var batchUpdateTasksTool = {
|
|
1211
|
+
name: "batch_update_tasks",
|
|
1212
|
+
description: "Update multiple tasks at once",
|
|
1213
|
+
inputSchema: z2.object({
|
|
1214
|
+
projectId: z2.string().min(1, "Project ID is required"),
|
|
1215
|
+
taskIds: z2.array(z2.string()).min(1, "At least one task ID is required"),
|
|
1216
|
+
status: TaskStatusEnum.optional(),
|
|
1217
|
+
priority: TaskPriorityEnum.optional(),
|
|
1218
|
+
tags: z2.array(z2.string()).optional(),
|
|
1219
|
+
tagOperation: z2.enum(["add", "remove", "replace"]).default("replace")
|
|
1220
|
+
}),
|
|
1221
|
+
async execute(input) {
|
|
1222
|
+
const { projectId, taskIds, tagOperation, ...restData } = input;
|
|
1223
|
+
const result = await TaskService.batchUpdate(projectId, taskIds, {
|
|
1224
|
+
...restData,
|
|
1225
|
+
tagOperation
|
|
1226
|
+
});
|
|
1227
|
+
return result;
|
|
1228
|
+
}
|
|
1229
|
+
};
|
|
1230
|
+
|
|
768
1231
|
// src/tools/tag-tools.ts
|
|
769
1232
|
init_esm_shims();
|
|
770
1233
|
import { z as z3 } from "zod";
|
|
771
|
-
|
|
772
|
-
return `tag_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
773
|
-
}
|
|
1234
|
+
var tagService = new TagService(storage);
|
|
774
1235
|
var createTagTool = {
|
|
775
1236
|
name: "create_tag",
|
|
776
1237
|
description: "Create a new tag in a project",
|
|
@@ -781,46 +1242,12 @@ var createTagTool = {
|
|
|
781
1242
|
description: z3.string().default("")
|
|
782
1243
|
}),
|
|
783
1244
|
async execute(input) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
success: false,
|
|
789
|
-
error: `Project with ID '${input.projectId}' not found`
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
const existingTag = projectData.tags.find(
|
|
793
|
-
(t) => t.name.toLowerCase() === input.name.toLowerCase()
|
|
794
|
-
);
|
|
795
|
-
if (existingTag) {
|
|
796
|
-
return {
|
|
797
|
-
success: false,
|
|
798
|
-
error: `Tag with name '${input.name}' already exists in this project`
|
|
799
|
-
};
|
|
800
|
-
}
|
|
801
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
802
|
-
const tag = {
|
|
803
|
-
id: generateTagId(),
|
|
804
|
-
name: input.name,
|
|
805
|
-
color: input.color,
|
|
806
|
-
description: input.description,
|
|
807
|
-
createdAt: now
|
|
808
|
-
};
|
|
809
|
-
projectData.tags.push(tag);
|
|
810
|
-
projectData.project.updatedAt = now;
|
|
811
|
-
const filePath = storage.getFilePath(input.projectId);
|
|
812
|
-
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
813
|
-
await writeJsonFile2(filePath, projectData);
|
|
814
|
-
return {
|
|
815
|
-
success: true,
|
|
816
|
-
data: tag
|
|
817
|
-
};
|
|
818
|
-
} catch (error) {
|
|
819
|
-
return {
|
|
820
|
-
success: false,
|
|
821
|
-
error: error instanceof Error ? error.message : "Failed to create tag"
|
|
822
|
-
};
|
|
1245
|
+
const { projectId, ...data } = input;
|
|
1246
|
+
const result = await tagService.create(projectId, data);
|
|
1247
|
+
if (!result.success) {
|
|
1248
|
+
return { success: false, error: result.error };
|
|
823
1249
|
}
|
|
1250
|
+
return { success: true, data: result.data };
|
|
824
1251
|
}
|
|
825
1252
|
};
|
|
826
1253
|
var listTagsTool = {
|
|
@@ -830,24 +1257,11 @@ var listTagsTool = {
|
|
|
830
1257
|
projectId: z3.string().min(1, "Project ID is required")
|
|
831
1258
|
}),
|
|
832
1259
|
async execute(input) {
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
return {
|
|
837
|
-
success: false,
|
|
838
|
-
error: `Project with ID '${input.projectId}' not found`
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
return {
|
|
842
|
-
success: true,
|
|
843
|
-
data: projectData.tags
|
|
844
|
-
};
|
|
845
|
-
} catch (error) {
|
|
846
|
-
return {
|
|
847
|
-
success: false,
|
|
848
|
-
error: error instanceof Error ? error.message : "Failed to list tags"
|
|
849
|
-
};
|
|
1260
|
+
const result = await tagService.list(input.projectId);
|
|
1261
|
+
if (!result.success) {
|
|
1262
|
+
return { success: false, error: result.error };
|
|
850
1263
|
}
|
|
1264
|
+
return { success: true, data: result.data };
|
|
851
1265
|
}
|
|
852
1266
|
};
|
|
853
1267
|
var updateTagTool = {
|
|
@@ -861,62 +1275,12 @@ var updateTagTool = {
|
|
|
861
1275
|
description: z3.string().optional()
|
|
862
1276
|
}),
|
|
863
1277
|
async execute(input) {
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
success: false,
|
|
869
|
-
error: `Project with ID '${input.projectId}' not found`
|
|
870
|
-
};
|
|
871
|
-
}
|
|
872
|
-
const tagIndex = projectData.tags.findIndex((t) => t.id === input.tagId);
|
|
873
|
-
if (tagIndex === -1) {
|
|
874
|
-
return {
|
|
875
|
-
success: false,
|
|
876
|
-
error: `Tag with ID '${input.tagId}' not found in project '${input.projectId}'`
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
const { projectId, tagId, ...updateData } = input;
|
|
880
|
-
if (Object.keys(updateData).length === 0) {
|
|
881
|
-
return {
|
|
882
|
-
success: false,
|
|
883
|
-
error: "At least one field to update is required"
|
|
884
|
-
};
|
|
885
|
-
}
|
|
886
|
-
if (updateData.name) {
|
|
887
|
-
const existingTag2 = projectData.tags.find(
|
|
888
|
-
(t) => t.name.toLowerCase() === updateData.name.toLowerCase() && t.id !== input.tagId
|
|
889
|
-
);
|
|
890
|
-
if (existingTag2) {
|
|
891
|
-
return {
|
|
892
|
-
success: false,
|
|
893
|
-
error: `Tag with name '${updateData.name}' already exists in this project`
|
|
894
|
-
};
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
898
|
-
const existingTag = projectData.tags[tagIndex];
|
|
899
|
-
const updatedTag = {
|
|
900
|
-
...existingTag,
|
|
901
|
-
...updateData,
|
|
902
|
-
id: existingTag.id,
|
|
903
|
-
createdAt: existingTag.createdAt
|
|
904
|
-
};
|
|
905
|
-
projectData.tags[tagIndex] = updatedTag;
|
|
906
|
-
projectData.project.updatedAt = now;
|
|
907
|
-
const filePath = storage.getFilePath(input.projectId);
|
|
908
|
-
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
909
|
-
await writeJsonFile2(filePath, projectData);
|
|
910
|
-
return {
|
|
911
|
-
success: true,
|
|
912
|
-
data: updatedTag
|
|
913
|
-
};
|
|
914
|
-
} catch (error) {
|
|
915
|
-
return {
|
|
916
|
-
success: false,
|
|
917
|
-
error: error instanceof Error ? error.message : "Failed to update tag"
|
|
918
|
-
};
|
|
1278
|
+
const { projectId, tagId, ...data } = input;
|
|
1279
|
+
const result = await tagService.update(projectId, tagId, data);
|
|
1280
|
+
if (!result.success) {
|
|
1281
|
+
return { success: false, error: result.error };
|
|
919
1282
|
}
|
|
1283
|
+
return { success: true, data: result.data };
|
|
920
1284
|
}
|
|
921
1285
|
};
|
|
922
1286
|
var deleteTagTool = {
|
|
@@ -927,51 +1291,11 @@ var deleteTagTool = {
|
|
|
927
1291
|
tagId: z3.string().min(1, "Tag ID is required")
|
|
928
1292
|
}),
|
|
929
1293
|
async execute(input) {
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
return {
|
|
934
|
-
success: false,
|
|
935
|
-
error: `Project with ID '${input.projectId}' not found`
|
|
936
|
-
};
|
|
937
|
-
}
|
|
938
|
-
const tagIndex = projectData.tags.findIndex((t) => t.id === input.tagId);
|
|
939
|
-
if (tagIndex === -1) {
|
|
940
|
-
return {
|
|
941
|
-
success: false,
|
|
942
|
-
error: `Tag with ID '${input.tagId}' not found in project '${input.projectId}'`
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
const tag = projectData.tags[tagIndex];
|
|
946
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
947
|
-
let tasksUpdated = 0;
|
|
948
|
-
for (const task of projectData.tasks) {
|
|
949
|
-
const tagIndexInTask = task.tags.indexOf(input.tagId);
|
|
950
|
-
if (tagIndexInTask !== -1) {
|
|
951
|
-
task.tags.splice(tagIndexInTask, 1);
|
|
952
|
-
task.updatedAt = now;
|
|
953
|
-
tasksUpdated++;
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
projectData.tags.splice(tagIndex, 1);
|
|
957
|
-
projectData.project.updatedAt = now;
|
|
958
|
-
const filePath = storage.getFilePath(input.projectId);
|
|
959
|
-
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
960
|
-
await writeJsonFile2(filePath, projectData);
|
|
961
|
-
return {
|
|
962
|
-
success: true,
|
|
963
|
-
data: {
|
|
964
|
-
deleted: true,
|
|
965
|
-
tag,
|
|
966
|
-
tasksUpdated
|
|
967
|
-
}
|
|
968
|
-
};
|
|
969
|
-
} catch (error) {
|
|
970
|
-
return {
|
|
971
|
-
success: false,
|
|
972
|
-
error: error instanceof Error ? error.message : "Failed to delete tag"
|
|
973
|
-
};
|
|
1294
|
+
const result = await tagService.delete(input.projectId, input.tagId);
|
|
1295
|
+
if (!result.success) {
|
|
1296
|
+
return { success: false, error: result.error };
|
|
974
1297
|
}
|
|
1298
|
+
return { success: true, data: result.data };
|
|
975
1299
|
}
|
|
976
1300
|
};
|
|
977
1301
|
var getTasksByTagTool = {
|
|
@@ -982,38 +1306,11 @@ var getTasksByTagTool = {
|
|
|
982
1306
|
tagName: z3.string().min(1, "Tag name is required")
|
|
983
1307
|
}),
|
|
984
1308
|
async execute(input) {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
return {
|
|
989
|
-
success: false,
|
|
990
|
-
error: `Project with ID '${input.projectId}' not found`
|
|
991
|
-
};
|
|
992
|
-
}
|
|
993
|
-
const tag = projectData.tags.find(
|
|
994
|
-
(t) => t.name.toLowerCase() === input.tagName.toLowerCase()
|
|
995
|
-
);
|
|
996
|
-
if (!tag) {
|
|
997
|
-
return {
|
|
998
|
-
success: false,
|
|
999
|
-
error: `Tag with name '${input.tagName}' not found in project '${input.projectId}'`
|
|
1000
|
-
};
|
|
1001
|
-
}
|
|
1002
|
-
const tasks = projectData.tasks.filter((t) => t.tags.includes(tag.id));
|
|
1003
|
-
return {
|
|
1004
|
-
success: true,
|
|
1005
|
-
data: {
|
|
1006
|
-
tag,
|
|
1007
|
-
tasks,
|
|
1008
|
-
count: tasks.length
|
|
1009
|
-
}
|
|
1010
|
-
};
|
|
1011
|
-
} catch (error) {
|
|
1012
|
-
return {
|
|
1013
|
-
success: false,
|
|
1014
|
-
error: error instanceof Error ? error.message : "Failed to get tasks by tag"
|
|
1015
|
-
};
|
|
1309
|
+
const result = await tagService.getTasksByTag(input.projectId, input.tagName);
|
|
1310
|
+
if (!result.success) {
|
|
1311
|
+
return { success: false, error: result.error };
|
|
1016
1312
|
}
|
|
1313
|
+
return { success: true, data: result.data };
|
|
1017
1314
|
}
|
|
1018
1315
|
};
|
|
1019
1316
|
|
|
@@ -1024,6 +1321,10 @@ init_esm_shims();
|
|
|
1024
1321
|
init_esm_shims();
|
|
1025
1322
|
import express from "express";
|
|
1026
1323
|
import * as path4 from "path";
|
|
1324
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1325
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
1326
|
+
var __dirname2 = path4.dirname(__filename2);
|
|
1327
|
+
var tagService2 = new TagService(storage);
|
|
1027
1328
|
function createServer(port = 7860) {
|
|
1028
1329
|
return new Promise((resolve, reject) => {
|
|
1029
1330
|
const app = express();
|
|
@@ -1089,31 +1390,13 @@ function createServer(port = 7860) {
|
|
|
1089
1390
|
app.post("/api/tasks", async (req, res) => {
|
|
1090
1391
|
try {
|
|
1091
1392
|
const { projectId, ...taskData } = req.body;
|
|
1092
|
-
const
|
|
1093
|
-
if (!
|
|
1094
|
-
|
|
1393
|
+
const result = await TaskService.create(projectId, taskData);
|
|
1394
|
+
if (!result.success) {
|
|
1395
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1396
|
+
res.status(statusCode).json({ error: result.error });
|
|
1095
1397
|
return;
|
|
1096
1398
|
}
|
|
1097
|
-
|
|
1098
|
-
const task = {
|
|
1099
|
-
id: `task_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
1100
|
-
projectId,
|
|
1101
|
-
...taskData,
|
|
1102
|
-
status: taskData.status || "todo",
|
|
1103
|
-
priority: taskData.priority || "medium",
|
|
1104
|
-
tags: taskData.tags || [],
|
|
1105
|
-
dueDate: taskData.dueDate || null,
|
|
1106
|
-
assignee: taskData.assignee || null,
|
|
1107
|
-
createdAt: now,
|
|
1108
|
-
updatedAt: now,
|
|
1109
|
-
completedAt: null
|
|
1110
|
-
};
|
|
1111
|
-
projectData.tasks.push(task);
|
|
1112
|
-
projectData.project.updatedAt = now;
|
|
1113
|
-
const filePath = storage.getFilePath(projectId);
|
|
1114
|
-
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
1115
|
-
await writeJsonFile2(filePath, projectData);
|
|
1116
|
-
res.json({ success: true, data: task });
|
|
1399
|
+
res.json({ success: true, data: result.data });
|
|
1117
1400
|
} catch (error) {
|
|
1118
1401
|
res.status(500).json({ error: error.message });
|
|
1119
1402
|
}
|
|
@@ -1121,33 +1404,13 @@ function createServer(port = 7860) {
|
|
|
1121
1404
|
app.put("/api/tasks", async (req, res) => {
|
|
1122
1405
|
try {
|
|
1123
1406
|
const { projectId, taskId, ...updateData } = req.body;
|
|
1124
|
-
const
|
|
1125
|
-
if (!
|
|
1126
|
-
|
|
1407
|
+
const result = await TaskService.update(projectId, taskId, updateData);
|
|
1408
|
+
if (!result.success) {
|
|
1409
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1410
|
+
res.status(statusCode).json({ error: result.error });
|
|
1127
1411
|
return;
|
|
1128
1412
|
}
|
|
1129
|
-
|
|
1130
|
-
if (taskIndex === -1) {
|
|
1131
|
-
res.status(404).json({ error: "Task not found" });
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1135
|
-
const existingTask = projectData.tasks[taskIndex];
|
|
1136
|
-
const updatedTask = {
|
|
1137
|
-
...existingTask,
|
|
1138
|
-
...updateData,
|
|
1139
|
-
id: existingTask.id,
|
|
1140
|
-
projectId: existingTask.projectId,
|
|
1141
|
-
createdAt: existingTask.createdAt,
|
|
1142
|
-
updatedAt: now,
|
|
1143
|
-
completedAt: updateData.status === "done" && existingTask.status !== "done" ? now : updateData.status && updateData.status !== "done" ? null : existingTask.completedAt
|
|
1144
|
-
};
|
|
1145
|
-
projectData.tasks[taskIndex] = updatedTask;
|
|
1146
|
-
projectData.project.updatedAt = now;
|
|
1147
|
-
const filePath = storage.getFilePath(projectId);
|
|
1148
|
-
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
1149
|
-
await writeJsonFile2(filePath, projectData);
|
|
1150
|
-
res.json({ success: true, data: updatedTask });
|
|
1413
|
+
res.json({ success: true, data: result.data });
|
|
1151
1414
|
} catch (error) {
|
|
1152
1415
|
res.status(500).json({ error: error.message });
|
|
1153
1416
|
}
|
|
@@ -1155,27 +1418,96 @@ function createServer(port = 7860) {
|
|
|
1155
1418
|
app.delete("/api/tasks", async (req, res) => {
|
|
1156
1419
|
try {
|
|
1157
1420
|
const { projectId, taskId } = req.query;
|
|
1158
|
-
const
|
|
1159
|
-
if (!
|
|
1160
|
-
|
|
1421
|
+
const result = await TaskService.delete(projectId, taskId);
|
|
1422
|
+
if (!result.success) {
|
|
1423
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1424
|
+
res.status(statusCode).json({ error: result.error });
|
|
1161
1425
|
return;
|
|
1162
1426
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1427
|
+
res.json({ success: true });
|
|
1428
|
+
} catch (error) {
|
|
1429
|
+
res.status(500).json({ error: error.message });
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
app.post("/api/projects/:projectId/tags", async (req, res) => {
|
|
1433
|
+
try {
|
|
1434
|
+
const { projectId } = req.params;
|
|
1435
|
+
const result = await tagService2.create(projectId, req.body);
|
|
1436
|
+
if (!result.success) {
|
|
1437
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1438
|
+
res.status(statusCode).json({ error: result.error });
|
|
1166
1439
|
return;
|
|
1167
1440
|
}
|
|
1168
|
-
|
|
1169
|
-
projectData.project.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1170
|
-
const filePath = storage.getFilePath(projectId);
|
|
1171
|
-
const { writeJsonFile: writeJsonFile2 } = await Promise.resolve().then(() => (init_file_helpers(), file_helpers_exports));
|
|
1172
|
-
await writeJsonFile2(filePath, projectData);
|
|
1173
|
-
res.json({ success: true });
|
|
1441
|
+
res.json({ success: true, data: result.data });
|
|
1174
1442
|
} catch (error) {
|
|
1175
1443
|
res.status(500).json({ error: error.message });
|
|
1176
1444
|
}
|
|
1177
1445
|
});
|
|
1178
|
-
|
|
1446
|
+
app.get("/api/projects/:projectId/tags", async (req, res) => {
|
|
1447
|
+
try {
|
|
1448
|
+
const { projectId } = req.params;
|
|
1449
|
+
const result = await tagService2.list(projectId);
|
|
1450
|
+
if (!result.success) {
|
|
1451
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1452
|
+
res.status(statusCode).json({ error: result.error });
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
res.json({ success: true, data: result.data });
|
|
1456
|
+
} catch (error) {
|
|
1457
|
+
res.status(500).json({ error: error.message });
|
|
1458
|
+
}
|
|
1459
|
+
});
|
|
1460
|
+
app.put("/api/projects/:projectId/tags/:tagId", async (req, res) => {
|
|
1461
|
+
try {
|
|
1462
|
+
const { projectId, tagId } = req.params;
|
|
1463
|
+
const result = await tagService2.update(projectId, tagId, req.body);
|
|
1464
|
+
if (!result.success) {
|
|
1465
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1466
|
+
res.status(statusCode).json({ error: result.error });
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
res.json({ success: true, data: result.data });
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
res.status(500).json({ error: error.message });
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
app.delete("/api/projects/:projectId/tags/:tagId", async (req, res) => {
|
|
1475
|
+
try {
|
|
1476
|
+
const { projectId, tagId } = req.params;
|
|
1477
|
+
const result = await tagService2.delete(projectId, tagId);
|
|
1478
|
+
if (!result.success) {
|
|
1479
|
+
const statusCode = result.code === "NOT_FOUND" ? 404 : 400;
|
|
1480
|
+
res.status(statusCode).json({ error: result.error });
|
|
1481
|
+
return;
|
|
1482
|
+
}
|
|
1483
|
+
res.json({ success: true, data: result.data });
|
|
1484
|
+
} catch (error) {
|
|
1485
|
+
res.status(500).json({ error: error.message });
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
app.get("/api/backup", async (_req, res) => {
|
|
1489
|
+
try {
|
|
1490
|
+
const backup = await storage.exportAllData();
|
|
1491
|
+
const filename = `roadmap-skill-backup-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.json`;
|
|
1492
|
+
res.setHeader("Content-Type", "application/json");
|
|
1493
|
+
res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
|
|
1494
|
+
res.json(backup);
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
res.status(500).json({ error: error.message });
|
|
1497
|
+
}
|
|
1498
|
+
});
|
|
1499
|
+
app.post("/api/backup", async (req, res) => {
|
|
1500
|
+
try {
|
|
1501
|
+
const result = await storage.importAllData(req.body);
|
|
1502
|
+
res.json(result);
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
res.status(400).json({
|
|
1505
|
+
success: false,
|
|
1506
|
+
error: error.message
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
});
|
|
1510
|
+
const distPath = path4.join(__dirname2, "app");
|
|
1179
1511
|
app.use(express.static(distPath));
|
|
1180
1512
|
app.get("*", (req, res) => {
|
|
1181
1513
|
if (req.path.startsWith("/api")) {
|
|
@@ -1198,6 +1530,13 @@ function createServer(port = 7860) {
|
|
|
1198
1530
|
});
|
|
1199
1531
|
});
|
|
1200
1532
|
}
|
|
1533
|
+
if (process.argv[1]?.endsWith("server.js")) {
|
|
1534
|
+
const port = parseInt(process.argv[2] || "7860", 10);
|
|
1535
|
+
createServer(port).catch((err) => {
|
|
1536
|
+
console.error("Failed to start server:", err);
|
|
1537
|
+
process.exit(1);
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1201
1540
|
|
|
1202
1541
|
// src/tools/web-tools.ts
|
|
1203
1542
|
import open from "open";
|