@geeveeh/atlassian-tools 0.2.0 → 0.4.0
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.md +156 -26
- package/dist/cli/confluence.d.ts.map +1 -1
- package/dist/cli/confluence.js +424 -9
- package/dist/cli/confluence.js.map +1 -1
- package/dist/cli/jira.d.ts.map +1 -1
- package/dist/cli/jira.js +518 -2
- package/dist/cli/jira.js.map +1 -1
- package/dist/confluence/client.d.ts +15 -1
- package/dist/confluence/client.d.ts.map +1 -1
- package/dist/confluence/client.js +114 -0
- package/dist/confluence/client.js.map +1 -1
- package/dist/confluence/index.d.ts +1 -0
- package/dist/confluence/index.d.ts.map +1 -1
- package/dist/confluence/index.js +1 -0
- package/dist/confluence/index.js.map +1 -1
- package/dist/confluence/markdown.d.ts +15 -0
- package/dist/confluence/markdown.d.ts.map +1 -0
- package/dist/confluence/markdown.js +83 -0
- package/dist/confluence/markdown.js.map +1 -0
- package/dist/confluence/types.d.ts +91 -0
- package/dist/confluence/types.d.ts.map +1 -1
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/auth.js +54 -10
- package/dist/core/auth.js.map +1 -1
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +57 -18
- package/dist/core/client.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/mime.d.ts +2 -0
- package/dist/core/mime.d.ts.map +1 -0
- package/dist/core/mime.js +21 -0
- package/dist/core/mime.js.map +1 -0
- package/dist/jira/client.d.ts +20 -1
- package/dist/jira/client.d.ts.map +1 -1
- package/dist/jira/client.js +127 -0
- package/dist/jira/client.js.map +1 -1
- package/dist/jira/types.d.ts +115 -0
- package/dist/jira/types.d.ts.map +1 -1
- package/dist/mcp/confluence.d.ts +3 -0
- package/dist/mcp/confluence.d.ts.map +1 -0
- package/dist/mcp/confluence.js +405 -0
- package/dist/mcp/confluence.js.map +1 -0
- package/dist/mcp/helpers.d.ts +3 -0
- package/dist/mcp/helpers.d.ts.map +1 -0
- package/dist/mcp/helpers.js +15 -0
- package/dist/mcp/helpers.js.map +1 -0
- package/dist/mcp/index.js +8 -354
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/jira.d.ts +3 -0
- package/dist/mcp/jira.d.ts.map +1 -0
- package/dist/mcp/jira.js +479 -0
- package/dist/mcp/jira.js.map +1 -0
- package/package.json +3 -3
package/dist/cli/jira.js
CHANGED
|
@@ -40,10 +40,11 @@ export function registerJiraCommands(program) {
|
|
|
40
40
|
.command("projects")
|
|
41
41
|
.description("List available projects")
|
|
42
42
|
.option("-l, --limit <n>", "Max projects to return", "25")
|
|
43
|
+
.option("--all", "Fetch up to 50 results (Jira default max per request)")
|
|
43
44
|
.action(async (opts) => {
|
|
44
45
|
try {
|
|
45
46
|
const client = createClient();
|
|
46
|
-
const projects = await client.listProjects(Number(opts.limit));
|
|
47
|
+
const projects = await client.listProjects(opts.all ? 50 : Number(opts.limit));
|
|
47
48
|
for (const p of projects) {
|
|
48
49
|
console.log(`${chalk.bold(p.name)} ${chalk.dim(`[${p.key}] id:${p.id}`)}`);
|
|
49
50
|
}
|
|
@@ -52,6 +53,248 @@ export function registerJiraCommands(program) {
|
|
|
52
53
|
handleError(err);
|
|
53
54
|
}
|
|
54
55
|
});
|
|
56
|
+
jira
|
|
57
|
+
.command("epic <epicKey>")
|
|
58
|
+
.description("List all issues belonging to an epic")
|
|
59
|
+
.option("-l, --limit <n>", "Max results", "50")
|
|
60
|
+
.option("--all", "Fetch up to 100 results per request")
|
|
61
|
+
.action(async (epicKey, opts) => {
|
|
62
|
+
try {
|
|
63
|
+
const issues = await createClient().listEpicIssues(epicKey, opts.all ? 100 : Number(opts.limit));
|
|
64
|
+
if (issues.length === 0) {
|
|
65
|
+
console.log(chalk.yellow("No issues found in this epic."));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
for (const i of issues) {
|
|
69
|
+
console.log(formatIssue(i));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
handleError(err);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
jira
|
|
77
|
+
.command("boards")
|
|
78
|
+
.description("List Jira boards")
|
|
79
|
+
.option("-l, --limit <n>", "Max results", "25")
|
|
80
|
+
.action(async (opts) => {
|
|
81
|
+
try {
|
|
82
|
+
const boards = await createClient().listBoards(Number(opts.limit));
|
|
83
|
+
if (boards.length === 0) {
|
|
84
|
+
console.log(chalk.yellow("No boards found."));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const b of boards) {
|
|
88
|
+
const proj = b.location ? chalk.dim(` — ${b.location.projectName} [${b.location.projectKey}]`) : "";
|
|
89
|
+
console.log(`${chalk.bold(b.name)}${proj} ${chalk.dim(`(id: ${b.id}, ${b.type})`)}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
handleError(err);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
jira
|
|
97
|
+
.command("sprints <boardId>")
|
|
98
|
+
.description("List sprints on a board")
|
|
99
|
+
.option("--state <state>", "Filter by state: active, future, closed")
|
|
100
|
+
.action(async (boardId, opts) => {
|
|
101
|
+
try {
|
|
102
|
+
const state = opts.state;
|
|
103
|
+
const sprints = await createClient().listSprints(Number(boardId), state);
|
|
104
|
+
if (sprints.length === 0) {
|
|
105
|
+
console.log(chalk.yellow("No sprints found."));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
for (const s of sprints) {
|
|
109
|
+
const dates = s.startDate && s.endDate
|
|
110
|
+
? chalk.dim(` (${new Date(s.startDate).toLocaleDateString()} – ${new Date(s.endDate).toLocaleDateString()})`)
|
|
111
|
+
: "";
|
|
112
|
+
const stateColour = s.state === "active" ? chalk.green(s.state) : chalk.dim(s.state);
|
|
113
|
+
console.log(`${chalk.bold(s.name)} — ${stateColour}${dates} ${chalk.dim(`(id: ${s.id})`)}`);
|
|
114
|
+
if (s.goal)
|
|
115
|
+
console.log(` ${chalk.dim(s.goal)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
handleError(err);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
jira
|
|
123
|
+
.command("move-to-sprint <sprintId> [issueKeys...]")
|
|
124
|
+
.description("Move one or more issues to a sprint")
|
|
125
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
126
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
127
|
+
.action(async (sprintId, issueKeys, opts) => {
|
|
128
|
+
try {
|
|
129
|
+
if (issueKeys.length === 0) {
|
|
130
|
+
console.error(chalk.red("Specify at least one issue key."));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
if (opts.dryRun) {
|
|
134
|
+
console.log(chalk.cyan("[dry run] Would move issues to sprint:"));
|
|
135
|
+
console.log(` Issues: ${issueKeys.join(", ")}`);
|
|
136
|
+
console.log(` Sprint: ${sprintId}`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (!opts.yes) {
|
|
140
|
+
const ok = await confirm(`Move ${issueKeys.join(", ")} to sprint ${sprintId}?`);
|
|
141
|
+
if (!ok) {
|
|
142
|
+
console.log(chalk.dim("Cancelled."));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
await createClient().moveToSprint(Number(sprintId), issueKeys);
|
|
147
|
+
console.log(chalk.green(`✓ Moved ${issueKeys.join(", ")} to sprint ${sprintId}.`));
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
handleError(err);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
jira
|
|
154
|
+
.command("subtasks <issueKey>")
|
|
155
|
+
.description("List subtasks of an issue")
|
|
156
|
+
.option("-l, --limit <n>", "Max results", "50")
|
|
157
|
+
.action(async (issueKey, opts) => {
|
|
158
|
+
try {
|
|
159
|
+
const subtasks = await createClient().listSubtasks(issueKey, Number(opts.limit));
|
|
160
|
+
if (subtasks.length === 0) {
|
|
161
|
+
console.log(chalk.yellow("No subtasks found."));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
for (const i of subtasks) {
|
|
165
|
+
console.log(formatIssue(i));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
handleError(err);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
jira
|
|
173
|
+
.command("create-sprint")
|
|
174
|
+
.description("Create a new sprint on a board")
|
|
175
|
+
.requiredOption("--board <id>", "Board ID")
|
|
176
|
+
.requiredOption("--name <name>", "Sprint name")
|
|
177
|
+
.option("--goal <text>", "Sprint goal")
|
|
178
|
+
.option("--start <date>", "Start date (ISO 8601)")
|
|
179
|
+
.option("--end <date>", "End date (ISO 8601)")
|
|
180
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
181
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
182
|
+
.action(async (opts) => {
|
|
183
|
+
try {
|
|
184
|
+
if (opts.dryRun) {
|
|
185
|
+
console.log(chalk.cyan("[dry run] Would create sprint:"));
|
|
186
|
+
console.log(` Board: ${opts.board}`);
|
|
187
|
+
console.log(` Name: ${opts.name}`);
|
|
188
|
+
if (opts.goal)
|
|
189
|
+
console.log(` Goal: ${opts.goal}`);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (!opts.yes) {
|
|
193
|
+
const ok = await confirm(`Create sprint "${opts.name}" on board ${opts.board}?`);
|
|
194
|
+
if (!ok) {
|
|
195
|
+
console.log(chalk.dim("Cancelled."));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const sprint = await createClient().createSprint({
|
|
200
|
+
boardId: Number(opts.board),
|
|
201
|
+
name: opts.name,
|
|
202
|
+
goal: opts.goal,
|
|
203
|
+
startDate: opts.start,
|
|
204
|
+
endDate: opts.end,
|
|
205
|
+
});
|
|
206
|
+
console.log(chalk.green(`✓ Created sprint: ${chalk.bold(sprint.name)} ${chalk.dim(`(id: ${sprint.id})`)}`));
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
handleError(err);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
jira
|
|
213
|
+
.command("update-sprint <sprintId>")
|
|
214
|
+
.description("Update a sprint's name, goal, or dates")
|
|
215
|
+
.option("--name <name>", "New sprint name")
|
|
216
|
+
.option("--goal <text>", "New sprint goal")
|
|
217
|
+
.option("--start <date>", "New start date (ISO 8601)")
|
|
218
|
+
.option("--end <date>", "New end date (ISO 8601)")
|
|
219
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
220
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
221
|
+
.action(async (sprintId, opts) => {
|
|
222
|
+
try {
|
|
223
|
+
if (opts.dryRun) {
|
|
224
|
+
console.log(chalk.cyan("[dry run] Would update sprint:"));
|
|
225
|
+
console.log(` Sprint: ${sprintId}`);
|
|
226
|
+
if (opts.name)
|
|
227
|
+
console.log(` Name: ${opts.name}`);
|
|
228
|
+
if (opts.goal)
|
|
229
|
+
console.log(` Goal: ${opts.goal}`);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (!opts.yes) {
|
|
233
|
+
const ok = await confirm(`Update sprint ${sprintId}?`);
|
|
234
|
+
if (!ok) {
|
|
235
|
+
console.log(chalk.dim("Cancelled."));
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const sprint = await createClient().updateSprint(Number(sprintId), {
|
|
240
|
+
name: opts.name,
|
|
241
|
+
goal: opts.goal,
|
|
242
|
+
startDate: opts.start,
|
|
243
|
+
endDate: opts.end,
|
|
244
|
+
});
|
|
245
|
+
console.log(chalk.green(`✓ Updated sprint: ${chalk.bold(sprint.name)} ${chalk.dim(`(id: ${sprint.id})`)}`));
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
handleError(err);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
jira
|
|
252
|
+
.command("close-sprint <sprintId>")
|
|
253
|
+
.description("Close a sprint (moves remaining issues to backlog)")
|
|
254
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
255
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
256
|
+
.action(async (sprintId, opts) => {
|
|
257
|
+
try {
|
|
258
|
+
if (opts.dryRun) {
|
|
259
|
+
console.log(chalk.cyan(`[dry run] Would close sprint ${sprintId}`));
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
if (!opts.yes) {
|
|
263
|
+
const ok = await confirm(`Close sprint ${sprintId}? This cannot be undone.`);
|
|
264
|
+
if (!ok) {
|
|
265
|
+
console.log(chalk.dim("Cancelled."));
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const sprint = await createClient().closeSprint(Number(sprintId));
|
|
270
|
+
console.log(chalk.green(`✓ Closed sprint: ${chalk.bold(sprint.name)} ${chalk.dim(`(id: ${sprint.id})`)}`));
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
handleError(err);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
jira
|
|
277
|
+
.command("users <query>")
|
|
278
|
+
.description("Search for users by name or email (to find accountIds for assignee)")
|
|
279
|
+
.option("-l, --limit <n>", "Max results", "10")
|
|
280
|
+
.action(async (query, opts) => {
|
|
281
|
+
try {
|
|
282
|
+
const users = await createClient().searchUsers(query, Number(opts.limit));
|
|
283
|
+
if (users.length === 0) {
|
|
284
|
+
console.log(chalk.yellow("No users found."));
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
for (const u of users) {
|
|
288
|
+
const email = u.emailAddress ? chalk.dim(` <${u.emailAddress}>`) : "";
|
|
289
|
+
const inactive = u.active ? "" : chalk.red(" (inactive)");
|
|
290
|
+
console.log(`${chalk.bold(u.displayName)}${email}${inactive}`);
|
|
291
|
+
console.log(` ${chalk.dim(`accountId: ${u.accountId}`)}`);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
catch (err) {
|
|
295
|
+
handleError(err);
|
|
296
|
+
}
|
|
297
|
+
});
|
|
55
298
|
jira
|
|
56
299
|
.command("list")
|
|
57
300
|
.description("Search for issues")
|
|
@@ -61,6 +304,7 @@ export function registerJiraCommands(program) {
|
|
|
61
304
|
.option("--type <type>", "Filter by issue type")
|
|
62
305
|
.option("--jql <query>", "Raw JQL query (overrides other filters)")
|
|
63
306
|
.option("-l, --limit <n>", "Max results", "25")
|
|
307
|
+
.option("--all", "Fetch up to 100 results per request")
|
|
64
308
|
.action(async (opts) => {
|
|
65
309
|
try {
|
|
66
310
|
const client = createClient();
|
|
@@ -70,7 +314,7 @@ export function registerJiraCommands(program) {
|
|
|
70
314
|
status: opts.status,
|
|
71
315
|
assignee: opts.assignee,
|
|
72
316
|
type: opts.type,
|
|
73
|
-
limit: Number(opts.limit),
|
|
317
|
+
limit: opts.all ? 100 : Number(opts.limit),
|
|
74
318
|
});
|
|
75
319
|
if (issues.length === 0) {
|
|
76
320
|
console.log(chalk.yellow("No issues found."));
|
|
@@ -119,10 +363,23 @@ export function registerJiraCommands(program) {
|
|
|
119
363
|
.option("--description <text>", "Issue description")
|
|
120
364
|
.option("--priority <name>", "Priority (Highest, High, Medium, Low, Lowest)")
|
|
121
365
|
.option("--labels <labels>", "Comma-separated labels")
|
|
366
|
+
.option("--parent <key>", "Parent issue key (creates a subtask)")
|
|
122
367
|
.option("-y, --yes", "Skip confirmation prompt")
|
|
368
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
123
369
|
.action(async (opts) => {
|
|
124
370
|
try {
|
|
125
371
|
const client = createClient();
|
|
372
|
+
if (opts.dryRun) {
|
|
373
|
+
console.log(chalk.cyan("[dry run] Would create issue:"));
|
|
374
|
+
console.log(` Project: ${opts.project}`);
|
|
375
|
+
console.log(` Type: ${opts.type}`);
|
|
376
|
+
console.log(` Summary: ${opts.summary}`);
|
|
377
|
+
if (opts.priority)
|
|
378
|
+
console.log(` Priority: ${opts.priority}`);
|
|
379
|
+
if (opts.parent)
|
|
380
|
+
console.log(` Parent: ${opts.parent}`);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
126
383
|
if (!opts.yes) {
|
|
127
384
|
console.log(chalk.yellow("⚠ About to create issue:"));
|
|
128
385
|
console.log(` Project: ${opts.project}`);
|
|
@@ -130,6 +387,8 @@ export function registerJiraCommands(program) {
|
|
|
130
387
|
console.log(` Summary: ${opts.summary}`);
|
|
131
388
|
if (opts.priority)
|
|
132
389
|
console.log(` Priority: ${opts.priority}`);
|
|
390
|
+
if (opts.parent)
|
|
391
|
+
console.log(` Parent: ${opts.parent}`);
|
|
133
392
|
console.log();
|
|
134
393
|
const ok = await confirm("Proceed?");
|
|
135
394
|
if (!ok) {
|
|
@@ -144,6 +403,7 @@ export function registerJiraCommands(program) {
|
|
|
144
403
|
description: opts.description,
|
|
145
404
|
priority: opts.priority,
|
|
146
405
|
labels: opts.labels?.split(",").map((l) => l.trim()),
|
|
406
|
+
parentKey: opts.parent,
|
|
147
407
|
});
|
|
148
408
|
console.log(chalk.green(`✓ Created: ${formatIssue(issue)}`));
|
|
149
409
|
console.log(` ${chalk.cyan(issueUrl(issue.key))}`);
|
|
@@ -160,10 +420,20 @@ export function registerJiraCommands(program) {
|
|
|
160
420
|
.option("--priority <name>", "New priority")
|
|
161
421
|
.option("--labels <labels>", "Comma-separated labels")
|
|
162
422
|
.option("-y, --yes", "Skip confirmation prompt")
|
|
423
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
163
424
|
.action(async (issueKey, opts) => {
|
|
164
425
|
try {
|
|
165
426
|
const client = createClient();
|
|
166
427
|
const current = await client.getIssue(issueKey);
|
|
428
|
+
if (opts.dryRun) {
|
|
429
|
+
console.log(chalk.cyan("[dry run] Would update issue:"));
|
|
430
|
+
console.log(` ${formatIssue(current)}`);
|
|
431
|
+
if (opts.summary)
|
|
432
|
+
console.log(` Summary: ${current.fields.summary} → ${opts.summary}`);
|
|
433
|
+
if (opts.priority)
|
|
434
|
+
console.log(` Priority: → ${opts.priority}`);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
167
437
|
if (!opts.yes) {
|
|
168
438
|
console.log(chalk.yellow("⚠ About to update issue:"));
|
|
169
439
|
console.log(` ${formatIssue(current)}`);
|
|
@@ -198,6 +468,7 @@ export function registerJiraCommands(program) {
|
|
|
198
468
|
.option("--to <status>", "Target status name")
|
|
199
469
|
.option("--list", "List available transitions")
|
|
200
470
|
.option("-y, --yes", "Skip confirmation prompt")
|
|
471
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
201
472
|
.action(async (issueKey, opts) => {
|
|
202
473
|
try {
|
|
203
474
|
const client = createClient();
|
|
@@ -217,6 +488,12 @@ export function registerJiraCommands(program) {
|
|
|
217
488
|
}
|
|
218
489
|
process.exit(1);
|
|
219
490
|
}
|
|
491
|
+
if (opts.dryRun) {
|
|
492
|
+
const issue = await client.getIssue(issueKey);
|
|
493
|
+
console.log(chalk.cyan(`[dry run] Would transition ${chalk.bold(issueKey)}:`));
|
|
494
|
+
console.log(` ${issue.fields.status.name} → ${match.to.name}`);
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
220
497
|
if (!opts.yes) {
|
|
221
498
|
const issue = await client.getIssue(issueKey);
|
|
222
499
|
console.log(chalk.yellow(`⚠ About to transition ${chalk.bold(issueKey)}:`));
|
|
@@ -235,14 +512,253 @@ export function registerJiraCommands(program) {
|
|
|
235
512
|
handleError(err);
|
|
236
513
|
}
|
|
237
514
|
});
|
|
515
|
+
jira
|
|
516
|
+
.command("worklogs <issueKey>")
|
|
517
|
+
.description("List work log entries on an issue")
|
|
518
|
+
.action(async (issueKey) => {
|
|
519
|
+
try {
|
|
520
|
+
const worklogs = await createClient().listWorklogs(issueKey);
|
|
521
|
+
if (worklogs.length === 0) {
|
|
522
|
+
console.log(chalk.yellow("No work logged."));
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
for (const w of worklogs) {
|
|
526
|
+
const author = w.author?.displayName ?? "Unknown";
|
|
527
|
+
const date = new Date(w.started).toLocaleDateString();
|
|
528
|
+
console.log(` ${chalk.bold(w.timeSpent)} ${chalk.dim(`· ${author} · ${date}`)}`);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
catch (err) {
|
|
532
|
+
handleError(err);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
jira
|
|
536
|
+
.command("log <issueKey>")
|
|
537
|
+
.description("Log time worked on an issue")
|
|
538
|
+
.requiredOption("--time <duration>", "Time spent, e.g. '2h', '30m', '1d 2h'")
|
|
539
|
+
.option("--comment <text>", "Work description")
|
|
540
|
+
.option("--started <datetime>", "When work started (ISO datetime, defaults to now)")
|
|
541
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
542
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
543
|
+
.action(async (issueKey, opts) => {
|
|
544
|
+
try {
|
|
545
|
+
if (opts.dryRun) {
|
|
546
|
+
console.log(chalk.cyan(`[dry run] Would log time on ${issueKey}:`));
|
|
547
|
+
console.log(` Time: ${opts.time}`);
|
|
548
|
+
if (opts.comment)
|
|
549
|
+
console.log(` Comment: ${opts.comment}`);
|
|
550
|
+
if (opts.started)
|
|
551
|
+
console.log(` Started: ${opts.started}`);
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
if (!opts.yes) {
|
|
555
|
+
const ok = await confirm(`Log ${opts.time} on ${issueKey}?`);
|
|
556
|
+
if (!ok) {
|
|
557
|
+
console.log(chalk.dim("Cancelled."));
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const log = await createClient().addWorklog({
|
|
562
|
+
issueKey,
|
|
563
|
+
timeSpent: opts.time,
|
|
564
|
+
started: opts.started,
|
|
565
|
+
comment: opts.comment,
|
|
566
|
+
});
|
|
567
|
+
console.log(chalk.green(`✓ Logged ${log.timeSpent} on ${issueKey}.`));
|
|
568
|
+
}
|
|
569
|
+
catch (err) {
|
|
570
|
+
handleError(err);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
jira
|
|
574
|
+
.command("links <issueKey>")
|
|
575
|
+
.description("List links on an issue")
|
|
576
|
+
.action(async (issueKey) => {
|
|
577
|
+
try {
|
|
578
|
+
const links = await createClient().listIssueLinks(issueKey);
|
|
579
|
+
if (links.length === 0) {
|
|
580
|
+
console.log(chalk.yellow("No issue links."));
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
for (const l of links) {
|
|
584
|
+
if (l.outwardIssue) {
|
|
585
|
+
console.log(` ${chalk.dim(l.type.outward)} ${chalk.bold(l.outwardIssue.key)} ${l.outwardIssue.fields.summary} ${chalk.dim(`[${l.outwardIssue.fields.status.name}]`)}`);
|
|
586
|
+
}
|
|
587
|
+
else if (l.inwardIssue) {
|
|
588
|
+
console.log(` ${chalk.dim(l.type.inward)} ${chalk.bold(l.inwardIssue.key)} ${l.inwardIssue.fields.summary} ${chalk.dim(`[${l.inwardIssue.fields.status.name}]`)}`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
catch (err) {
|
|
593
|
+
handleError(err);
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
jira
|
|
597
|
+
.command("link <issueKey>")
|
|
598
|
+
.description("Link an issue to another")
|
|
599
|
+
.requiredOption("--type <name>", "Link type (e.g. 'Blocks', 'Relates to')")
|
|
600
|
+
.requiredOption("--target <key>", "Target issue key")
|
|
601
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
602
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
603
|
+
.action(async (issueKey, opts) => {
|
|
604
|
+
try {
|
|
605
|
+
if (opts.dryRun) {
|
|
606
|
+
console.log(chalk.cyan(`[dry run] Would link issue:`));
|
|
607
|
+
console.log(` ${issueKey} "${opts.type}" ${opts.target}`);
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
if (!opts.yes) {
|
|
611
|
+
const ok = await confirm(`Link ${issueKey} "${opts.type}" ${opts.target}?`);
|
|
612
|
+
if (!ok) {
|
|
613
|
+
console.log(chalk.dim("Cancelled."));
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
await createClient().linkIssues(issueKey, opts.type, opts.target);
|
|
618
|
+
console.log(chalk.green(`✓ Linked: ${issueKey} "${opts.type}" ${opts.target}`));
|
|
619
|
+
}
|
|
620
|
+
catch (err) {
|
|
621
|
+
handleError(err);
|
|
622
|
+
}
|
|
623
|
+
});
|
|
624
|
+
jira
|
|
625
|
+
.command("link-types")
|
|
626
|
+
.description("List available issue link types")
|
|
627
|
+
.action(async () => {
|
|
628
|
+
try {
|
|
629
|
+
const types = await createClient().listIssueLinkTypes();
|
|
630
|
+
for (const t of types) {
|
|
631
|
+
console.log(`${chalk.bold(t.name)} ${chalk.dim(`— outward: "${t.outward}", inward: "${t.inward}"`)}`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
catch (err) {
|
|
635
|
+
handleError(err);
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
jira
|
|
639
|
+
.command("comments <issueKey>")
|
|
640
|
+
.description("List comments on an issue")
|
|
641
|
+
.action(async (issueKey) => {
|
|
642
|
+
try {
|
|
643
|
+
const client = createClient();
|
|
644
|
+
const comments = await client.listComments(issueKey);
|
|
645
|
+
if (comments.length === 0) {
|
|
646
|
+
console.log(chalk.yellow("No comments."));
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
for (const c of comments) {
|
|
650
|
+
const author = c.author?.displayName ?? "Unknown";
|
|
651
|
+
const date = new Date(c.created).toLocaleDateString();
|
|
652
|
+
console.log(chalk.dim("─".repeat(60)));
|
|
653
|
+
console.log(`${chalk.bold(author)} ${chalk.dim(`· ${date}`)}`);
|
|
654
|
+
console.log(client.descriptionToText({ fields: { description: c.body } }));
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
catch (err) {
|
|
658
|
+
handleError(err);
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
jira
|
|
662
|
+
.command("comment <issueKey>")
|
|
663
|
+
.description("Add a comment to an issue")
|
|
664
|
+
.requiredOption("-t, --text <text>", "Comment text")
|
|
665
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
666
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
667
|
+
.action(async (issueKey, opts) => {
|
|
668
|
+
try {
|
|
669
|
+
if (opts.dryRun) {
|
|
670
|
+
console.log(chalk.cyan(`[dry run] Would add comment to ${issueKey}:`));
|
|
671
|
+
console.log(` Text: ${opts.text}`);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
if (!opts.yes) {
|
|
675
|
+
const ok = await confirm(`Add comment to ${issueKey}?`);
|
|
676
|
+
if (!ok) {
|
|
677
|
+
console.log(chalk.dim("Cancelled."));
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
await createClient().addComment(issueKey, opts.text);
|
|
682
|
+
console.log(chalk.green(`✓ Comment added to ${issueKey}.`));
|
|
683
|
+
}
|
|
684
|
+
catch (err) {
|
|
685
|
+
handleError(err);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
jira
|
|
689
|
+
.command("attach <issueKey> <file>")
|
|
690
|
+
.description("Upload a file as an attachment to an issue")
|
|
691
|
+
.option("-y, --yes", "Skip confirmation prompt")
|
|
692
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
693
|
+
.action(async (issueKey, file, opts) => {
|
|
694
|
+
try {
|
|
695
|
+
const client = createClient();
|
|
696
|
+
const issue = await client.getIssue(issueKey);
|
|
697
|
+
if (opts.dryRun) {
|
|
698
|
+
console.log(chalk.cyan("[dry run] Would attach file to issue:"));
|
|
699
|
+
console.log(` Issue: ${chalk.bold(issue.key)} ${issue.fields.summary}`);
|
|
700
|
+
console.log(` File: ${file}`);
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (!opts.yes) {
|
|
704
|
+
console.log(chalk.yellow("⚠ About to attach file to issue:"));
|
|
705
|
+
console.log(` Issue: ${chalk.bold(issue.key)} ${issue.fields.summary}`);
|
|
706
|
+
console.log(` File: ${file}`);
|
|
707
|
+
console.log();
|
|
708
|
+
const ok = await confirm("Proceed?");
|
|
709
|
+
if (!ok) {
|
|
710
|
+
console.log(chalk.dim("Cancelled."));
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
const att = await client.uploadAttachment({ issueKey, filePath: file });
|
|
715
|
+
console.log(chalk.green(`✓ Uploaded: ${chalk.bold(att.filename)} ${chalk.dim(`(id: ${att.id}, ${att.mimeType}, ${att.size} bytes)`)}`));
|
|
716
|
+
if (att.content) {
|
|
717
|
+
console.log(` ${chalk.cyan(att.content)}`);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
catch (err) {
|
|
721
|
+
handleError(err);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
jira
|
|
725
|
+
.command("attachments <issueKey>")
|
|
726
|
+
.description("List attachments on an issue")
|
|
727
|
+
.action(async (issueKey) => {
|
|
728
|
+
try {
|
|
729
|
+
const client = createClient();
|
|
730
|
+
const issue = await client.getIssue(issueKey);
|
|
731
|
+
const attachments = await client.listAttachments(issueKey);
|
|
732
|
+
console.log(`Attachments on ${chalk.bold(issue.key)} ${issue.fields.summary}`);
|
|
733
|
+
if (attachments.length === 0) {
|
|
734
|
+
console.log(chalk.yellow(" No attachments."));
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
for (const att of attachments) {
|
|
738
|
+
console.log(` ${chalk.bold(att.filename)} ${chalk.dim(`(id: ${att.id}, ${att.mimeType}, ${att.size} bytes)`)}`);
|
|
739
|
+
if (att.content) {
|
|
740
|
+
console.log(` ${chalk.cyan(att.content)}`);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
catch (err) {
|
|
745
|
+
handleError(err);
|
|
746
|
+
}
|
|
747
|
+
});
|
|
238
748
|
jira
|
|
239
749
|
.command("delete <issueKey>")
|
|
240
750
|
.description("Delete an issue")
|
|
241
751
|
.option("-y, --yes", "Skip confirmation prompt")
|
|
752
|
+
.option("--dry-run", "Print what would happen without making changes")
|
|
242
753
|
.action(async (issueKey, opts) => {
|
|
243
754
|
try {
|
|
244
755
|
const client = createClient();
|
|
245
756
|
const issue = await client.getIssue(issueKey);
|
|
757
|
+
if (opts.dryRun) {
|
|
758
|
+
console.log(chalk.cyan("[dry run] Would DELETE issue:"));
|
|
759
|
+
console.log(` ${formatIssue(issue)}`);
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
246
762
|
if (!opts.yes) {
|
|
247
763
|
console.log(chalk.red("⚠ About to DELETE issue:"));
|
|
248
764
|
console.log(` ${formatIssue(issue)}`);
|