@fink-andreas/pi-linear-tools 0.4.2 → 0.4.3
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/CHANGELOG.md +16 -0
- package/README.md +131 -3
- package/extensions/pi-linear-tools.js +180 -10
- package/index.js +1 -1019
- package/package.json +2 -2
- package/src/cli.js +700 -6
- package/src/handlers.js +403 -0
- package/src/linear.js +1218 -9
- package/src/sync-doc.js +1208 -0
package/src/handlers.js
CHANGED
|
@@ -13,18 +13,32 @@ import {
|
|
|
13
13
|
updateIssue,
|
|
14
14
|
createIssue,
|
|
15
15
|
fetchProjects,
|
|
16
|
+
fetchProjectDetails,
|
|
16
17
|
fetchTeams,
|
|
17
18
|
resolveProjectRef,
|
|
18
19
|
resolveTeamRef,
|
|
19
20
|
getTeamWorkflowStates,
|
|
20
21
|
fetchIssueDetails,
|
|
22
|
+
fetchIssueActivity,
|
|
21
23
|
formatIssueAsMarkdown,
|
|
24
|
+
formatIssueActivityAsMarkdown,
|
|
22
25
|
fetchIssuesByProject,
|
|
23
26
|
fetchProjectMilestones,
|
|
24
27
|
fetchMilestoneDetails,
|
|
25
28
|
createProjectMilestone,
|
|
26
29
|
updateProjectMilestone,
|
|
27
30
|
deleteProjectMilestone,
|
|
31
|
+
createProject,
|
|
32
|
+
updateProject,
|
|
33
|
+
deleteProject,
|
|
34
|
+
archiveProject,
|
|
35
|
+
unarchiveProject,
|
|
36
|
+
fetchProjectUpdates,
|
|
37
|
+
fetchProjectUpdateDetails,
|
|
38
|
+
createProjectUpdate,
|
|
39
|
+
updateProjectUpdate,
|
|
40
|
+
archiveProjectUpdate,
|
|
41
|
+
unarchiveProjectUpdate,
|
|
28
42
|
deleteIssue,
|
|
29
43
|
withHandlerErrorHandling,
|
|
30
44
|
getViewer,
|
|
@@ -44,6 +58,17 @@ function ensureNonEmpty(value, fieldName) {
|
|
|
44
58
|
return text;
|
|
45
59
|
}
|
|
46
60
|
|
|
61
|
+
function parseRefList(value) {
|
|
62
|
+
if (value === undefined || value === null) return [];
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
return value.map((item) => String(item || '').trim()).filter(Boolean);
|
|
65
|
+
}
|
|
66
|
+
return String(value)
|
|
67
|
+
.split(',')
|
|
68
|
+
.map((item) => item.trim())
|
|
69
|
+
.filter(Boolean);
|
|
70
|
+
}
|
|
71
|
+
|
|
47
72
|
// ===== GIT OPERATIONS (for issue start) =====
|
|
48
73
|
|
|
49
74
|
/**
|
|
@@ -226,6 +251,28 @@ export async function executeIssueView(client, params) {
|
|
|
226
251
|
};
|
|
227
252
|
}
|
|
228
253
|
|
|
254
|
+
export async function executeIssueActivity(client, params) {
|
|
255
|
+
const issue = ensureNonEmpty(params.issue, 'issue');
|
|
256
|
+
const activityData = await fetchIssueActivity(client, issue, {
|
|
257
|
+
limit: params.limit || 25,
|
|
258
|
+
includeArchived: params.includeArchived === true,
|
|
259
|
+
});
|
|
260
|
+
const markdown = formatIssueActivityAsMarkdown(activityData, {
|
|
261
|
+
limit: params.limit,
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
content: [{ type: 'text', text: markdown }],
|
|
266
|
+
details: {
|
|
267
|
+
issueId: activityData.issue.id,
|
|
268
|
+
identifier: activityData.issue.identifier,
|
|
269
|
+
title: activityData.issue.title,
|
|
270
|
+
activityCount: activityData.activity.length,
|
|
271
|
+
url: activityData.issue.url,
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
229
276
|
/**
|
|
230
277
|
* Create a new issue
|
|
231
278
|
*/
|
|
@@ -551,6 +598,362 @@ export async function executeProjectList(client) {
|
|
|
551
598
|
});
|
|
552
599
|
}
|
|
553
600
|
|
|
601
|
+
export async function executeProjectView(client, params) {
|
|
602
|
+
return withHandlerErrorHandling(async () => {
|
|
603
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
604
|
+
const project = await fetchProjectDetails(client, projectRef);
|
|
605
|
+
|
|
606
|
+
const lines = [`# Project: ${project.name}`];
|
|
607
|
+
const metaParts = [];
|
|
608
|
+
|
|
609
|
+
if (project.status?.name) metaParts.push(`**Status:** ${project.status.name}`);
|
|
610
|
+
if (project.health) metaParts.push(`**Health:** ${project.health}`);
|
|
611
|
+
if (project.progress !== undefined && project.progress !== null) metaParts.push(`**Progress:** ${project.progress}%`);
|
|
612
|
+
if (project.priority !== undefined && project.priority !== null) metaParts.push(`**Priority:** ${project.priority}`);
|
|
613
|
+
if (project.startDate) metaParts.push(`**Start:** ${project.startDate}`);
|
|
614
|
+
if (project.targetDate) metaParts.push(`**Target:** ${project.targetDate}`);
|
|
615
|
+
|
|
616
|
+
if (metaParts.length > 0) {
|
|
617
|
+
lines.push('');
|
|
618
|
+
lines.push(metaParts.join(' | '));
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const teamLabel = project.teams.length > 0
|
|
622
|
+
? project.teams.map((team) => `\`${team.key}\``).join(', ')
|
|
623
|
+
: 'None';
|
|
624
|
+
const leadLabel = project.lead?.displayName || project.lead?.name || 'Unassigned';
|
|
625
|
+
|
|
626
|
+
lines.push('');
|
|
627
|
+
lines.push(`**Teams:** ${teamLabel}`);
|
|
628
|
+
lines.push(`**Lead:** ${leadLabel}`);
|
|
629
|
+
|
|
630
|
+
if (project.url) {
|
|
631
|
+
lines.push(`**URL:** ${project.url}`);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (project.description) {
|
|
635
|
+
lines.push('');
|
|
636
|
+
lines.push(project.description);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (project.content) {
|
|
640
|
+
lines.push('');
|
|
641
|
+
if (project.description) {
|
|
642
|
+
lines.push('## Content');
|
|
643
|
+
lines.push('');
|
|
644
|
+
}
|
|
645
|
+
lines.push(project.content);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
if (project.projectMilestones.length > 0) {
|
|
649
|
+
lines.push('');
|
|
650
|
+
lines.push(`## Milestones (${project.projectMilestones.length})`);
|
|
651
|
+
lines.push('');
|
|
652
|
+
|
|
653
|
+
for (const milestone of project.projectMilestones) {
|
|
654
|
+
const progressLabel = milestone.progress !== undefined && milestone.progress !== null
|
|
655
|
+
? `${milestone.progress}%`
|
|
656
|
+
: 'N/A';
|
|
657
|
+
const targetLabel = milestone.targetDate ? ` → ${milestone.targetDate}` : '';
|
|
658
|
+
lines.push(`- **${milestone.name}** _[${milestone.status}]_ (${progressLabel})${targetLabel}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return toTextResult(lines.join('\n'), {
|
|
663
|
+
projectId: project.id,
|
|
664
|
+
name: project.name,
|
|
665
|
+
status: project.status,
|
|
666
|
+
teamCount: project.teams.length,
|
|
667
|
+
milestoneCount: project.projectMilestones.length,
|
|
668
|
+
url: project.url,
|
|
669
|
+
});
|
|
670
|
+
}, 'executeProjectView');
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export async function executeProjectCreate(client, params) {
|
|
674
|
+
return withHandlerErrorHandling(async () => {
|
|
675
|
+
const name = ensureNonEmpty(params.name, 'name');
|
|
676
|
+
const teamRefs = parseRefList(params.teams ?? params.team);
|
|
677
|
+
|
|
678
|
+
if (teamRefs.length === 0) {
|
|
679
|
+
throw new Error('Missing required field: teams');
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const teams = await Promise.all(teamRefs.map((teamRef) => resolveTeamRef(client, teamRef)));
|
|
683
|
+
|
|
684
|
+
let leadId;
|
|
685
|
+
if (params.lead === 'me') {
|
|
686
|
+
const viewer = await getViewer(client);
|
|
687
|
+
leadId = viewer.id;
|
|
688
|
+
} else if (params.lead) {
|
|
689
|
+
leadId = params.lead;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const project = await createProject(client, {
|
|
693
|
+
name,
|
|
694
|
+
teamIds: teams.map((team) => team.id),
|
|
695
|
+
description: params.description,
|
|
696
|
+
leadId,
|
|
697
|
+
priority: params.priority,
|
|
698
|
+
color: params.color,
|
|
699
|
+
icon: params.icon,
|
|
700
|
+
startDate: params.startDate,
|
|
701
|
+
targetDate: params.targetDate,
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
return toTextResult(
|
|
705
|
+
`Created project **${project.name}** for ${teams.map((team) => team.key).join(', ')}`,
|
|
706
|
+
{
|
|
707
|
+
projectId: project.id,
|
|
708
|
+
name: project.name,
|
|
709
|
+
teams: project.teams,
|
|
710
|
+
status: project.status,
|
|
711
|
+
url: project.url,
|
|
712
|
+
}
|
|
713
|
+
);
|
|
714
|
+
}, 'executeProjectCreate');
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
export async function executeProjectUpdate(client, params) {
|
|
718
|
+
return withHandlerErrorHandling(async () => {
|
|
719
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
720
|
+
const patch = {
|
|
721
|
+
name: params.name,
|
|
722
|
+
description: params.description,
|
|
723
|
+
priority: params.priority,
|
|
724
|
+
color: params.color,
|
|
725
|
+
icon: params.icon,
|
|
726
|
+
startDate: params.startDate,
|
|
727
|
+
targetDate: params.targetDate,
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
if (params.lead === 'me') {
|
|
731
|
+
const viewer = await getViewer(client);
|
|
732
|
+
patch.leadId = viewer.id;
|
|
733
|
+
} else if (params.lead === 'none') {
|
|
734
|
+
patch.leadId = null;
|
|
735
|
+
} else if (params.lead !== undefined) {
|
|
736
|
+
patch.leadId = params.lead;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (params.teams !== undefined || params.team !== undefined) {
|
|
740
|
+
const teamRefs = parseRefList(params.teams ?? params.team);
|
|
741
|
+
if (teamRefs.length === 0) {
|
|
742
|
+
throw new Error('At least one team is required when updating teams');
|
|
743
|
+
}
|
|
744
|
+
const teams = await Promise.all(teamRefs.map((teamRef) => resolveTeamRef(client, teamRef)));
|
|
745
|
+
patch.teamIds = teams.map((team) => team.id);
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const result = await updateProject(client, projectRef, patch);
|
|
749
|
+
|
|
750
|
+
return toTextResult(
|
|
751
|
+
`Updated project **${result.project.name}** (${result.changed.join(', ')})`,
|
|
752
|
+
{
|
|
753
|
+
projectId: result.project.id,
|
|
754
|
+
name: result.project.name,
|
|
755
|
+
changed: result.changed,
|
|
756
|
+
status: result.project.status,
|
|
757
|
+
}
|
|
758
|
+
);
|
|
759
|
+
}, 'executeProjectUpdate');
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
export async function executeProjectDelete(client, params) {
|
|
763
|
+
return withHandlerErrorHandling(async () => {
|
|
764
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
765
|
+
const result = await deleteProject(client, projectRef);
|
|
766
|
+
|
|
767
|
+
return toTextResult(
|
|
768
|
+
`Deleted project **${result.name}** \`${result.projectId}\``,
|
|
769
|
+
{
|
|
770
|
+
projectId: result.projectId,
|
|
771
|
+
name: result.name,
|
|
772
|
+
success: result.success,
|
|
773
|
+
}
|
|
774
|
+
);
|
|
775
|
+
}, 'executeProjectDelete');
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
export async function executeProjectArchive(client, params) {
|
|
779
|
+
return withHandlerErrorHandling(async () => {
|
|
780
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
781
|
+
const result = await archiveProject(client, projectRef);
|
|
782
|
+
|
|
783
|
+
return toTextResult(
|
|
784
|
+
`Archived project **${result.name || result.entity?.name || result.projectId}**`,
|
|
785
|
+
{
|
|
786
|
+
projectId: result.projectId,
|
|
787
|
+
name: result.name || result.entity?.name || null,
|
|
788
|
+
success: result.success,
|
|
789
|
+
}
|
|
790
|
+
);
|
|
791
|
+
}, 'executeProjectArchive');
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
export async function executeProjectUnarchive(client, params) {
|
|
795
|
+
return withHandlerErrorHandling(async () => {
|
|
796
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
797
|
+
const result = await unarchiveProject(client, projectRef);
|
|
798
|
+
|
|
799
|
+
return toTextResult(
|
|
800
|
+
`Unarchived project **${result.project.name}**`,
|
|
801
|
+
{
|
|
802
|
+
projectId: result.project.id,
|
|
803
|
+
name: result.project.name,
|
|
804
|
+
success: result.success,
|
|
805
|
+
}
|
|
806
|
+
);
|
|
807
|
+
}, 'executeProjectUnarchive');
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// ===== PROJECT UPDATE HANDLERS =====
|
|
811
|
+
|
|
812
|
+
export async function executeProjectUpdateList(client, params) {
|
|
813
|
+
return withHandlerErrorHandling(async () => {
|
|
814
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
815
|
+
const { project, updates } = await fetchProjectUpdates(client, projectRef, {
|
|
816
|
+
limit: params.limit ?? 10,
|
|
817
|
+
includeArchived: params.includeArchived === true,
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
if (updates.length === 0) {
|
|
821
|
+
return toTextResult(`No project updates found for "${project.name}"`, {
|
|
822
|
+
projectId: project.id,
|
|
823
|
+
projectName: project.name,
|
|
824
|
+
updateCount: 0,
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const lines = [`## Project updates for "${project.name}" (${updates.length})`, ''];
|
|
829
|
+
for (const update of updates) {
|
|
830
|
+
const author = update.user?.displayName || update.user?.name || 'Unknown';
|
|
831
|
+
const createdAt = update.createdAt ? String(update.createdAt).slice(0, 10) : 'unknown date';
|
|
832
|
+
const healthLabel = update.health ? ` [${update.health}]` : '';
|
|
833
|
+
const archivedLabel = update.archivedAt ? ' [archived]' : '';
|
|
834
|
+
const preview = update.body.replace(/\s+/g, ' ').trim().slice(0, 120);
|
|
835
|
+
lines.push(`- **${update.id}**${healthLabel}${archivedLabel} by ${author} on ${createdAt}`);
|
|
836
|
+
if (preview) {
|
|
837
|
+
lines.push(` ${preview}${update.body.length > 120 ? '...' : ''}`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
return toTextResult(lines.join('\n'), {
|
|
842
|
+
projectId: project.id,
|
|
843
|
+
projectName: project.name,
|
|
844
|
+
updateCount: updates.length,
|
|
845
|
+
});
|
|
846
|
+
}, 'executeProjectUpdateList');
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
export async function executeProjectUpdateView(client, params) {
|
|
850
|
+
return withHandlerErrorHandling(async () => {
|
|
851
|
+
const projectUpdateId = ensureNonEmpty(params.projectUpdate, 'projectUpdate');
|
|
852
|
+
const projectUpdate = await fetchProjectUpdateDetails(client, projectUpdateId);
|
|
853
|
+
|
|
854
|
+
const lines = [`# Project update ${projectUpdate.id}`];
|
|
855
|
+
const meta = [];
|
|
856
|
+
if (projectUpdate.project?.name) meta.push(`**Project:** ${projectUpdate.project.name}`);
|
|
857
|
+
if (projectUpdate.health) meta.push(`**Health:** ${projectUpdate.health}`);
|
|
858
|
+
if (projectUpdate.user?.displayName || projectUpdate.user?.name) meta.push(`**Author:** ${projectUpdate.user?.displayName || projectUpdate.user?.name}`);
|
|
859
|
+
if (projectUpdate.createdAt) meta.push(`**Created:** ${String(projectUpdate.createdAt).slice(0, 10)}`);
|
|
860
|
+
if (projectUpdate.archivedAt) meta.push(`**Archived:** ${String(projectUpdate.archivedAt).slice(0, 10)}`);
|
|
861
|
+
if (meta.length > 0) {
|
|
862
|
+
lines.push('');
|
|
863
|
+
lines.push(meta.join(' | '));
|
|
864
|
+
}
|
|
865
|
+
if (projectUpdate.url) {
|
|
866
|
+
lines.push('');
|
|
867
|
+
lines.push(`**URL:** ${projectUpdate.url}`);
|
|
868
|
+
}
|
|
869
|
+
if (projectUpdate.body) {
|
|
870
|
+
lines.push('');
|
|
871
|
+
lines.push(projectUpdate.body);
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
return toTextResult(lines.join('\n'), {
|
|
875
|
+
projectUpdateId: projectUpdate.id,
|
|
876
|
+
projectId: projectUpdate.project?.id || null,
|
|
877
|
+
projectName: projectUpdate.project?.name || null,
|
|
878
|
+
health: projectUpdate.health,
|
|
879
|
+
archivedAt: projectUpdate.archivedAt,
|
|
880
|
+
});
|
|
881
|
+
}, 'executeProjectUpdateView');
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
export async function executeProjectUpdateCreate(client, params) {
|
|
885
|
+
return withHandlerErrorHandling(async () => {
|
|
886
|
+
const projectRef = ensureNonEmpty(params.project, 'project');
|
|
887
|
+
const resolved = await resolveProjectRef(client, projectRef);
|
|
888
|
+
const projectUpdate = await createProjectUpdate(client, {
|
|
889
|
+
projectId: resolved.id,
|
|
890
|
+
body: params.body,
|
|
891
|
+
health: params.health,
|
|
892
|
+
isDiffHidden: params.isDiffHidden,
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
return toTextResult(
|
|
896
|
+
`Created project update **${projectUpdate.id}** for "${resolved.name}"`,
|
|
897
|
+
{
|
|
898
|
+
projectUpdateId: projectUpdate.id,
|
|
899
|
+
projectId: resolved.id,
|
|
900
|
+
projectName: resolved.name,
|
|
901
|
+
health: projectUpdate.health,
|
|
902
|
+
}
|
|
903
|
+
);
|
|
904
|
+
}, 'executeProjectUpdateCreate');
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
export async function executeProjectUpdateUpdate(client, params) {
|
|
908
|
+
return withHandlerErrorHandling(async () => {
|
|
909
|
+
const projectUpdateId = ensureNonEmpty(params.projectUpdate, 'projectUpdate');
|
|
910
|
+
const result = await updateProjectUpdate(client, projectUpdateId, {
|
|
911
|
+
body: params.body,
|
|
912
|
+
health: params.health,
|
|
913
|
+
isDiffHidden: params.isDiffHidden,
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
return toTextResult(
|
|
917
|
+
`Updated project update **${result.projectUpdate.id}** (${result.changed.join(', ')})`,
|
|
918
|
+
{
|
|
919
|
+
projectUpdateId: result.projectUpdate.id,
|
|
920
|
+
changed: result.changed,
|
|
921
|
+
health: result.projectUpdate.health,
|
|
922
|
+
}
|
|
923
|
+
);
|
|
924
|
+
}, 'executeProjectUpdateUpdate');
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
export async function executeProjectUpdateArchive(client, params) {
|
|
928
|
+
return withHandlerErrorHandling(async () => {
|
|
929
|
+
const projectUpdateId = ensureNonEmpty(params.projectUpdate, 'projectUpdate');
|
|
930
|
+
const result = await archiveProjectUpdate(client, projectUpdateId);
|
|
931
|
+
|
|
932
|
+
return toTextResult(
|
|
933
|
+
`Archived project update **${projectUpdateId}**`,
|
|
934
|
+
{
|
|
935
|
+
projectUpdateId,
|
|
936
|
+
success: result.success,
|
|
937
|
+
}
|
|
938
|
+
);
|
|
939
|
+
}, 'executeProjectUpdateArchive');
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
export async function executeProjectUpdateUnarchive(client, params) {
|
|
943
|
+
return withHandlerErrorHandling(async () => {
|
|
944
|
+
const projectUpdateId = ensureNonEmpty(params.projectUpdate, 'projectUpdate');
|
|
945
|
+
const result = await unarchiveProjectUpdate(client, projectUpdateId);
|
|
946
|
+
|
|
947
|
+
return toTextResult(
|
|
948
|
+
`Unarchived project update **${result.projectUpdate.id}**`,
|
|
949
|
+
{
|
|
950
|
+
projectUpdateId: result.projectUpdate.id,
|
|
951
|
+
success: result.success,
|
|
952
|
+
}
|
|
953
|
+
);
|
|
954
|
+
}, 'executeProjectUpdateUnarchive');
|
|
955
|
+
}
|
|
956
|
+
|
|
554
957
|
// ===== TEAM HANDLERS =====
|
|
555
958
|
|
|
556
959
|
/**
|