@pschroee/redmine-mcp 0.4.5 → 0.5.1
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/formatters/account.d.ts +2 -0
- package/dist/formatters/account.js +48 -0
- package/dist/formatters/agile.d.ts +6 -0
- package/dist/formatters/agile.js +53 -0
- package/dist/formatters/checklist.d.ts +5 -0
- package/dist/formatters/checklist.js +30 -0
- package/dist/formatters/file.d.ts +5 -0
- package/dist/formatters/file.js +61 -0
- package/dist/formatters/group.d.ts +11 -0
- package/dist/formatters/group.js +53 -0
- package/dist/formatters/index.d.ts +15 -1
- package/dist/formatters/index.js +15 -1
- package/dist/formatters/issue.d.ts +13 -3
- package/dist/formatters/issue.js +43 -10
- package/dist/formatters/journals.d.ts +7 -1
- package/dist/formatters/journals.js +13 -15
- package/dist/formatters/membership.d.ts +5 -0
- package/dist/formatters/membership.js +41 -0
- package/dist/formatters/metadata.d.ts +135 -0
- package/dist/formatters/metadata.js +213 -0
- package/dist/formatters/project.d.ts +11 -0
- package/dist/formatters/project.js +82 -0
- package/dist/formatters/relation.d.ts +5 -0
- package/dist/formatters/relation.js +55 -0
- package/dist/formatters/search.d.ts +5 -0
- package/dist/formatters/search.js +61 -0
- package/dist/formatters/time.d.ts +11 -0
- package/dist/formatters/time.js +47 -0
- package/dist/formatters/user.d.ts +11 -0
- package/dist/formatters/user.js +83 -0
- package/dist/formatters/utils.d.ts +12 -0
- package/dist/formatters/utils.js +31 -0
- package/dist/formatters/version.d.ts +11 -0
- package/dist/formatters/version.js +75 -0
- package/dist/formatters/wiki.d.ts +11 -0
- package/dist/formatters/wiki.js +86 -0
- package/dist/redmine/client.d.ts +4 -0
- package/dist/redmine/client.js +6 -0
- package/dist/server.js +1 -1
- package/dist/tools/account.js +7 -1
- package/dist/tools/admin.js +25 -4
- package/dist/tools/agile.js +19 -3
- package/dist/tools/checklists.js +13 -2
- package/dist/tools/core.js +23 -5
- package/dist/tools/enumerations.js +19 -3
- package/dist/tools/files.js +13 -2
- package/dist/tools/memberships.js +13 -2
- package/dist/tools/metadata.js +37 -6
- package/dist/tools/relations.js +25 -4
- package/dist/tools/roles.js +13 -2
- package/dist/tools/search.js +7 -1
- package/dist/tools/time.js +13 -2
- package/dist/tools/wiki.js +13 -2
- package/package.json +1 -1
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
interface RedmineTracker {
|
|
2
|
+
id: number;
|
|
3
|
+
name: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
}
|
|
6
|
+
interface RedmineTrackersResponse {
|
|
7
|
+
trackers: RedmineTracker[];
|
|
8
|
+
}
|
|
9
|
+
interface RedmineIssueStatus {
|
|
10
|
+
id: number;
|
|
11
|
+
name: string;
|
|
12
|
+
is_closed: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface RedmineIssueStatusesResponse {
|
|
15
|
+
issue_statuses: RedmineIssueStatus[];
|
|
16
|
+
}
|
|
17
|
+
interface RedmineCategory {
|
|
18
|
+
id: number;
|
|
19
|
+
name: string;
|
|
20
|
+
project?: {
|
|
21
|
+
id: number;
|
|
22
|
+
name: string;
|
|
23
|
+
};
|
|
24
|
+
assigned_to?: {
|
|
25
|
+
id: number;
|
|
26
|
+
name: string;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
interface RedmineCategoriesResponse {
|
|
30
|
+
issue_categories: RedmineCategory[];
|
|
31
|
+
}
|
|
32
|
+
interface RedminePriority {
|
|
33
|
+
id: number;
|
|
34
|
+
name: string;
|
|
35
|
+
is_default: boolean;
|
|
36
|
+
}
|
|
37
|
+
interface RedminePrioritiesResponse {
|
|
38
|
+
issue_priorities: RedminePriority[];
|
|
39
|
+
}
|
|
40
|
+
interface RedmineActivity {
|
|
41
|
+
id: number;
|
|
42
|
+
name: string;
|
|
43
|
+
is_default: boolean;
|
|
44
|
+
active?: boolean;
|
|
45
|
+
}
|
|
46
|
+
interface RedmineActivitiesResponse {
|
|
47
|
+
time_entry_activities: RedmineActivity[];
|
|
48
|
+
}
|
|
49
|
+
interface RedmineRole {
|
|
50
|
+
id: number;
|
|
51
|
+
name: string;
|
|
52
|
+
assignable?: boolean;
|
|
53
|
+
permissions?: string[];
|
|
54
|
+
}
|
|
55
|
+
interface RedmineRolesResponse {
|
|
56
|
+
roles: RedmineRole[];
|
|
57
|
+
}
|
|
58
|
+
interface RedmineCustomField {
|
|
59
|
+
id: number;
|
|
60
|
+
name: string;
|
|
61
|
+
customized_type: string;
|
|
62
|
+
field_format: string;
|
|
63
|
+
is_required: boolean;
|
|
64
|
+
is_filter: boolean;
|
|
65
|
+
searchable: boolean;
|
|
66
|
+
}
|
|
67
|
+
interface RedmineCustomFieldsResponse {
|
|
68
|
+
custom_fields: RedmineCustomField[];
|
|
69
|
+
}
|
|
70
|
+
interface RedmineQuery {
|
|
71
|
+
id: number;
|
|
72
|
+
name: string;
|
|
73
|
+
is_public: boolean;
|
|
74
|
+
project_id?: number;
|
|
75
|
+
}
|
|
76
|
+
interface RedmineQueriesResponse {
|
|
77
|
+
queries: RedmineQuery[];
|
|
78
|
+
}
|
|
79
|
+
interface RedmineDocumentCategory {
|
|
80
|
+
id: number;
|
|
81
|
+
name: string;
|
|
82
|
+
is_default: boolean;
|
|
83
|
+
}
|
|
84
|
+
interface RedmineDocumentCategoriesResponse {
|
|
85
|
+
document_categories: RedmineDocumentCategory[];
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Format a list of trackers as a Markdown table
|
|
89
|
+
*/
|
|
90
|
+
export declare function formatTrackerList(response: RedmineTrackersResponse): string;
|
|
91
|
+
/**
|
|
92
|
+
* Format a list of issue statuses as a Markdown table
|
|
93
|
+
*/
|
|
94
|
+
export declare function formatStatusList(response: RedmineIssueStatusesResponse): string;
|
|
95
|
+
/**
|
|
96
|
+
* Format a list of issue categories as a Markdown table
|
|
97
|
+
*/
|
|
98
|
+
export declare function formatCategoryList(response: RedmineCategoriesResponse): string;
|
|
99
|
+
/**
|
|
100
|
+
* Format a list of issue priorities as a Markdown table
|
|
101
|
+
*/
|
|
102
|
+
export declare function formatPriorityList(response: RedminePrioritiesResponse): string;
|
|
103
|
+
/**
|
|
104
|
+
* Format a list of time entry activities as a Markdown table
|
|
105
|
+
*/
|
|
106
|
+
export declare function formatActivityList(response: RedmineActivitiesResponse): string;
|
|
107
|
+
/**
|
|
108
|
+
* Format a list of roles as a Markdown table
|
|
109
|
+
*/
|
|
110
|
+
export declare function formatRoleList(response: RedmineRolesResponse): string;
|
|
111
|
+
/**
|
|
112
|
+
* Format a single role with details as Markdown
|
|
113
|
+
*/
|
|
114
|
+
export declare function formatRole(response: {
|
|
115
|
+
role: RedmineRole;
|
|
116
|
+
}): string;
|
|
117
|
+
/**
|
|
118
|
+
* Format a single issue category as Markdown
|
|
119
|
+
*/
|
|
120
|
+
export declare function formatCategory(response: {
|
|
121
|
+
issue_category: RedmineCategory;
|
|
122
|
+
}): string;
|
|
123
|
+
/**
|
|
124
|
+
* Format a list of custom fields as a Markdown table
|
|
125
|
+
*/
|
|
126
|
+
export declare function formatCustomFieldList(response: RedmineCustomFieldsResponse): string;
|
|
127
|
+
/**
|
|
128
|
+
* Format a list of saved queries as a Markdown table
|
|
129
|
+
*/
|
|
130
|
+
export declare function formatQueryList(response: RedmineQueriesResponse): string;
|
|
131
|
+
/**
|
|
132
|
+
* Format a list of document categories as a Markdown table
|
|
133
|
+
*/
|
|
134
|
+
export declare function formatDocumentCategoryList(response: RedmineDocumentCategoriesResponse): string;
|
|
135
|
+
export {};
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a list of trackers as a Markdown table
|
|
3
|
+
*/
|
|
4
|
+
export function formatTrackerList(response) {
|
|
5
|
+
const lines = [];
|
|
6
|
+
lines.push("# Trackers");
|
|
7
|
+
lines.push("");
|
|
8
|
+
if (response.trackers.length === 0) {
|
|
9
|
+
lines.push("No trackers found.");
|
|
10
|
+
return lines.join("\n");
|
|
11
|
+
}
|
|
12
|
+
lines.push("| ID | Name |");
|
|
13
|
+
lines.push("|----|------|");
|
|
14
|
+
for (const tracker of response.trackers) {
|
|
15
|
+
lines.push(`| ${tracker.id} | ${tracker.name} |`);
|
|
16
|
+
}
|
|
17
|
+
return lines.join("\n");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Format a list of issue statuses as a Markdown table
|
|
21
|
+
*/
|
|
22
|
+
export function formatStatusList(response) {
|
|
23
|
+
const lines = [];
|
|
24
|
+
lines.push("# Issue Statuses");
|
|
25
|
+
lines.push("");
|
|
26
|
+
if (response.issue_statuses.length === 0) {
|
|
27
|
+
lines.push("No statuses found.");
|
|
28
|
+
return lines.join("\n");
|
|
29
|
+
}
|
|
30
|
+
lines.push("| ID | Name | Closed |");
|
|
31
|
+
lines.push("|----|------|--------|");
|
|
32
|
+
for (const status of response.issue_statuses) {
|
|
33
|
+
const closed = status.is_closed ? "Yes" : "No";
|
|
34
|
+
lines.push(`| ${status.id} | ${status.name} | ${closed} |`);
|
|
35
|
+
}
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format a list of issue categories as a Markdown table
|
|
40
|
+
*/
|
|
41
|
+
export function formatCategoryList(response) {
|
|
42
|
+
const lines = [];
|
|
43
|
+
lines.push("# Issue Categories");
|
|
44
|
+
lines.push("");
|
|
45
|
+
if (response.issue_categories.length === 0) {
|
|
46
|
+
lines.push("No categories found.");
|
|
47
|
+
return lines.join("\n");
|
|
48
|
+
}
|
|
49
|
+
lines.push("| ID | Name | Assigned To |");
|
|
50
|
+
lines.push("|----|------|-------------|");
|
|
51
|
+
for (const category of response.issue_categories) {
|
|
52
|
+
const assignedTo = category.assigned_to?.name || "";
|
|
53
|
+
lines.push(`| ${category.id} | ${category.name} | ${assignedTo} |`);
|
|
54
|
+
}
|
|
55
|
+
return lines.join("\n");
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Format a list of issue priorities as a Markdown table
|
|
59
|
+
*/
|
|
60
|
+
export function formatPriorityList(response) {
|
|
61
|
+
const lines = [];
|
|
62
|
+
lines.push("# Issue Priorities");
|
|
63
|
+
lines.push("");
|
|
64
|
+
if (response.issue_priorities.length === 0) {
|
|
65
|
+
lines.push("No priorities found.");
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|
|
68
|
+
lines.push("| ID | Name | Default |");
|
|
69
|
+
lines.push("|----|------|---------|");
|
|
70
|
+
for (const priority of response.issue_priorities) {
|
|
71
|
+
const isDefault = priority.is_default ? "Yes" : "No";
|
|
72
|
+
lines.push(`| ${priority.id} | ${priority.name} | ${isDefault} |`);
|
|
73
|
+
}
|
|
74
|
+
return lines.join("\n");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Format a list of time entry activities as a Markdown table
|
|
78
|
+
*/
|
|
79
|
+
export function formatActivityList(response) {
|
|
80
|
+
const lines = [];
|
|
81
|
+
lines.push("# Time Entry Activities");
|
|
82
|
+
lines.push("");
|
|
83
|
+
if (response.time_entry_activities.length === 0) {
|
|
84
|
+
lines.push("No activities found.");
|
|
85
|
+
return lines.join("\n");
|
|
86
|
+
}
|
|
87
|
+
lines.push("| ID | Name | Default | Active |");
|
|
88
|
+
lines.push("|----|------|---------|--------|");
|
|
89
|
+
for (const activity of response.time_entry_activities) {
|
|
90
|
+
const isDefault = activity.is_default ? "Yes" : "No";
|
|
91
|
+
const isActive = activity.active === undefined ? "" : activity.active ? "Yes" : "No";
|
|
92
|
+
lines.push(`| ${activity.id} | ${activity.name} | ${isDefault} | ${isActive} |`);
|
|
93
|
+
}
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Format a list of roles as a Markdown table
|
|
98
|
+
*/
|
|
99
|
+
export function formatRoleList(response) {
|
|
100
|
+
const lines = [];
|
|
101
|
+
lines.push("# Roles");
|
|
102
|
+
lines.push("");
|
|
103
|
+
if (response.roles.length === 0) {
|
|
104
|
+
lines.push("No roles found.");
|
|
105
|
+
return lines.join("\n");
|
|
106
|
+
}
|
|
107
|
+
lines.push("| ID | Name | Assignable |");
|
|
108
|
+
lines.push("|----|------|------------|");
|
|
109
|
+
for (const role of response.roles) {
|
|
110
|
+
const assignable = role.assignable === undefined ? "" : role.assignable ? "Yes" : "No";
|
|
111
|
+
lines.push(`| ${role.id} | ${role.name} | ${assignable} |`);
|
|
112
|
+
}
|
|
113
|
+
return lines.join("\n");
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Format a single role with details as Markdown
|
|
117
|
+
*/
|
|
118
|
+
export function formatRole(response) {
|
|
119
|
+
const role = response.role;
|
|
120
|
+
const lines = [];
|
|
121
|
+
lines.push(`# ${role.name}`);
|
|
122
|
+
lines.push("");
|
|
123
|
+
lines.push(`**ID:** ${role.id}`);
|
|
124
|
+
if (role.assignable !== undefined) {
|
|
125
|
+
lines.push(`**Assignable:** ${role.assignable ? "Yes" : "No"}`);
|
|
126
|
+
}
|
|
127
|
+
lines.push("");
|
|
128
|
+
if (role.permissions && role.permissions.length > 0) {
|
|
129
|
+
lines.push("## Permissions");
|
|
130
|
+
lines.push("");
|
|
131
|
+
for (const permission of role.permissions) {
|
|
132
|
+
lines.push(`- ${permission}`);
|
|
133
|
+
}
|
|
134
|
+
lines.push("");
|
|
135
|
+
}
|
|
136
|
+
return lines.join("\n").trimEnd();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Format a single issue category as Markdown
|
|
140
|
+
*/
|
|
141
|
+
export function formatCategory(response) {
|
|
142
|
+
const cat = response.issue_category;
|
|
143
|
+
const lines = [];
|
|
144
|
+
lines.push(`# ${cat.name}`);
|
|
145
|
+
lines.push("");
|
|
146
|
+
lines.push("| Field | Value |");
|
|
147
|
+
lines.push("|-------|-------|");
|
|
148
|
+
lines.push(`| ID | ${cat.id} |`);
|
|
149
|
+
if (cat.project) {
|
|
150
|
+
lines.push(`| Project | ${cat.project.name} |`);
|
|
151
|
+
}
|
|
152
|
+
if (cat.assigned_to) {
|
|
153
|
+
lines.push(`| Default Assignee | ${cat.assigned_to.name} |`);
|
|
154
|
+
}
|
|
155
|
+
return lines.join("\n");
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Format a list of custom fields as a Markdown table
|
|
159
|
+
*/
|
|
160
|
+
export function formatCustomFieldList(response) {
|
|
161
|
+
const fields = response.custom_fields;
|
|
162
|
+
if (fields.length === 0) {
|
|
163
|
+
return "No custom fields found.";
|
|
164
|
+
}
|
|
165
|
+
const lines = [];
|
|
166
|
+
lines.push(`# Custom Fields (${fields.length})`);
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push("| ID | Name | Type | Format | Required |");
|
|
169
|
+
lines.push("|----|------|------|--------|----------|");
|
|
170
|
+
for (const field of fields) {
|
|
171
|
+
const required = field.is_required ? "Yes" : "No";
|
|
172
|
+
lines.push(`| ${field.id} | ${field.name} | ${field.customized_type} | ${field.field_format} | ${required} |`);
|
|
173
|
+
}
|
|
174
|
+
return lines.join("\n");
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Format a list of saved queries as a Markdown table
|
|
178
|
+
*/
|
|
179
|
+
export function formatQueryList(response) {
|
|
180
|
+
const queries = response.queries;
|
|
181
|
+
if (queries.length === 0) {
|
|
182
|
+
return "No queries found.";
|
|
183
|
+
}
|
|
184
|
+
const lines = [];
|
|
185
|
+
lines.push(`# Saved Queries (${queries.length})`);
|
|
186
|
+
lines.push("");
|
|
187
|
+
lines.push("| ID | Name | Visibility |");
|
|
188
|
+
lines.push("|----|------|------------|");
|
|
189
|
+
for (const query of queries) {
|
|
190
|
+
const visibility = query.is_public ? "Public" : "Private";
|
|
191
|
+
lines.push(`| ${query.id} | ${query.name} | ${visibility} |`);
|
|
192
|
+
}
|
|
193
|
+
return lines.join("\n");
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Format a list of document categories as a Markdown table
|
|
197
|
+
*/
|
|
198
|
+
export function formatDocumentCategoryList(response) {
|
|
199
|
+
const categories = response.document_categories;
|
|
200
|
+
if (categories.length === 0) {
|
|
201
|
+
return "No document categories found.";
|
|
202
|
+
}
|
|
203
|
+
const lines = [];
|
|
204
|
+
lines.push(`# Document Categories (${categories.length})`);
|
|
205
|
+
lines.push("");
|
|
206
|
+
lines.push("| ID | Name | Default |");
|
|
207
|
+
lines.push("|----|------|---------|");
|
|
208
|
+
for (const cat of categories) {
|
|
209
|
+
const isDefault = cat.is_default ? "Yes" : "No";
|
|
210
|
+
lines.push(`| ${cat.id} | ${cat.name} | ${isDefault} |`);
|
|
211
|
+
}
|
|
212
|
+
return lines.join("\n");
|
|
213
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RedmineProject, RedmineProjectsResponse } from "../redmine/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Format a single project as complete Markdown
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatProject(response: {
|
|
6
|
+
project: RedmineProject;
|
|
7
|
+
}): string;
|
|
8
|
+
/**
|
|
9
|
+
* Format a list of projects as Markdown
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatProjectList(response: RedmineProjectsResponse): string;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { formatDate } from "./utils.js";
|
|
2
|
+
const PROJECT_STATUS = {
|
|
3
|
+
1: "Active",
|
|
4
|
+
5: "Closed",
|
|
5
|
+
9: "Archived",
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Format a single project as complete Markdown
|
|
9
|
+
*/
|
|
10
|
+
export function formatProject(response) {
|
|
11
|
+
const project = response.project;
|
|
12
|
+
const lines = [];
|
|
13
|
+
// Title
|
|
14
|
+
lines.push(`# ${project.name}`);
|
|
15
|
+
lines.push("");
|
|
16
|
+
// Status line
|
|
17
|
+
const statusParts = [];
|
|
18
|
+
statusParts.push(`**Identifier:** ${project.identifier}`);
|
|
19
|
+
statusParts.push(`**Status:** ${PROJECT_STATUS[project.status] || "Unknown"}`);
|
|
20
|
+
statusParts.push(`**Public:** ${project.is_public ? "Yes" : "No"}`);
|
|
21
|
+
lines.push(statusParts.join(" | "));
|
|
22
|
+
lines.push("");
|
|
23
|
+
// Description
|
|
24
|
+
if (project.description) {
|
|
25
|
+
lines.push("## Description");
|
|
26
|
+
lines.push("");
|
|
27
|
+
lines.push(project.description);
|
|
28
|
+
lines.push("");
|
|
29
|
+
}
|
|
30
|
+
// Metadata table
|
|
31
|
+
lines.push("| Field | Value |");
|
|
32
|
+
lines.push("|-------|-------|");
|
|
33
|
+
if (project.parent) {
|
|
34
|
+
lines.push(`| Parent | ${project.parent.name} |`);
|
|
35
|
+
}
|
|
36
|
+
if (project.homepage) {
|
|
37
|
+
lines.push(`| Homepage | ${project.homepage} |`);
|
|
38
|
+
}
|
|
39
|
+
lines.push(`| Created | ${formatDate(project.created_on)} |`);
|
|
40
|
+
lines.push(`| Updated | ${formatDate(project.updated_on)} |`);
|
|
41
|
+
lines.push("");
|
|
42
|
+
// Trackers
|
|
43
|
+
if (project.trackers && project.trackers.length > 0) {
|
|
44
|
+
lines.push("## Trackers");
|
|
45
|
+
lines.push("");
|
|
46
|
+
for (const tracker of project.trackers) {
|
|
47
|
+
lines.push(`- ${tracker.name}`);
|
|
48
|
+
}
|
|
49
|
+
lines.push("");
|
|
50
|
+
}
|
|
51
|
+
// Enabled modules
|
|
52
|
+
if (project.enabled_modules && project.enabled_modules.length > 0) {
|
|
53
|
+
lines.push("## Enabled Modules");
|
|
54
|
+
lines.push("");
|
|
55
|
+
for (const mod of project.enabled_modules) {
|
|
56
|
+
lines.push(`- ${mod.name}`);
|
|
57
|
+
}
|
|
58
|
+
lines.push("");
|
|
59
|
+
}
|
|
60
|
+
return lines.join("\n");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Format a list of projects as Markdown
|
|
64
|
+
*/
|
|
65
|
+
export function formatProjectList(response) {
|
|
66
|
+
const lines = [];
|
|
67
|
+
lines.push(`# Projects (${response.total_count})`);
|
|
68
|
+
lines.push("");
|
|
69
|
+
if (response.projects.length === 0) {
|
|
70
|
+
lines.push("No projects found.");
|
|
71
|
+
return lines.join("\n");
|
|
72
|
+
}
|
|
73
|
+
// Table header
|
|
74
|
+
lines.push("| Name | Identifier | Status | Public |");
|
|
75
|
+
lines.push("|------|------------|--------|--------|");
|
|
76
|
+
for (const project of response.projects) {
|
|
77
|
+
const status = PROJECT_STATUS[project.status] || "Unknown";
|
|
78
|
+
const isPublic = project.is_public ? "Yes" : "No";
|
|
79
|
+
lines.push(`| ${project.name} | ${project.identifier} | ${status} | ${isPublic} |`);
|
|
80
|
+
}
|
|
81
|
+
return lines.join("\n");
|
|
82
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const RELATION_LABELS = {
|
|
2
|
+
relates: "relates",
|
|
3
|
+
duplicates: "duplicates",
|
|
4
|
+
duplicated: "duplicated by",
|
|
5
|
+
blocks: "blocks",
|
|
6
|
+
blocked: "blocked by",
|
|
7
|
+
precedes: "precedes",
|
|
8
|
+
follows: "follows",
|
|
9
|
+
copied_to: "copied to",
|
|
10
|
+
copied_from: "copied from",
|
|
11
|
+
};
|
|
12
|
+
export function formatRelation(response) {
|
|
13
|
+
const rel = response.relation;
|
|
14
|
+
const lines = [];
|
|
15
|
+
lines.push(`# Relation #${rel.id}`);
|
|
16
|
+
lines.push("");
|
|
17
|
+
lines.push("| Field | Value |");
|
|
18
|
+
lines.push("|-------|-------|");
|
|
19
|
+
lines.push(`| Issue | #${rel.issue_id} |`);
|
|
20
|
+
lines.push(`| Type | ${RELATION_LABELS[rel.relation_type] || rel.relation_type} |`);
|
|
21
|
+
lines.push(`| Related To | #${rel.issue_to_id} |`);
|
|
22
|
+
if (rel.delay !== undefined && rel.delay > 0) {
|
|
23
|
+
lines.push(`| Delay | ${rel.delay} days |`);
|
|
24
|
+
}
|
|
25
|
+
return lines.join("\n");
|
|
26
|
+
}
|
|
27
|
+
export function formatRelationList(response) {
|
|
28
|
+
const relations = response.relations;
|
|
29
|
+
if (relations.length === 0) {
|
|
30
|
+
return "No relations found.";
|
|
31
|
+
}
|
|
32
|
+
const lines = [];
|
|
33
|
+
const hasDelay = relations.some((r) => r.delay !== undefined && r.delay > 0);
|
|
34
|
+
lines.push(`# Issue Relations (${relations.length})`);
|
|
35
|
+
lines.push("");
|
|
36
|
+
if (hasDelay) {
|
|
37
|
+
lines.push("| ID | Issue | Type | Related To | Delay |");
|
|
38
|
+
lines.push("|----|-------|------|------------|-------|");
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
lines.push("| ID | Issue | Type | Related To |");
|
|
42
|
+
lines.push("|----|-------|------|------------|");
|
|
43
|
+
}
|
|
44
|
+
for (const rel of relations) {
|
|
45
|
+
const type = RELATION_LABELS[rel.relation_type] || rel.relation_type;
|
|
46
|
+
if (hasDelay) {
|
|
47
|
+
const delay = rel.delay ? `${rel.delay} days` : "";
|
|
48
|
+
lines.push(`| ${rel.id} | #${rel.issue_id} | ${type} | #${rel.issue_to_id} | ${delay} |`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
lines.push(`| ${rel.id} | #${rel.issue_id} | ${type} | #${rel.issue_to_id} |`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { formatDateShort, truncate } from "./utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* Get icon for search result type
|
|
4
|
+
*/
|
|
5
|
+
function getTypeIcon(type) {
|
|
6
|
+
switch (type.toLowerCase()) {
|
|
7
|
+
case "issue":
|
|
8
|
+
return "\u{1F3AB}"; // ticket
|
|
9
|
+
case "wiki-page":
|
|
10
|
+
return "\u{1F4C4}"; // page
|
|
11
|
+
case "project":
|
|
12
|
+
return "\u{1F4C1}"; // folder
|
|
13
|
+
default:
|
|
14
|
+
return "\u{1F4CB}"; // clipboard
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Format a single search result as Markdown
|
|
19
|
+
*/
|
|
20
|
+
function formatSearchResult(result) {
|
|
21
|
+
const lines = [];
|
|
22
|
+
const icon = getTypeIcon(result.type);
|
|
23
|
+
// Title with icon
|
|
24
|
+
lines.push(`### ${icon} ${result.title}`);
|
|
25
|
+
// Type and date metadata
|
|
26
|
+
lines.push(`**Type:** ${result.type} | **Date:** ${formatDateShort(result.datetime)}`);
|
|
27
|
+
// Description (truncated)
|
|
28
|
+
if (result.description) {
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push(truncate(result.description, 200));
|
|
31
|
+
}
|
|
32
|
+
return lines.join("\n");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Format search results as Markdown
|
|
36
|
+
*/
|
|
37
|
+
export function formatSearchResults(response) {
|
|
38
|
+
const { results, total_count, offset } = response;
|
|
39
|
+
const lines = [];
|
|
40
|
+
// Header with count
|
|
41
|
+
lines.push(`# Search Results (${results.length} of ${total_count})`);
|
|
42
|
+
lines.push("");
|
|
43
|
+
// Offset info if applicable
|
|
44
|
+
if (offset > 0) {
|
|
45
|
+
lines.push(`_Starting from result ${offset + 1}_`);
|
|
46
|
+
lines.push("");
|
|
47
|
+
}
|
|
48
|
+
// Empty case
|
|
49
|
+
if (results.length === 0) {
|
|
50
|
+
lines.push("No results found.");
|
|
51
|
+
return lines.join("\n");
|
|
52
|
+
}
|
|
53
|
+
// Format each result
|
|
54
|
+
for (let i = 0; i < results.length; i++) {
|
|
55
|
+
if (i > 0) {
|
|
56
|
+
lines.push("");
|
|
57
|
+
}
|
|
58
|
+
lines.push(formatSearchResult(results[i]));
|
|
59
|
+
}
|
|
60
|
+
return lines.join("\n");
|
|
61
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RedmineTimeEntry, RedmineTimeEntriesResponse } from "../redmine/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Format a single time entry as complete Markdown
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatTimeEntry(response: {
|
|
6
|
+
time_entry: RedmineTimeEntry;
|
|
7
|
+
}): string;
|
|
8
|
+
/**
|
|
9
|
+
* Format a list of time entries as Markdown
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatTimeEntryList(response: RedmineTimeEntriesResponse): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a single time entry as complete Markdown
|
|
3
|
+
*/
|
|
4
|
+
export function formatTimeEntry(response) {
|
|
5
|
+
const entry = response.time_entry;
|
|
6
|
+
const lines = [];
|
|
7
|
+
// Header
|
|
8
|
+
lines.push(`# Time Entry #${entry.id}`);
|
|
9
|
+
lines.push("");
|
|
10
|
+
// Metadata table
|
|
11
|
+
lines.push("| Field | Value |");
|
|
12
|
+
lines.push("|-------|-------|");
|
|
13
|
+
lines.push(`| Project | ${entry.project.name} |`);
|
|
14
|
+
if (entry.issue) {
|
|
15
|
+
lines.push(`| Issue | #${entry.issue.id} |`);
|
|
16
|
+
}
|
|
17
|
+
lines.push(`| User | ${entry.user.name} |`);
|
|
18
|
+
lines.push(`| Activity | ${entry.activity.name} |`);
|
|
19
|
+
lines.push(`| Hours | ${entry.hours} |`);
|
|
20
|
+
lines.push(`| Date | ${entry.spent_on} |`);
|
|
21
|
+
if (entry.comments) {
|
|
22
|
+
lines.push(`| Comments | ${entry.comments} |`);
|
|
23
|
+
}
|
|
24
|
+
lines.push("");
|
|
25
|
+
return lines.join("\n");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Format a list of time entries as Markdown
|
|
29
|
+
*/
|
|
30
|
+
export function formatTimeEntryList(response) {
|
|
31
|
+
const lines = [];
|
|
32
|
+
lines.push(`# Time Entries (${response.total_count})`);
|
|
33
|
+
lines.push("");
|
|
34
|
+
if (response.time_entries.length === 0) {
|
|
35
|
+
lines.push("No time entries found.");
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
// Table header
|
|
39
|
+
lines.push("| ID | Date | Hours | User | Activity | Issue | Comments |");
|
|
40
|
+
lines.push("|----|------|-------|------|----------|-------|----------|");
|
|
41
|
+
for (const entry of response.time_entries) {
|
|
42
|
+
const issue = entry.issue ? `#${entry.issue.id}` : "-";
|
|
43
|
+
const comments = entry.comments || "-";
|
|
44
|
+
lines.push(`| ${entry.id} | ${entry.spent_on} | ${entry.hours} | ${entry.user.name} | ${entry.activity.name} | ${issue} | ${comments} |`);
|
|
45
|
+
}
|
|
46
|
+
return lines.join("\n");
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RedmineUser, RedmineUsersResponse } from "../redmine/types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Format a single user as complete Markdown
|
|
4
|
+
*/
|
|
5
|
+
export declare function formatUser(response: {
|
|
6
|
+
user: RedmineUser;
|
|
7
|
+
}): string;
|
|
8
|
+
/**
|
|
9
|
+
* Format a list of users as Markdown table
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatUserList(response: RedmineUsersResponse): string;
|