@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.
Files changed (54) hide show
  1. package/dist/formatters/account.d.ts +2 -0
  2. package/dist/formatters/account.js +48 -0
  3. package/dist/formatters/agile.d.ts +6 -0
  4. package/dist/formatters/agile.js +53 -0
  5. package/dist/formatters/checklist.d.ts +5 -0
  6. package/dist/formatters/checklist.js +30 -0
  7. package/dist/formatters/file.d.ts +5 -0
  8. package/dist/formatters/file.js +61 -0
  9. package/dist/formatters/group.d.ts +11 -0
  10. package/dist/formatters/group.js +53 -0
  11. package/dist/formatters/index.d.ts +15 -1
  12. package/dist/formatters/index.js +15 -1
  13. package/dist/formatters/issue.d.ts +13 -3
  14. package/dist/formatters/issue.js +43 -10
  15. package/dist/formatters/journals.d.ts +7 -1
  16. package/dist/formatters/journals.js +13 -15
  17. package/dist/formatters/membership.d.ts +5 -0
  18. package/dist/formatters/membership.js +41 -0
  19. package/dist/formatters/metadata.d.ts +135 -0
  20. package/dist/formatters/metadata.js +213 -0
  21. package/dist/formatters/project.d.ts +11 -0
  22. package/dist/formatters/project.js +82 -0
  23. package/dist/formatters/relation.d.ts +5 -0
  24. package/dist/formatters/relation.js +55 -0
  25. package/dist/formatters/search.d.ts +5 -0
  26. package/dist/formatters/search.js +61 -0
  27. package/dist/formatters/time.d.ts +11 -0
  28. package/dist/formatters/time.js +47 -0
  29. package/dist/formatters/user.d.ts +11 -0
  30. package/dist/formatters/user.js +83 -0
  31. package/dist/formatters/utils.d.ts +12 -0
  32. package/dist/formatters/utils.js +31 -0
  33. package/dist/formatters/version.d.ts +11 -0
  34. package/dist/formatters/version.js +75 -0
  35. package/dist/formatters/wiki.d.ts +11 -0
  36. package/dist/formatters/wiki.js +86 -0
  37. package/dist/redmine/client.d.ts +4 -0
  38. package/dist/redmine/client.js +6 -0
  39. package/dist/server.js +1 -1
  40. package/dist/tools/account.js +7 -1
  41. package/dist/tools/admin.js +25 -4
  42. package/dist/tools/agile.js +19 -3
  43. package/dist/tools/checklists.js +13 -2
  44. package/dist/tools/core.js +23 -5
  45. package/dist/tools/enumerations.js +19 -3
  46. package/dist/tools/files.js +13 -2
  47. package/dist/tools/memberships.js +13 -2
  48. package/dist/tools/metadata.js +37 -6
  49. package/dist/tools/relations.js +25 -4
  50. package/dist/tools/roles.js +13 -2
  51. package/dist/tools/search.js +7 -1
  52. package/dist/tools/time.js +13 -2
  53. package/dist/tools/wiki.js +13 -2
  54. 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,5 @@
1
+ import type { RedmineRelation, RedmineRelationsResponse } from "../redmine/types.js";
2
+ export declare function formatRelation(response: {
3
+ relation: RedmineRelation;
4
+ }): string;
5
+ export declare function formatRelationList(response: RedmineRelationsResponse): string;
@@ -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,5 @@
1
+ import type { RedmineSearchResponse } from "../redmine/types.js";
2
+ /**
3
+ * Format search results as Markdown
4
+ */
5
+ export declare function formatSearchResults(response: RedmineSearchResponse): string;
@@ -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;