@mrc2204/openclaw-jira-tools 1.0.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/README.en.md +127 -0
- package/README.md +129 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +21 -0
- package/dist/lib/defaults-store.d.ts +8 -0
- package/dist/lib/defaults-store.js +27 -0
- package/dist/lib/jira-client.d.ts +36 -0
- package/dist/lib/jira-client.js +112 -0
- package/dist/lib/runtime.d.ts +17 -0
- package/dist/lib/runtime.js +21 -0
- package/dist/shared/types.d.ts +41 -0
- package/dist/shared/types.js +1 -0
- package/dist/tools/create-tools.d.ts +3 -0
- package/dist/tools/create-tools.js +134 -0
- package/dist/tools/defaults-tools.d.ts +2 -0
- package/dist/tools/defaults-tools.js +34 -0
- package/dist/tools/inspect-tool.d.ts +3 -0
- package/dist/tools/inspect-tool.js +53 -0
- package/dist/tools/management-tools.d.ts +3 -0
- package/dist/tools/management-tools.js +574 -0
- package/openclaw.plugin.json +45 -0
- package/package.json +39 -0
- package/skill/jira/SKILL.md +435 -0
- package/skill/jira/_meta.json +6 -0
|
@@ -0,0 +1,574 @@
|
|
|
1
|
+
import { JiraClient } from "../lib/jira-client.js";
|
|
2
|
+
import { createToolResult } from "../lib/runtime.js";
|
|
3
|
+
function makeClient(rawConfig) {
|
|
4
|
+
return new JiraClient({
|
|
5
|
+
server: rawConfig.server.replace(/\/$/, ""),
|
|
6
|
+
email: rawConfig.email,
|
|
7
|
+
token: rawConfig.token,
|
|
8
|
+
defaultProject: rawConfig.defaultProject || "TAA",
|
|
9
|
+
language: rawConfig.language || "vi",
|
|
10
|
+
requireClickableLinks: rawConfig.requireClickableLinks !== false,
|
|
11
|
+
issueTypeMap: {
|
|
12
|
+
task: rawConfig.issueTypeMap?.task || "Task",
|
|
13
|
+
epic: rawConfig.issueTypeMap?.epic || "Epic",
|
|
14
|
+
subtask: rawConfig.issueTypeMap?.subtask || "Subtask",
|
|
15
|
+
},
|
|
16
|
+
fieldMappings: {
|
|
17
|
+
epicName: rawConfig.fieldMappings?.epicName || "customfield_10011",
|
|
18
|
+
epicLink: rawConfig.fieldMappings?.epicLink || "customfield_10014",
|
|
19
|
+
parentLink: rawConfig.fieldMappings?.parentLink || "customfield_10018",
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
export function registerManagementTools(api, rawConfig) {
|
|
24
|
+
const client = makeClient(rawConfig);
|
|
25
|
+
api.registerTool({
|
|
26
|
+
name: "jira_me",
|
|
27
|
+
label: "Jira Me",
|
|
28
|
+
description: "Hiển thị thông tin Jira user hiện tại để agent biết account đang thao tác, accountId và ngữ cảnh auth/runtime.",
|
|
29
|
+
parameters: { type: "object", properties: {} },
|
|
30
|
+
async execute() {
|
|
31
|
+
try {
|
|
32
|
+
const data = await client.request(`/rest/api/3/myself`);
|
|
33
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
}, { optional: true });
|
|
40
|
+
api.registerTool({
|
|
41
|
+
name: "jira_serverinfo",
|
|
42
|
+
label: "Jira Server Info",
|
|
43
|
+
description: "Hiển thị thông tin Jira instance/server để xác minh đang thao tác đúng Jira site, version và deployment context.",
|
|
44
|
+
parameters: { type: "object", properties: {} },
|
|
45
|
+
async execute() {
|
|
46
|
+
try {
|
|
47
|
+
const data = await client.request(`/rest/api/3/serverInfo`);
|
|
48
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
}, { optional: true });
|
|
55
|
+
api.registerTool({
|
|
56
|
+
name: "jira_project_list",
|
|
57
|
+
label: "Jira Project List",
|
|
58
|
+
description: "Liệt kê các Jira project mà account hiện tại có quyền truy cập; dùng khi cần chọn đúng project key trước khi create/list issue.",
|
|
59
|
+
parameters: { type: "object", properties: {} },
|
|
60
|
+
async execute() {
|
|
61
|
+
try {
|
|
62
|
+
const data = await client.request(`/rest/api/3/project`);
|
|
63
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
64
|
+
}
|
|
65
|
+
catch (e) {
|
|
66
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}, { optional: true });
|
|
70
|
+
api.registerTool({
|
|
71
|
+
name: "jira_project_view",
|
|
72
|
+
label: "Jira Project View",
|
|
73
|
+
description: "Xem chi tiết một Jira project, gồm metadata project và issue types khả dụng trong project đó.",
|
|
74
|
+
parameters: { type: "object", properties: { project: { type: "string" } }, required: ["project"] },
|
|
75
|
+
async execute(_id, params) {
|
|
76
|
+
try {
|
|
77
|
+
const data = await client.getProject(params.project);
|
|
78
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
}, { optional: true });
|
|
85
|
+
api.registerTool({
|
|
86
|
+
name: "jira_issue_list",
|
|
87
|
+
label: "Jira Issue List",
|
|
88
|
+
description: "Liệt kê issues theo JQL hoặc filter project/status/assignee; dùng khi cần tra backlog, tìm issue tồn tại hoặc kiểm tra trạng thái trước khi thao tác tiếp.",
|
|
89
|
+
parameters: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
jql: { type: "string" },
|
|
93
|
+
project: { type: "string" },
|
|
94
|
+
assignee: { type: "string" },
|
|
95
|
+
status: { type: "string" },
|
|
96
|
+
maxResults: { type: "number" },
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
async execute(_id, params) {
|
|
100
|
+
try {
|
|
101
|
+
const clauses = [];
|
|
102
|
+
if (params?.project)
|
|
103
|
+
clauses.push(`project=${params.project}`);
|
|
104
|
+
if (params?.assignee)
|
|
105
|
+
clauses.push(`assignee=\"${params.assignee}\"`);
|
|
106
|
+
if (params?.status)
|
|
107
|
+
clauses.push(`status=\"${params.status}\"`);
|
|
108
|
+
const jql = params?.jql || clauses.join(" AND ") || `project=${rawConfig.defaultProject || "TAA"}`;
|
|
109
|
+
const q = new URLSearchParams({ jql, maxResults: String(params?.maxResults || 50), fields: "summary,status,assignee,issuetype,parent,labels,updated" });
|
|
110
|
+
const data = await client.request(`/rest/api/3/search/jql?${q.toString()}`);
|
|
111
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
}, { optional: true });
|
|
118
|
+
api.registerTool({
|
|
119
|
+
name: "jira_issue_view",
|
|
120
|
+
label: "Jira Issue View",
|
|
121
|
+
description: "Xem chi tiết một issue Jira, gồm fields, links, parent/epic và metadata liên quan để agent có đủ context trước khi edit/comment/move.",
|
|
122
|
+
parameters: { type: "object", properties: { issueKey: { type: "string" } }, required: ["issueKey"] },
|
|
123
|
+
async execute(_id, params) {
|
|
124
|
+
try {
|
|
125
|
+
const data = await client.getIssue(params.issueKey);
|
|
126
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
}, { optional: true });
|
|
133
|
+
api.registerTool({
|
|
134
|
+
name: "jira_open",
|
|
135
|
+
label: "Jira Open",
|
|
136
|
+
description: "Trả về URL mở issue trên Jira giống hành vi jira open; dùng khi cần gửi link bấm được thay vì mở browser cục bộ.",
|
|
137
|
+
parameters: { type: "object", properties: { issueKey: { type: "string" } }, required: ["issueKey"] },
|
|
138
|
+
async execute(_id, params) {
|
|
139
|
+
try {
|
|
140
|
+
return createToolResult(JSON.stringify({ ok: true, issueKey: params.issueKey, url: client.issueUrl(params.issueKey) }, null, 2));
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
}, { optional: true });
|
|
147
|
+
api.registerTool({
|
|
148
|
+
name: "jira_issue_edit",
|
|
149
|
+
label: "Jira Issue Edit",
|
|
150
|
+
description: "Sửa summary, description hoặc labels của issue; dùng khi cần cập nhật nội dung issue mà không tạo issue mới.",
|
|
151
|
+
parameters: {
|
|
152
|
+
type: "object",
|
|
153
|
+
properties: {
|
|
154
|
+
issueKey: { type: "string" },
|
|
155
|
+
summary: { type: "string" },
|
|
156
|
+
description: { type: "string" },
|
|
157
|
+
labels: { type: "array", items: { type: "string" } },
|
|
158
|
+
},
|
|
159
|
+
required: ["issueKey"],
|
|
160
|
+
},
|
|
161
|
+
async execute(_id, params) {
|
|
162
|
+
try {
|
|
163
|
+
const fields = {};
|
|
164
|
+
if (params.summary)
|
|
165
|
+
fields.summary = params.summary;
|
|
166
|
+
if (params.description)
|
|
167
|
+
fields.description = client.buildDescription("Update", params.summary || params.issueKey, params.description, params.description, params.description, []);
|
|
168
|
+
if (params.labels)
|
|
169
|
+
fields.labels = params.labels;
|
|
170
|
+
const data = await client.request(`/rest/api/3/issue/${params.issueKey}`, { method: "PUT", body: JSON.stringify({ fields }) });
|
|
171
|
+
return createToolResult(JSON.stringify({ ok: true, result: data || {} }, null, 2));
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
}, { optional: true });
|
|
178
|
+
api.registerTool({
|
|
179
|
+
name: "jira_issue_assign",
|
|
180
|
+
label: "Jira Issue Assign",
|
|
181
|
+
description: "Assign issue cho user theo email hoặc display name; tool sẽ resolve accountId trước khi gọi Jira API.",
|
|
182
|
+
parameters: {
|
|
183
|
+
type: "object",
|
|
184
|
+
properties: {
|
|
185
|
+
issueKey: { type: "string" },
|
|
186
|
+
assigneeEmail: { type: "string" },
|
|
187
|
+
assigneeName: { type: "string" },
|
|
188
|
+
},
|
|
189
|
+
required: ["issueKey"],
|
|
190
|
+
},
|
|
191
|
+
async execute(_id, params) {
|
|
192
|
+
try {
|
|
193
|
+
const accountId = await client.resolveAssigneeAccountId(params.assigneeEmail, params.assigneeName);
|
|
194
|
+
const data = await client.request(`/rest/api/3/issue/${params.issueKey}/assignee`, { method: "PUT", body: JSON.stringify({ accountId: accountId || null }) });
|
|
195
|
+
return createToolResult(JSON.stringify({ ok: true, result: data || {} }, null, 2));
|
|
196
|
+
}
|
|
197
|
+
catch (e) {
|
|
198
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
}, { optional: true });
|
|
202
|
+
api.registerTool({
|
|
203
|
+
name: "jira_issue_comment_add",
|
|
204
|
+
label: "Jira Issue Comment Add",
|
|
205
|
+
description: "Thêm comment vào issue bằng tiếng Việt; dùng cho cập nhật tiến độ, bằng chứng, blocker hoặc note nội bộ.",
|
|
206
|
+
parameters: { type: "object", properties: { issueKey: { type: "string" }, body: { type: "string" }, internal: { type: "boolean" } }, required: ["issueKey", "body"] },
|
|
207
|
+
async execute(_id, params) {
|
|
208
|
+
try {
|
|
209
|
+
const payload = {
|
|
210
|
+
body: {
|
|
211
|
+
type: "doc",
|
|
212
|
+
version: 1,
|
|
213
|
+
content: [{ type: "paragraph", content: [{ type: "text", text: params.body }] }],
|
|
214
|
+
},
|
|
215
|
+
};
|
|
216
|
+
if (params.internal)
|
|
217
|
+
payload.properties = [{ key: "sd.public.comment", value: { internal: true } }];
|
|
218
|
+
const data = await client.request(`/rest/api/3/issue/${params.issueKey}/comment`, { method: "POST", body: JSON.stringify(payload) });
|
|
219
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
220
|
+
}
|
|
221
|
+
catch (e) {
|
|
222
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
223
|
+
}
|
|
224
|
+
},
|
|
225
|
+
}, { optional: true });
|
|
226
|
+
api.registerTool({
|
|
227
|
+
name: "jira_issue_clone",
|
|
228
|
+
label: "Jira Issue Clone",
|
|
229
|
+
description: "Clone issue tương tự jira-cli clone và cho phép override summary, assignee, labels, parent, priority hoặc replace text trong summary/body.",
|
|
230
|
+
parameters: {
|
|
231
|
+
type: "object",
|
|
232
|
+
properties: {
|
|
233
|
+
issueKey: { type: "string" },
|
|
234
|
+
parent: { type: "string" },
|
|
235
|
+
summary: { type: "string" },
|
|
236
|
+
priority: { type: "string" },
|
|
237
|
+
assigneeEmail: { type: "string" },
|
|
238
|
+
assigneeName: { type: "string" },
|
|
239
|
+
labels: { type: "array", items: { type: "string" } },
|
|
240
|
+
replace: { type: "array", items: { type: "string" }, description: "Format search:replace" }
|
|
241
|
+
},
|
|
242
|
+
required: ["issueKey"],
|
|
243
|
+
},
|
|
244
|
+
async execute(_id, params) {
|
|
245
|
+
try {
|
|
246
|
+
const source = await client.getIssue(params.issueKey);
|
|
247
|
+
const fields = source.fields || {};
|
|
248
|
+
let summary = params.summary || fields.summary || `Clone of ${params.issueKey}`;
|
|
249
|
+
let bodyText = JSON.stringify(fields.description || {});
|
|
250
|
+
for (const pair of params.replace || []) {
|
|
251
|
+
const [search, ...rest] = String(pair).split(":");
|
|
252
|
+
const repl = rest.join(":");
|
|
253
|
+
if (search) {
|
|
254
|
+
summary = summary.split(search).join(repl);
|
|
255
|
+
bodyText = bodyText.split(search).join(repl);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const assigneeAccountId = await client.resolveAssigneeAccountId(params.assigneeEmail, params.assigneeName);
|
|
259
|
+
const cloneFields = {
|
|
260
|
+
project: fields.project,
|
|
261
|
+
summary,
|
|
262
|
+
issuetype: { id: fields.issuetype?.id },
|
|
263
|
+
description: fields.description,
|
|
264
|
+
labels: params.labels || fields.labels || [],
|
|
265
|
+
};
|
|
266
|
+
if (assigneeAccountId)
|
|
267
|
+
cloneFields.assignee = { accountId: assigneeAccountId };
|
|
268
|
+
if (params.parent)
|
|
269
|
+
cloneFields.parent = { key: params.parent };
|
|
270
|
+
if (params.priority)
|
|
271
|
+
cloneFields.priority = { name: params.priority };
|
|
272
|
+
const createMeta = await client.getCreateMeta(fields.project?.key || rawConfig.defaultProject || "TAA", fields.issuetype?.name || "Task");
|
|
273
|
+
const sanitized = client.sanitizeFields(cloneFields, createMeta);
|
|
274
|
+
const data = await client.createIssue(sanitized);
|
|
275
|
+
return createToolResult(JSON.stringify({ ok: true, key: data.key, url: client.issueUrl(data.key), result: data }, null, 2));
|
|
276
|
+
}
|
|
277
|
+
catch (e) {
|
|
278
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
}, { optional: true });
|
|
282
|
+
api.registerTool({
|
|
283
|
+
name: "jira_issue_delete",
|
|
284
|
+
label: "Jira Issue Delete",
|
|
285
|
+
description: "Xóa issue Jira khi user yêu cầu rõ ràng hoặc cần cleanup issue test; đây là thao tác có side effect mạnh.",
|
|
286
|
+
parameters: { type: "object", properties: { issueKey: { type: "string" } }, required: ["issueKey"] },
|
|
287
|
+
async execute(_id, params) {
|
|
288
|
+
try {
|
|
289
|
+
const data = await client.request(`/rest/api/3/issue/${params.issueKey}`, { method: "DELETE" });
|
|
290
|
+
return createToolResult(JSON.stringify({ ok: true, result: data || {} }, null, 2));
|
|
291
|
+
}
|
|
292
|
+
catch (e) {
|
|
293
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
294
|
+
}
|
|
295
|
+
},
|
|
296
|
+
}, { optional: true });
|
|
297
|
+
api.registerTool({
|
|
298
|
+
name: "jira_issue_watch",
|
|
299
|
+
label: "Jira Issue Watch",
|
|
300
|
+
description: "Thêm watcher vào issue theo email hoặc display name để người liên quan theo dõi thay đổi của issue.",
|
|
301
|
+
parameters: {
|
|
302
|
+
type: "object",
|
|
303
|
+
properties: {
|
|
304
|
+
issueKey: { type: "string" },
|
|
305
|
+
watcherEmail: { type: "string" },
|
|
306
|
+
watcherName: { type: "string" },
|
|
307
|
+
},
|
|
308
|
+
required: ["issueKey"],
|
|
309
|
+
},
|
|
310
|
+
async execute(_id, params) {
|
|
311
|
+
try {
|
|
312
|
+
const accountId = await client.resolveAssigneeAccountId(params.watcherEmail, params.watcherName);
|
|
313
|
+
const data = await client.request(`/rest/api/3/issue/${params.issueKey}/watchers`, { method: "POST", body: JSON.stringify(accountId) });
|
|
314
|
+
return createToolResult(JSON.stringify({ ok: true, watcherAccountId: accountId, result: data || {} }, null, 2));
|
|
315
|
+
}
|
|
316
|
+
catch (e) {
|
|
317
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
}, { optional: true });
|
|
321
|
+
api.registerTool({
|
|
322
|
+
name: "jira_issue_move",
|
|
323
|
+
label: "Jira Issue Move",
|
|
324
|
+
description: "Transition issue sang trạng thái khác bằng transition id; dùng khi workflow Jira yêu cầu move state chính xác.",
|
|
325
|
+
parameters: { type: "object", properties: { issueKey: { type: "string" }, transitionId: { type: "string" } }, required: ["issueKey", "transitionId"] },
|
|
326
|
+
async execute(_id, params) {
|
|
327
|
+
try {
|
|
328
|
+
const data = await client.request(`/rest/api/3/issue/${params.issueKey}/transitions`, { method: "POST", body: JSON.stringify({ transition: { id: params.transitionId } }) });
|
|
329
|
+
return createToolResult(JSON.stringify({ ok: true, result: data || {} }, null, 2));
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
}, { optional: true });
|
|
336
|
+
api.registerTool({
|
|
337
|
+
name: "jira_issue_worklog_add",
|
|
338
|
+
label: "Jira Issue Worklog Add",
|
|
339
|
+
description: "Thêm worklog vào issue giống jira issue worklog add; hỗ trợ timeSpent, started, comment và newEstimate.",
|
|
340
|
+
parameters: {
|
|
341
|
+
type: "object",
|
|
342
|
+
properties: {
|
|
343
|
+
issueKey: { type: "string" },
|
|
344
|
+
timeSpent: { type: "string" },
|
|
345
|
+
started: { type: "string" },
|
|
346
|
+
timezone: { type: "string" },
|
|
347
|
+
comment: { type: "string" },
|
|
348
|
+
newEstimate: { type: "string" }
|
|
349
|
+
},
|
|
350
|
+
required: ["issueKey", "timeSpent"]
|
|
351
|
+
},
|
|
352
|
+
async execute(_id, params) {
|
|
353
|
+
try {
|
|
354
|
+
const payload = { timeSpent: params.timeSpent };
|
|
355
|
+
if (params.started)
|
|
356
|
+
payload.started = params.started;
|
|
357
|
+
if (params.comment) {
|
|
358
|
+
payload.comment = {
|
|
359
|
+
type: "doc",
|
|
360
|
+
version: 1,
|
|
361
|
+
content: [{ type: "paragraph", content: [{ type: "text", text: params.comment }] }],
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
if (params.newEstimate)
|
|
365
|
+
payload.adjustEstimate = "new";
|
|
366
|
+
if (params.newEstimate)
|
|
367
|
+
payload.newEstimate = params.newEstimate;
|
|
368
|
+
const data = await client.request(`/rest/api/3/issue/${params.issueKey}/worklog`, { method: "POST", body: JSON.stringify(payload) });
|
|
369
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
370
|
+
}
|
|
371
|
+
catch (e) {
|
|
372
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
}, { optional: true });
|
|
376
|
+
api.registerTool({
|
|
377
|
+
name: "jira_issue_link",
|
|
378
|
+
label: "Jira Issue Link",
|
|
379
|
+
description: "Link hai issue với link type phù hợp như relates to hoặc blocks để giữ đúng ngữ nghĩa quan hệ trong Jira.",
|
|
380
|
+
parameters: {
|
|
381
|
+
type: "object",
|
|
382
|
+
properties: {
|
|
383
|
+
inwardIssueKey: { type: "string" },
|
|
384
|
+
outwardIssueKey: { type: "string" },
|
|
385
|
+
linkType: { type: "string" },
|
|
386
|
+
comment: { type: "string" },
|
|
387
|
+
},
|
|
388
|
+
required: ["inwardIssueKey", "outwardIssueKey"],
|
|
389
|
+
},
|
|
390
|
+
async execute(_id, params) {
|
|
391
|
+
try {
|
|
392
|
+
const data = await client.request(`/rest/api/3/issueLink`, {
|
|
393
|
+
method: "POST",
|
|
394
|
+
body: JSON.stringify({
|
|
395
|
+
type: { name: params.linkType || "Relates" },
|
|
396
|
+
inwardIssue: { key: params.inwardIssueKey },
|
|
397
|
+
outwardIssue: { key: params.outwardIssueKey },
|
|
398
|
+
...(params.comment ? { comment: { body: { type: "doc", version: 1, content: [{ type: "paragraph", content: [{ type: "text", text: params.comment }] }] } } } : {}),
|
|
399
|
+
}),
|
|
400
|
+
});
|
|
401
|
+
return createToolResult(JSON.stringify({ ok: true, result: data || {} }, null, 2));
|
|
402
|
+
}
|
|
403
|
+
catch (e) {
|
|
404
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
}, { optional: true });
|
|
408
|
+
api.registerTool({
|
|
409
|
+
name: "jira_issue_unlink",
|
|
410
|
+
label: "Jira Issue Unlink",
|
|
411
|
+
description: "Xóa issue link bằng link id khi quan hệ giữa hai issue không còn đúng hoặc cần cleanup link sai.",
|
|
412
|
+
parameters: { type: "object", properties: { linkId: { type: "string" } }, required: ["linkId"] },
|
|
413
|
+
async execute(_id, params) {
|
|
414
|
+
try {
|
|
415
|
+
const data = await client.request(`/rest/api/3/issueLink/${params.linkId}`, { method: "DELETE" });
|
|
416
|
+
return createToolResult(JSON.stringify({ ok: true, result: data || {} }, null, 2));
|
|
417
|
+
}
|
|
418
|
+
catch (e) {
|
|
419
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
420
|
+
}
|
|
421
|
+
},
|
|
422
|
+
}, { optional: true });
|
|
423
|
+
api.registerTool({
|
|
424
|
+
name: "jira_epic_list",
|
|
425
|
+
label: "Jira Epic List",
|
|
426
|
+
description: "Liệt kê epics trong project để agent chọn epic đúng trước khi gắn issue hoặc rà soát phạm vi lớn.",
|
|
427
|
+
parameters: { type: "object", properties: { project: { type: "string" }, maxResults: { type: "number" } } },
|
|
428
|
+
async execute(_id, params) {
|
|
429
|
+
try {
|
|
430
|
+
const project = params?.project || rawConfig.defaultProject || "TAA";
|
|
431
|
+
const q = new URLSearchParams({ jql: `project=${project} AND issuetype=\"${rawConfig.issueTypeMap?.epic || "Epic"}\"`, maxResults: String(params?.maxResults || 50), fields: "summary,status,assignee,labels" });
|
|
432
|
+
const data = await client.request(`/rest/api/3/search/jql?${q.toString()}`);
|
|
433
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
434
|
+
}
|
|
435
|
+
catch (e) {
|
|
436
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
}, { optional: true });
|
|
440
|
+
api.registerTool({
|
|
441
|
+
name: "jira_epic_view",
|
|
442
|
+
label: "Jira Epic View",
|
|
443
|
+
description: "Xem chi tiết epic và danh sách issue con đang thuộc epic đó để phục vụ planning, audit và điều phối backlog.",
|
|
444
|
+
parameters: { type: "object", properties: { epicKey: { type: "string" }, maxResults: { type: "number" } }, required: ["epicKey"] },
|
|
445
|
+
async execute(_id, params) {
|
|
446
|
+
try {
|
|
447
|
+
const epic = await client.getIssue(params.epicKey);
|
|
448
|
+
const epicLinkField = client["config"].fieldMappings.epicLink;
|
|
449
|
+
const q = new URLSearchParams({ jql: `\"${epicLinkField}\"=${params.epicKey}`, maxResults: String(params?.maxResults || 50), fields: "summary,status,assignee,issuetype,parent,labels" });
|
|
450
|
+
const children = await client.request(`/rest/api/3/search/jql?${q.toString()}`);
|
|
451
|
+
return createToolResult(JSON.stringify({ epic, children }, null, 2));
|
|
452
|
+
}
|
|
453
|
+
catch (e) {
|
|
454
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
455
|
+
}
|
|
456
|
+
},
|
|
457
|
+
}, { optional: true });
|
|
458
|
+
api.registerTool({
|
|
459
|
+
name: "jira_epic_add",
|
|
460
|
+
label: "Jira Epic Add",
|
|
461
|
+
description: "Gán một hoặc nhiều issue vào epic qua Epic Link field.",
|
|
462
|
+
parameters: { type: "object", properties: { epicKey: { type: "string" }, issueKeys: { type: "array", items: { type: "string" } } }, required: ["epicKey", "issueKeys"] },
|
|
463
|
+
async execute(_id, params) {
|
|
464
|
+
try {
|
|
465
|
+
const results = [];
|
|
466
|
+
for (const issueKey of params.issueKeys || []) {
|
|
467
|
+
const out = await client.request(`/rest/api/3/issue/${issueKey}`, { method: "PUT", body: JSON.stringify({ fields: { [client["config"].fieldMappings.epicLink]: params.epicKey } }) });
|
|
468
|
+
results.push({ issueKey, result: out || {} });
|
|
469
|
+
}
|
|
470
|
+
return createToolResult(JSON.stringify({ ok: true, results }, null, 2));
|
|
471
|
+
}
|
|
472
|
+
catch (e) {
|
|
473
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
}, { optional: true });
|
|
477
|
+
api.registerTool({
|
|
478
|
+
name: "jira_epic_remove",
|
|
479
|
+
label: "Jira Epic Remove",
|
|
480
|
+
description: "Bỏ Epic Link khỏi một hoặc nhiều issue khi cần tách khỏi epic hiện tại.",
|
|
481
|
+
parameters: { type: "object", properties: { issueKeys: { type: "array", items: { type: "string" } } }, required: ["issueKeys"] },
|
|
482
|
+
async execute(_id, params) {
|
|
483
|
+
try {
|
|
484
|
+
const results = [];
|
|
485
|
+
for (const issueKey of params.issueKeys || []) {
|
|
486
|
+
const out = await client.request(`/rest/api/3/issue/${issueKey}`, { method: "PUT", body: JSON.stringify({ fields: { [client["config"].fieldMappings.epicLink]: null } }) });
|
|
487
|
+
results.push({ issueKey, result: out || {} });
|
|
488
|
+
}
|
|
489
|
+
return createToolResult(JSON.stringify({ ok: true, results }, null, 2));
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
}, { optional: true });
|
|
496
|
+
api.registerTool({
|
|
497
|
+
name: "jira_board_list",
|
|
498
|
+
label: "Jira Board List",
|
|
499
|
+
description: "Liệt kê boards của project để agent có thể tìm đúng board trước khi xem sprint hoặc add issue vào sprint.",
|
|
500
|
+
parameters: { type: "object", properties: { project: { type: "string" }, maxResults: { type: "number" } } },
|
|
501
|
+
async execute(_id, params) {
|
|
502
|
+
try {
|
|
503
|
+
const q = new URLSearchParams({ projectKeyOrId: params?.project || rawConfig.defaultProject || "TAA", maxResults: String(params?.maxResults || 50) });
|
|
504
|
+
const data = await client.request(`/rest/agile/1.0/board?${q.toString()}`);
|
|
505
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
506
|
+
}
|
|
507
|
+
catch (e) {
|
|
508
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
509
|
+
}
|
|
510
|
+
},
|
|
511
|
+
}, { optional: true });
|
|
512
|
+
api.registerTool({
|
|
513
|
+
name: "jira_sprint_list",
|
|
514
|
+
label: "Jira Sprint List",
|
|
515
|
+
description: "Liệt kê sprints của board theo state để agent tra sprint hiện tại, sprint active hoặc sprint closed.",
|
|
516
|
+
parameters: { type: "object", properties: { boardId: { type: "number" }, state: { type: "string" } }, required: ["boardId"] },
|
|
517
|
+
async execute(_id, params) {
|
|
518
|
+
try {
|
|
519
|
+
const q = new URLSearchParams(params?.state ? { state: params.state } : {});
|
|
520
|
+
const data = await client.request(`/rest/agile/1.0/board/${params.boardId}/sprint${q.toString() ? `?${q.toString()}` : ""}`);
|
|
521
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
522
|
+
}
|
|
523
|
+
catch (e) {
|
|
524
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
}, { optional: true });
|
|
528
|
+
api.registerTool({
|
|
529
|
+
name: "jira_sprint_add",
|
|
530
|
+
label: "Jira Sprint Add",
|
|
531
|
+
description: "Thêm một hoặc nhiều issue vào sprint đã biết sprintId.",
|
|
532
|
+
parameters: { type: "object", properties: { sprintId: { type: "number" }, issueKeys: { type: "array", items: { type: "string" } } }, required: ["sprintId", "issueKeys"] },
|
|
533
|
+
async execute(_id, params) {
|
|
534
|
+
try {
|
|
535
|
+
const data = await client.request(`/rest/agile/1.0/sprint/${params.sprintId}/issue`, { method: "POST", body: JSON.stringify({ issues: params.issueKeys }) });
|
|
536
|
+
return createToolResult(JSON.stringify({ ok: true, result: data || {} }, null, 2));
|
|
537
|
+
}
|
|
538
|
+
catch (e) {
|
|
539
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
}, { optional: true });
|
|
543
|
+
api.registerTool({
|
|
544
|
+
name: "jira_sprint_close",
|
|
545
|
+
label: "Jira Sprint Close",
|
|
546
|
+
description: "Đóng sprint bằng state=closed khi user yêu cầu hoàn tất sprint.",
|
|
547
|
+
parameters: { type: "object", properties: { sprintId: { type: "number" } }, required: ["sprintId"] },
|
|
548
|
+
async execute(_id, params) {
|
|
549
|
+
try {
|
|
550
|
+
const data = await client.request(`/rest/agile/1.0/sprint/${params.sprintId}`, { method: "PUT", body: JSON.stringify({ state: "closed" }) });
|
|
551
|
+
return createToolResult(JSON.stringify({ ok: true, result: data || {} }, null, 2));
|
|
552
|
+
}
|
|
553
|
+
catch (e) {
|
|
554
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
}, { optional: true });
|
|
558
|
+
api.registerTool({
|
|
559
|
+
name: "jira_release_list",
|
|
560
|
+
label: "Jira Release List",
|
|
561
|
+
description: "Liệt kê versions/releases của project để phục vụ release planning hoặc gắn version cho issue.",
|
|
562
|
+
parameters: { type: "object", properties: { project: { type: "string" } } },
|
|
563
|
+
async execute(_id, params) {
|
|
564
|
+
try {
|
|
565
|
+
const project = params?.project || rawConfig.defaultProject || "TAA";
|
|
566
|
+
const data = await client.request(`/rest/api/3/project/${project}/versions`);
|
|
567
|
+
return createToolResult(JSON.stringify(data, null, 2));
|
|
568
|
+
}
|
|
569
|
+
catch (e) {
|
|
570
|
+
return createToolResult(`Error: ${e instanceof Error ? e.message : String(e)}`, true);
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
}, { optional: true });
|
|
574
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "jira-tools",
|
|
3
|
+
"name": "Jira Tools",
|
|
4
|
+
"description": "Native Jira agent tools for OpenClaw with config schema and per-agent defaults.",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"configSchema": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"additionalProperties": false,
|
|
9
|
+
"properties": {
|
|
10
|
+
"server": { "type": "string", "description": "Base Jira URL, e.g. https://your-domain.atlassian.net" },
|
|
11
|
+
"email": { "type": "string", "description": "Jira account email for basic auth" },
|
|
12
|
+
"token": { "type": "string", "description": "Jira API token" },
|
|
13
|
+
"defaultProject": { "type": "string", "description": "Default Jira project key", "default": "TAA" },
|
|
14
|
+
"language": { "type": "string", "description": "Content language", "default": "vi" },
|
|
15
|
+
"requireClickableLinks": { "type": "boolean", "description": "Force related links section in descriptions", "default": true },
|
|
16
|
+
"issueTypeMap": {
|
|
17
|
+
"type": "object",
|
|
18
|
+
"additionalProperties": false,
|
|
19
|
+
"properties": {
|
|
20
|
+
"task": { "type": "string", "default": "Task" },
|
|
21
|
+
"epic": { "type": "string", "default": "Epic" },
|
|
22
|
+
"subtask": { "type": "string", "default": "Subtask" }
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"fieldMappings": {
|
|
26
|
+
"type": "object",
|
|
27
|
+
"additionalProperties": false,
|
|
28
|
+
"properties": {
|
|
29
|
+
"epicName": { "type": "string", "default": "customfield_10011" },
|
|
30
|
+
"epicLink": { "type": "string", "default": "customfield_10014" },
|
|
31
|
+
"parentLink": { "type": "string", "default": "customfield_10018" }
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"required": ["server", "email", "token"]
|
|
36
|
+
},
|
|
37
|
+
"uiHints": {
|
|
38
|
+
"server": { "label": "Jira Server", "placeholder": "https://linktovn.atlassian.net" },
|
|
39
|
+
"email": { "label": "Jira Email", "placeholder": "name@example.com" },
|
|
40
|
+
"token": { "label": "Jira API Token", "sensitive": true },
|
|
41
|
+
"defaultProject": { "label": "Default Project", "placeholder": "TAA" },
|
|
42
|
+
"language": { "label": "Language", "placeholder": "vi" },
|
|
43
|
+
"requireClickableLinks": { "label": "Require Clickable Links" }
|
|
44
|
+
}
|
|
45
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mrc2204/openclaw-jira-tools",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Native Jira tools plugin for OpenClaw",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/cong91/openclaw-jira-tools.git"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"openclaw": {
|
|
16
|
+
"extensions": [
|
|
17
|
+
"./dist/index.js"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc -p tsconfig.json",
|
|
22
|
+
"clean": "rm -rf dist"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@sinclair/typebox": "^0.34.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"openclaw": "*",
|
|
29
|
+
"typescript": "^5.7.3",
|
|
30
|
+
"@types/node": "^22.13.10"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist/",
|
|
34
|
+
"openclaw.plugin.json",
|
|
35
|
+
"README.md",
|
|
36
|
+
"README.en.md",
|
|
37
|
+
"skill/"
|
|
38
|
+
]
|
|
39
|
+
}
|