jira-pilot 2.1.2 → 2.2.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 +54 -0
- package/bin/jira.ts +2 -0
- package/dist/bin/jira.js +2 -0
- package/dist/bin/jira.js.map +1 -1
- package/dist/src/commands/ai-actions/plan.js +1 -1
- package/dist/src/commands/ai-actions/plan.js.map +1 -1
- package/dist/src/commands/ai-actions/review.js +5 -4
- package/dist/src/commands/ai-actions/review.js.map +1 -1
- package/dist/src/commands/ai-actions/standup.js +1 -1
- package/dist/src/commands/ai-actions/standup.js.map +1 -1
- package/dist/src/commands/ai.js +1 -1
- package/dist/src/commands/ai.js.map +1 -1
- package/dist/src/commands/board.js +10 -5
- package/dist/src/commands/board.js.map +1 -1
- package/dist/src/commands/bulk.js +11 -10
- package/dist/src/commands/bulk.js.map +1 -1
- package/dist/src/commands/config.js +1 -1
- package/dist/src/commands/config.js.map +1 -1
- package/dist/src/commands/dashboard.js +19 -12
- package/dist/src/commands/dashboard.js.map +1 -1
- package/dist/src/commands/filter.js +7 -4
- package/dist/src/commands/filter.js.map +1 -1
- package/dist/src/commands/git.js +1 -1
- package/dist/src/commands/git.js.map +1 -1
- package/dist/src/commands/issue-attach.js +1 -1
- package/dist/src/commands/issue-attach.js.map +1 -1
- package/dist/src/commands/issue-pr.js +1 -1
- package/dist/src/commands/issue-pr.js.map +1 -1
- package/dist/src/commands/issue-worklog.js +10 -5
- package/dist/src/commands/issue-worklog.js.map +1 -1
- package/dist/src/commands/issue.js +173 -122
- package/dist/src/commands/issue.js.map +1 -1
- package/dist/src/commands/project.js +10 -5
- package/dist/src/commands/project.js.map +1 -1
- package/dist/src/commands/sprint.js +19 -8
- package/dist/src/commands/sprint.js.map +1 -1
- package/dist/src/commands/tui.d.ts +2 -0
- package/dist/src/commands/tui.js +10 -0
- package/dist/src/commands/tui.js.map +1 -0
- package/dist/src/server/mcp-server.js +209 -27
- package/dist/src/server/mcp-server.js.map +1 -1
- package/dist/src/services/ai-service.js +7 -4
- package/dist/src/services/ai-service.js.map +1 -1
- package/dist/src/services/api-service.d.ts +2 -0
- package/dist/src/services/api-service.js +32 -20
- package/dist/src/services/api-service.js.map +1 -1
- package/dist/src/tui/App.d.ts +1 -0
- package/dist/src/tui/App.js +26 -0
- package/dist/src/tui/App.js.map +1 -0
- package/dist/src/tui/index.d.ts +1 -0
- package/dist/src/tui/index.js +8 -0
- package/dist/src/tui/index.js.map +1 -0
- package/dist/src/tui/screens/BoardList.d.ts +1 -0
- package/dist/src/tui/screens/BoardList.js +71 -0
- package/dist/src/tui/screens/BoardList.js.map +1 -0
- package/dist/src/tui/screens/Dashboard.d.ts +1 -0
- package/dist/src/tui/screens/Dashboard.js +41 -0
- package/dist/src/tui/screens/Dashboard.js.map +1 -0
- package/dist/src/tui/screens/IssueDetail.d.ts +6 -0
- package/dist/src/tui/screens/IssueDetail.js +40 -0
- package/dist/src/tui/screens/IssueDetail.js.map +1 -0
- package/dist/src/tui/screens/IssueList.d.ts +1 -0
- package/dist/src/tui/screens/IssueList.js +72 -0
- package/dist/src/tui/screens/IssueList.js.map +1 -0
- package/dist/src/tui/screens/KanbanBoard.d.ts +6 -0
- package/dist/src/tui/screens/KanbanBoard.js +86 -0
- package/dist/src/tui/screens/KanbanBoard.js.map +1 -0
- package/dist/src/tui/utils/adf-render.d.ts +1 -0
- package/dist/src/tui/utils/adf-render.js +29 -0
- package/dist/src/tui/utils/adf-render.js.map +1 -0
- package/dist/src/utils/api-paths.d.ts +31 -0
- package/dist/src/utils/api-paths.js +32 -0
- package/dist/src/utils/api-paths.js.map +1 -0
- package/dist/src/utils/error-handler.d.ts +2 -2
- package/dist/src/utils/error-handler.js.map +1 -1
- package/dist/src/utils/http.d.ts +27 -0
- package/dist/src/utils/http.js +95 -0
- package/dist/src/utils/http.js.map +1 -0
- package/dist/src/utils/spinner.d.ts +21 -0
- package/dist/src/utils/spinner.js +79 -0
- package/dist/src/utils/spinner.js.map +1 -0
- package/package.json +10 -5
- package/src/commands/ai-actions/plan.ts +1 -1
- package/src/commands/ai-actions/review.ts +5 -4
- package/src/commands/ai-actions/standup.ts +1 -1
- package/src/commands/ai.ts +1 -1
- package/src/commands/board.ts +10 -5
- package/src/commands/bulk.ts +11 -10
- package/src/commands/config.ts +1 -1
- package/src/commands/dashboard.ts +20 -12
- package/src/commands/filter.ts +8 -5
- package/src/commands/git.ts +1 -1
- package/src/commands/issue-attach.ts +1 -1
- package/src/commands/issue-pr.ts +1 -1
- package/src/commands/issue-worklog.ts +10 -5
- package/src/commands/issue.ts +181 -124
- package/src/commands/project.ts +10 -5
- package/src/commands/sprint.ts +19 -8
- package/src/commands/tui.ts +11 -0
- package/src/server/mcp-server.ts +234 -27
- package/src/services/ai-service.ts +7 -4
- package/src/services/api-service.ts +34 -21
- package/src/tui/App.tsx +61 -0
- package/src/tui/index.tsx +8 -0
- package/src/tui/screens/BoardList.tsx +102 -0
- package/src/tui/screens/Dashboard.tsx +75 -0
- package/src/tui/screens/IssueDetail.tsx +93 -0
- package/src/tui/screens/IssueList.tsx +116 -0
- package/src/tui/screens/KanbanBoard.tsx +133 -0
- package/src/tui/utils/adf-render.ts +30 -0
- package/src/utils/api-paths.ts +32 -0
- package/src/utils/error-handler.ts +2 -2
- package/src/utils/http.ts +128 -0
- package/src/utils/spinner.ts +87 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import Table from '
|
|
3
|
+
import { Table } from 'cmd-table';
|
|
4
4
|
import { api } from '../services/api-service.js';
|
|
5
5
|
import { aiService } from '../services/ai-service.js';
|
|
6
|
-
import ora from '
|
|
6
|
+
import ora from '../utils/spinner.js';
|
|
7
7
|
import enquirer from 'enquirer';
|
|
8
8
|
import { parseADF } from '../utils/adf-parser.js';
|
|
9
9
|
import { textToADF } from '../utils/text-to-adf.js';
|
|
@@ -13,6 +13,7 @@ import { registerWorklogCommand } from './issue-worklog.js';
|
|
|
13
13
|
import { registerPrCommand } from './issue-pr.js';
|
|
14
14
|
import { registerAttachCommand } from './issue-attach.js';
|
|
15
15
|
import { ConfigService } from '../services/config-service.js';
|
|
16
|
+
import { API } from '../utils/api-paths.js';
|
|
16
17
|
export function registerIssueCommand(program) {
|
|
17
18
|
const issueCmd = new Command('issue')
|
|
18
19
|
.description('Manage Jira issues')
|
|
@@ -68,21 +69,16 @@ Examples:
|
|
|
68
69
|
if (options.jql)
|
|
69
70
|
jqlParts.push(options.jql);
|
|
70
71
|
// Order by updated desc by default if no JQL
|
|
71
|
-
if (!options.jql && jqlParts.length === 0) {
|
|
72
|
-
jqlParts.push('order by updated DESC');
|
|
73
|
-
}
|
|
74
|
-
else if (jqlParts.length > 0 && !options.jql) {
|
|
75
|
-
// Add order if not custom jql
|
|
76
|
-
// jqlParts.push('order by updated DESC');
|
|
77
|
-
}
|
|
78
72
|
const jql = jqlParts.join(' AND ');
|
|
79
|
-
|
|
73
|
+
// Default to last 30 days if no filter provided to satisfy "unbounded" check
|
|
74
|
+
const defaultJql = 'updated >= -30d ORDER BY updated DESC';
|
|
75
|
+
const finalJql = jql || defaultJql;
|
|
80
76
|
const body = {
|
|
81
|
-
jql:
|
|
77
|
+
jql: finalJql,
|
|
82
78
|
maxResults: parseInt(options.limit),
|
|
83
|
-
fields: ['summary', 'status', 'assignee', 'created', 'updated', 'description']
|
|
79
|
+
fields: ['summary', 'status', 'assignee', 'created', 'updated', 'description', 'priority', 'issuetype', 'project', 'reporter']
|
|
84
80
|
};
|
|
85
|
-
const data = await api.post(
|
|
81
|
+
const data = await api.post(API.SEARCH.JQL, body);
|
|
86
82
|
spinner.stop();
|
|
87
83
|
if (!data.issues || data.issues.length === 0) {
|
|
88
84
|
console.log(chalk.yellow('No issues found.'));
|
|
@@ -124,10 +120,17 @@ Examples:
|
|
|
124
120
|
return;
|
|
125
121
|
}
|
|
126
122
|
const table = new Table({
|
|
127
|
-
|
|
123
|
+
columns: [
|
|
124
|
+
{ name: chalk.bold('Key') },
|
|
125
|
+
{ name: chalk.bold('Summary') },
|
|
126
|
+
{ name: chalk.bold('Status') },
|
|
127
|
+
{ name: chalk.bold('Assignee') },
|
|
128
|
+
{ name: chalk.bold('Created') },
|
|
129
|
+
{ name: chalk.bold('Updated') }
|
|
130
|
+
]
|
|
128
131
|
});
|
|
129
132
|
data.issues.forEach((i) => {
|
|
130
|
-
table.
|
|
133
|
+
table.addRow([
|
|
131
134
|
chalk.cyan(i.key),
|
|
132
135
|
i.fields.summary ? (i.fields.summary.length > 50 ? i.fields.summary.substring(0, 47) + '...' : i.fields.summary) : '',
|
|
133
136
|
i.fields.status ? i.fields.status.name : '',
|
|
@@ -136,7 +139,7 @@ Examples:
|
|
|
136
139
|
i.fields.updated ? i.fields.updated.split('T')[0] : ''
|
|
137
140
|
]);
|
|
138
141
|
});
|
|
139
|
-
console.log(table.
|
|
142
|
+
console.log(table.render());
|
|
140
143
|
}
|
|
141
144
|
catch (e) {
|
|
142
145
|
handleCommandError(spinner, e, 'Failed to list issues');
|
|
@@ -160,7 +163,7 @@ Examples:
|
|
|
160
163
|
}
|
|
161
164
|
const spinner = ora(`Fetching issue ${issueKey}...`).start();
|
|
162
165
|
try {
|
|
163
|
-
const issue = await api.get(
|
|
166
|
+
const issue = await api.get(API.ISSUE.GET(issueKey));
|
|
164
167
|
spinner.stop();
|
|
165
168
|
if (options.output === 'json') {
|
|
166
169
|
console.log(JSON.stringify({
|
|
@@ -195,7 +198,7 @@ Examples:
|
|
|
195
198
|
if (issue.fields.comment && issue.fields.comment.comments.length > 0) {
|
|
196
199
|
console.log(chalk.bold('\nComments:'));
|
|
197
200
|
issue.fields.comment.comments.forEach((c) => {
|
|
198
|
-
console.log(chalk.cyan(c.author.displayName) + ': ' + c.body);
|
|
201
|
+
console.log(chalk.cyan(c.author.displayName) + ': ' + (parseADF(c.body) || ''));
|
|
199
202
|
});
|
|
200
203
|
}
|
|
201
204
|
console.log('');
|
|
@@ -214,6 +217,11 @@ Examples:
|
|
|
214
217
|
.option('-d, --description <text>', 'Issue description')
|
|
215
218
|
.option('--priority <name>', 'Priority name (e.g., High, Medium, Low)')
|
|
216
219
|
.option('-a, --assignee <id>', 'Assignee account ID (use "me" for self)')
|
|
220
|
+
.option('-l, --labels <list>', 'Labels (comma separated)')
|
|
221
|
+
.option('-c, --components <list>', 'Component IDs (comma separated)', (v, l) => l.concat([v]), [])
|
|
222
|
+
.option('--fix-versions <list>', 'Fix Version IDs (comma separated)', (v, l) => l.concat([v]), [])
|
|
223
|
+
.option('--due-date <date>', 'Due Date (YYYY-MM-DD)')
|
|
224
|
+
.option('--no-input', 'Disable interactive prompts for optional fields')
|
|
217
225
|
.option('--custom <key=value>', 'Custom fields (key=value, repeatable)', (v, l) => l.concat([v]), [])
|
|
218
226
|
.addHelpText('after', `
|
|
219
227
|
Examples:
|
|
@@ -230,7 +238,7 @@ Examples:
|
|
|
230
238
|
let projectKey = options.project;
|
|
231
239
|
if (!projectKey) {
|
|
232
240
|
const spinner = ora('Fetching projects...').start();
|
|
233
|
-
const projectData = await api.get(
|
|
241
|
+
const projectData = await api.get(API.PROJECT.SEARCH);
|
|
234
242
|
spinner.stop();
|
|
235
243
|
if (!projectData.values || projectData.values.length === 0) {
|
|
236
244
|
console.error(chalk.red('No projects found. Check your permissions.'));
|
|
@@ -255,13 +263,13 @@ Examples:
|
|
|
255
263
|
let issueTypes = [];
|
|
256
264
|
try {
|
|
257
265
|
// Jira Cloud v3 - createmeta endpoint
|
|
258
|
-
const metaData = await api.get(
|
|
266
|
+
const metaData = await api.get(API.ISSUE.CREATEMETA(projectKey));
|
|
259
267
|
issueTypes = metaData.issueTypes || metaData.values || [];
|
|
260
268
|
}
|
|
261
269
|
catch (metaErr) {
|
|
262
270
|
// Fallback: use project-level issue types
|
|
263
271
|
try {
|
|
264
|
-
const projectInfo = await api.get(
|
|
272
|
+
const projectInfo = await api.get(API.PROJECT.GET(projectKey));
|
|
265
273
|
issueTypes = projectInfo.issueTypes || [];
|
|
266
274
|
}
|
|
267
275
|
catch {
|
|
@@ -313,10 +321,10 @@ Examples:
|
|
|
313
321
|
}
|
|
314
322
|
// ── Step 5: Priority ────────────────────────────────
|
|
315
323
|
let priorityName = options.priority;
|
|
316
|
-
if (!priorityName) {
|
|
324
|
+
if (!priorityName && !options.noInput) {
|
|
317
325
|
const spinner = ora('Fetching priorities...').start();
|
|
318
326
|
try {
|
|
319
|
-
const priorities = await api.get(
|
|
327
|
+
const priorities = await api.get(API.PRIORITY.ALL);
|
|
320
328
|
spinner.stop();
|
|
321
329
|
if (Array.isArray(priorities) && priorities.length > 0) {
|
|
322
330
|
const priorityChoices = priorities.map((p) => ({
|
|
@@ -338,70 +346,81 @@ Examples:
|
|
|
338
346
|
}
|
|
339
347
|
}
|
|
340
348
|
// ── Step 5.5: Components ────────────────────────────
|
|
341
|
-
let componentIds = [];
|
|
342
|
-
// Interactive only
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
349
|
+
let componentIds = options.components || [];
|
|
350
|
+
// Interactive only if components not provided and input allowed
|
|
351
|
+
if (componentIds.length === 0 && !options.noInput) {
|
|
352
|
+
const compSpinner = ora('Fetching components...').start();
|
|
353
|
+
try {
|
|
354
|
+
const components = await api.get(API.PROJECT.COMPONENTS(projectKey));
|
|
355
|
+
compSpinner.stop();
|
|
356
|
+
if (Array.isArray(components) && components.length > 0) {
|
|
357
|
+
const { selectedComponents } = await enquirer.prompt({
|
|
358
|
+
type: 'multiselect',
|
|
359
|
+
name: 'selectedComponents',
|
|
360
|
+
message: 'Select Components (Space to select, Enter to confirm):',
|
|
361
|
+
choices: components.map((c) => ({ name: c.id, message: c.name }))
|
|
362
|
+
});
|
|
363
|
+
componentIds = selectedComponents;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
catch {
|
|
367
|
+
compSpinner.stop();
|
|
355
368
|
}
|
|
356
|
-
}
|
|
357
|
-
catch {
|
|
358
|
-
compSpinner.stop();
|
|
359
369
|
}
|
|
360
370
|
// ── Step 5.6: Labels ────────────────────────────────
|
|
361
371
|
let labels = [];
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
name: 'inputLabels',
|
|
365
|
-
message: 'Labels (comma-separated, optional):'
|
|
366
|
-
});
|
|
367
|
-
if (inputLabels && inputLabels.trim().length > 0) {
|
|
368
|
-
labels = inputLabels.split(',').map((l) => l.trim()).filter((l) => l.length > 0);
|
|
372
|
+
if (options.labels) {
|
|
373
|
+
labels = options.labels.split(',').map((l) => l.trim()).filter((l) => l.length > 0);
|
|
369
374
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (Array.isArray(unreleased) && unreleased.length > 0) {
|
|
379
|
-
const { selectedVersions } = await enquirer.prompt({
|
|
380
|
-
type: 'multiselect',
|
|
381
|
-
name: 'selectedVersions',
|
|
382
|
-
message: 'Fix Versions:',
|
|
383
|
-
choices: unreleased.map((v) => ({ name: v.id, message: v.name }))
|
|
384
|
-
});
|
|
385
|
-
fixVersionIds = selectedVersions;
|
|
375
|
+
if (labels.length === 0 && !options.noInput) {
|
|
376
|
+
const { inputLabels } = await enquirer.prompt({
|
|
377
|
+
type: 'input',
|
|
378
|
+
name: 'inputLabels',
|
|
379
|
+
message: 'Labels (comma-separated, optional):'
|
|
380
|
+
});
|
|
381
|
+
if (inputLabels && inputLabels.trim().length > 0) {
|
|
382
|
+
labels = inputLabels.split(',').map((l) => l.trim()).filter((l) => l.length > 0);
|
|
386
383
|
}
|
|
387
384
|
}
|
|
388
|
-
|
|
389
|
-
|
|
385
|
+
// ── Step 5.7: Fix Versions ──────────────────────────
|
|
386
|
+
let fixVersionIds = options.fixVersions || [];
|
|
387
|
+
if (fixVersionIds.length === 0 && !options.noInput) {
|
|
388
|
+
const verSpinner = ora('Fetching versions...').start();
|
|
389
|
+
try {
|
|
390
|
+
const versions = await api.get(API.PROJECT.VERSIONS(projectKey));
|
|
391
|
+
verSpinner.stop();
|
|
392
|
+
// Filter unreleased versions usually
|
|
393
|
+
const unreleased = versions.filter((v) => !v.released);
|
|
394
|
+
if (Array.isArray(unreleased) && unreleased.length > 0) {
|
|
395
|
+
const { selectedVersions } = await enquirer.prompt({
|
|
396
|
+
type: 'multiselect',
|
|
397
|
+
name: 'selectedVersions',
|
|
398
|
+
message: 'Fix Versions:',
|
|
399
|
+
choices: unreleased.map((v) => ({ name: v.id, message: v.name }))
|
|
400
|
+
});
|
|
401
|
+
fixVersionIds = selectedVersions;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
verSpinner.stop();
|
|
406
|
+
}
|
|
390
407
|
}
|
|
391
408
|
// ── Step 5.8: Due Date ──────────────────────────────
|
|
392
|
-
let duedate = null;
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
409
|
+
let duedate = options.dueDate || null;
|
|
410
|
+
if (!duedate && !options.noInput) {
|
|
411
|
+
const { inputDueDate } = await enquirer.prompt({
|
|
412
|
+
type: 'input',
|
|
413
|
+
name: 'inputDueDate',
|
|
414
|
+
message: 'Due Date (YYYY-MM-DD, optional):',
|
|
415
|
+
validate: (val) => {
|
|
416
|
+
if (!val)
|
|
417
|
+
return true;
|
|
418
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(val) || 'Format must be YYYY-MM-DD';
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
if (inputDueDate)
|
|
422
|
+
duedate = inputDueDate;
|
|
423
|
+
}
|
|
405
424
|
// ── Step 6: Assignee ────────────────────────────────
|
|
406
425
|
let assigneeId = options.assignee;
|
|
407
426
|
if (!assigneeId) {
|
|
@@ -418,7 +437,7 @@ Examples:
|
|
|
418
437
|
if (assigneeChoice === 'me') {
|
|
419
438
|
const spinner = ora('Fetching your account...').start();
|
|
420
439
|
try {
|
|
421
|
-
const myself = await api.get(
|
|
440
|
+
const myself = await api.get(API.USER.MYSELF);
|
|
422
441
|
assigneeId = myself.accountId;
|
|
423
442
|
spinner.stop();
|
|
424
443
|
}
|
|
@@ -436,7 +455,7 @@ Examples:
|
|
|
436
455
|
if (searchQuery.trim()) {
|
|
437
456
|
const spinner = ora('Searching users...').start();
|
|
438
457
|
try {
|
|
439
|
-
const users = await api.get(
|
|
458
|
+
const users = await api.get(`${API.USER.SEARCH}?query=${encodeURIComponent(searchQuery)}`);
|
|
440
459
|
spinner.stop();
|
|
441
460
|
if (Array.isArray(users) && users.length > 0) {
|
|
442
461
|
const userChoices = users.map((u) => ({
|
|
@@ -470,7 +489,7 @@ Examples:
|
|
|
470
489
|
// --assignee me flag: resolve to account ID
|
|
471
490
|
const spinner = ora('Fetching your account...').start();
|
|
472
491
|
try {
|
|
473
|
-
const myself = await api.get(
|
|
492
|
+
const myself = await api.get(API.USER.MYSELF);
|
|
474
493
|
assigneeId = myself.accountId;
|
|
475
494
|
spinner.stop();
|
|
476
495
|
}
|
|
@@ -480,23 +499,25 @@ Examples:
|
|
|
480
499
|
}
|
|
481
500
|
}
|
|
482
501
|
// ── Confirmation ────────────────────────────────────
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
502
|
+
if (!options.noInput) {
|
|
503
|
+
console.log(chalk.blue('\n── Issue Summary ──────────────────'));
|
|
504
|
+
console.log(` Project: ${chalk.cyan(projectKey)}`);
|
|
505
|
+
console.log(` Type: ${issueTypeName}`);
|
|
506
|
+
console.log(` Summary: ${summary}`);
|
|
507
|
+
console.log(` Description: ${description || chalk.grey('(none)')}`);
|
|
508
|
+
console.log(` Priority: ${priorityName || chalk.grey('(default)')}`);
|
|
509
|
+
console.log(` Assignee: ${assigneeId || chalk.grey('Unassigned')}`);
|
|
510
|
+
console.log(chalk.blue('──────────────────────────────────\n'));
|
|
511
|
+
const { confirmed } = await enquirer.prompt({
|
|
512
|
+
type: 'confirm',
|
|
513
|
+
name: 'confirmed',
|
|
514
|
+
message: 'Create this issue?',
|
|
515
|
+
initial: true
|
|
516
|
+
});
|
|
517
|
+
if (!confirmed) {
|
|
518
|
+
console.log(chalk.yellow('Issue creation cancelled.'));
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
500
521
|
}
|
|
501
522
|
// ── Build Request Body ──────────────────────────────
|
|
502
523
|
const issueBody = {
|
|
@@ -541,7 +562,7 @@ Examples:
|
|
|
541
562
|
}
|
|
542
563
|
// ── Create Issue ────────────────────────────────────
|
|
543
564
|
const spinner = ora('Creating issue...').start();
|
|
544
|
-
const result = await api.post(
|
|
565
|
+
const result = await api.post(API.ISSUE.BASE, issueBody);
|
|
545
566
|
spinner.succeed(chalk.green(`Issue created: ${chalk.bold(result.key)}`));
|
|
546
567
|
console.log(chalk.grey(`View it: jira issue view ${result.key}`));
|
|
547
568
|
}
|
|
@@ -570,10 +591,10 @@ Examples:
|
|
|
570
591
|
const spinner = ora(`Fetching transitions for ${issueKey}...`).start();
|
|
571
592
|
try {
|
|
572
593
|
// Fetch current issue to show context
|
|
573
|
-
const issue = await api.get(
|
|
594
|
+
const issue = await api.get(`${API.ISSUE.GET(issueKey)}?fields=summary,status`);
|
|
574
595
|
const currentStatus = issue.fields.status.name;
|
|
575
596
|
// Fetch available transitions
|
|
576
|
-
const transData = await api.get(
|
|
597
|
+
const transData = await api.get(API.ISSUE.TRANSITIONS(issueKey));
|
|
577
598
|
spinner.stop();
|
|
578
599
|
if (!transData.transitions || transData.transitions.length === 0) {
|
|
579
600
|
console.log(chalk.yellow(`No transitions available for ${issueKey} (current status: ${currentStatus}).`));
|
|
@@ -611,7 +632,7 @@ Examples:
|
|
|
611
632
|
}
|
|
612
633
|
// Execute transition
|
|
613
634
|
const execSpinner = ora(`Transitioning to "${targetTransition.to.name}"...`).start();
|
|
614
|
-
await api.post(
|
|
635
|
+
await api.post(API.ISSUE.TRANSITIONS(issueKey), {
|
|
615
636
|
transition: { id: targetTransition.id }
|
|
616
637
|
});
|
|
617
638
|
execSpinner.succeed(chalk.green(`${issueKey} transitioned: ${currentStatus} → ${chalk.bold(targetTransition.to.name)}`));
|
|
@@ -644,7 +665,7 @@ Examples:
|
|
|
644
665
|
if (!assigneeId) {
|
|
645
666
|
// Interactive selection
|
|
646
667
|
const spinner = ora(`Fetching issue ${issueKey}...`).start();
|
|
647
|
-
const issue = await api.get(
|
|
668
|
+
const issue = await api.get(`${API.ISSUE.GET(issueKey)}?fields=summary,assignee`);
|
|
648
669
|
spinner.stop();
|
|
649
670
|
const currentAssignee = issue.fields.assignee?.displayName || 'Unassigned';
|
|
650
671
|
console.log(chalk.bold(`\n${issue.key}: ${issue.fields.summary}`));
|
|
@@ -663,7 +684,7 @@ Examples:
|
|
|
663
684
|
}
|
|
664
685
|
if (assigneeId === 'me') {
|
|
665
686
|
const spinner = ora('Fetching your account...').start();
|
|
666
|
-
const myself = await api.get(
|
|
687
|
+
const myself = await api.get(API.USER.MYSELF);
|
|
667
688
|
assigneeId = myself.accountId;
|
|
668
689
|
spinner.stop();
|
|
669
690
|
}
|
|
@@ -674,7 +695,7 @@ Examples:
|
|
|
674
695
|
message: 'Search user by name or email:'
|
|
675
696
|
});
|
|
676
697
|
const spinner = ora('Searching users...').start();
|
|
677
|
-
const users = await api.get(
|
|
698
|
+
const users = await api.get(`${API.USER.SEARCH}?query=${encodeURIComponent(searchQuery)}`);
|
|
678
699
|
spinner.stop();
|
|
679
700
|
if (!Array.isArray(users) || users.length === 0) {
|
|
680
701
|
console.log(chalk.yellow('No users found.'));
|
|
@@ -695,7 +716,7 @@ Examples:
|
|
|
695
716
|
const body = assigneeId === 'none'
|
|
696
717
|
? { accountId: null }
|
|
697
718
|
: { accountId: assigneeId };
|
|
698
|
-
await api.put(
|
|
719
|
+
await api.put(API.ISSUE.ASSIGNEE(issueKey), body);
|
|
699
720
|
spinner.succeed(chalk.green(`${issueKey} ${assigneeId === 'none' ? 'unassigned' : 'assigned'} successfully.`));
|
|
700
721
|
}
|
|
701
722
|
catch (e) {
|
|
@@ -738,7 +759,7 @@ Examples:
|
|
|
738
759
|
commentText = inputComment;
|
|
739
760
|
}
|
|
740
761
|
const spinner = ora('Adding comment...').start();
|
|
741
|
-
await api.post(
|
|
762
|
+
await api.post(API.ISSUE.COMMENT(issueKey), {
|
|
742
763
|
body: textToADF(commentText)
|
|
743
764
|
});
|
|
744
765
|
spinner.succeed(chalk.green(`Comment added to ${issueKey}.`));
|
|
@@ -770,7 +791,7 @@ Examples:
|
|
|
770
791
|
}
|
|
771
792
|
const spinner = ora(`Fetching issue ${issueKey}...`).start();
|
|
772
793
|
try {
|
|
773
|
-
const issue = await api.get(
|
|
794
|
+
const issue = await api.get(`${API.ISSUE.GET(issueKey)}?fields=summary,description,priority`);
|
|
774
795
|
spinner.stop();
|
|
775
796
|
const updateBody = { fields: {} };
|
|
776
797
|
const hasFlags = options.summary || options.description || options.priority || (options.custom && options.custom.length > 0);
|
|
@@ -826,7 +847,7 @@ Examples:
|
|
|
826
847
|
updateBody.fields.description = textToADF(desc);
|
|
827
848
|
}
|
|
828
849
|
if (field === 'priority') {
|
|
829
|
-
const priorities = await api.get(
|
|
850
|
+
const priorities = await api.get(API.PRIORITY.ALL);
|
|
830
851
|
const prioSelect = new Select({
|
|
831
852
|
name: 'priority',
|
|
832
853
|
message: 'Select priority',
|
|
@@ -835,7 +856,7 @@ Examples:
|
|
|
835
856
|
updateBody.fields.priority = { name: await prioSelect.run() };
|
|
836
857
|
}
|
|
837
858
|
if (field === 'components') {
|
|
838
|
-
const components = await api.get(
|
|
859
|
+
const components = await api.get(API.PROJECT.COMPONENTS(issue.fields.project.key));
|
|
839
860
|
if (components.length > 0) {
|
|
840
861
|
const compSelect = new Select({
|
|
841
862
|
// Wait, fieldSelect was initialized from enquirer as any.
|
|
@@ -871,7 +892,7 @@ Examples:
|
|
|
871
892
|
updateBody.fields.labels = labelStr.split(',').map((l) => l.trim()).filter((l) => l.length > 0);
|
|
872
893
|
}
|
|
873
894
|
if (field === 'fixVersions') {
|
|
874
|
-
const versions = await api.get(
|
|
895
|
+
const versions = await api.get(API.PROJECT.VERSIONS(issue.fields.project.key));
|
|
875
896
|
const unreleased = versions.filter((v) => !v.released);
|
|
876
897
|
if (unreleased.length > 0) {
|
|
877
898
|
const { selectedVersions } = await enquirer.prompt({
|
|
@@ -899,7 +920,7 @@ Examples:
|
|
|
899
920
|
return;
|
|
900
921
|
}
|
|
901
922
|
const updateSpinner = ora('Updating issue...').start();
|
|
902
|
-
await api.put(
|
|
923
|
+
await api.put(API.ISSUE.GET(issueKey), updateBody);
|
|
903
924
|
updateSpinner.succeed(`${chalk.cyan(issueKey)} updated successfully`);
|
|
904
925
|
}
|
|
905
926
|
catch (e) {
|
|
@@ -927,7 +948,7 @@ Examples:
|
|
|
927
948
|
if (options.project)
|
|
928
949
|
jqlParts.push(`project = "${options.project}"`);
|
|
929
950
|
const jql = jqlParts.join(' AND ') + ' ORDER BY updated DESC';
|
|
930
|
-
const data = await api.post(
|
|
951
|
+
const data = await api.post(API.SEARCH.JQL, {
|
|
931
952
|
jql,
|
|
932
953
|
maxResults: parseInt(options.limit),
|
|
933
954
|
fields: ['summary', 'status', 'assignee', 'updated']
|
|
@@ -946,17 +967,22 @@ Examples:
|
|
|
946
967
|
return;
|
|
947
968
|
}
|
|
948
969
|
const table = new Table({
|
|
949
|
-
|
|
970
|
+
columns: [
|
|
971
|
+
{ name: chalk.bold('Key') },
|
|
972
|
+
{ name: chalk.bold('Summary') },
|
|
973
|
+
{ name: chalk.bold('Status') },
|
|
974
|
+
{ name: chalk.bold('Assignee') }
|
|
975
|
+
]
|
|
950
976
|
});
|
|
951
977
|
data.issues.forEach((i) => {
|
|
952
|
-
table.
|
|
978
|
+
table.addRow([
|
|
953
979
|
chalk.cyan(i.key),
|
|
954
980
|
i.fields.summary ? (i.fields.summary.length > 55 ? i.fields.summary.substring(0, 52) + '...' : i.fields.summary) : '',
|
|
955
981
|
i.fields.status?.name || '',
|
|
956
982
|
i.fields.assignee?.displayName || 'Unassigned'
|
|
957
983
|
]);
|
|
958
984
|
});
|
|
959
|
-
console.log(table.
|
|
985
|
+
console.log(table.render());
|
|
960
986
|
console.log(chalk.grey(`Found ${data.issues.length} result(s)`));
|
|
961
987
|
}
|
|
962
988
|
catch (e) {
|
|
@@ -1078,13 +1104,37 @@ Examples:
|
|
|
1078
1104
|
}
|
|
1079
1105
|
const spinner = ora(`Fetching parent ${parentKey}...`).start();
|
|
1080
1106
|
try {
|
|
1081
|
-
const parent = await api.get(`/issue/${parentKey}?fields=project,summary`);
|
|
1107
|
+
const parent = await api.get(`/issue/${parentKey}?fields=project,summary,issuetype,id`);
|
|
1082
1108
|
const projectKey = parent.fields.project.key;
|
|
1109
|
+
if (parent.fields.issuetype.subtask) {
|
|
1110
|
+
spinner.fail(chalk.red(`Issue ${parentKey} is already a subtask. Cannot create a subtask of a subtask.`));
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
if (parent.fields.issuetype.name === 'Epic') {
|
|
1114
|
+
spinner.fail(chalk.red(`Issue ${parentKey} is an Epic. Epics cannot have sub-tasks.`));
|
|
1115
|
+
console.log(chalk.yellow('Tip: To add work to an Epic, create a standard issue (Story, Task) and link it to the Epic.'));
|
|
1116
|
+
return;
|
|
1117
|
+
}
|
|
1083
1118
|
spinner.text = 'Fetching subtask types...';
|
|
1084
1119
|
// Get valid subtask types for project
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1120
|
+
let subtaskTypes = [];
|
|
1121
|
+
try {
|
|
1122
|
+
// Correct V3 endpoint for creation metadata
|
|
1123
|
+
const meta = await api.get(`/issue/createmeta?projectKeys=${projectKey}`);
|
|
1124
|
+
if (meta.projects && meta.projects.length > 0) {
|
|
1125
|
+
subtaskTypes = meta.projects[0].issuetypes.filter((t) => t.subtask);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
catch (err) {
|
|
1129
|
+
// Fallback to project fetch
|
|
1130
|
+
try {
|
|
1131
|
+
const proj = await api.get(API.PROJECT.GET(projectKey));
|
|
1132
|
+
subtaskTypes = (proj.issueTypes || []).filter((t) => t.subtask);
|
|
1133
|
+
}
|
|
1134
|
+
catch (e) {
|
|
1135
|
+
console.error(chalk.red('Failed to fetch project issue types.'));
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1088
1138
|
spinner.stop();
|
|
1089
1139
|
if (subtaskTypes.length === 0) {
|
|
1090
1140
|
console.error(chalk.red(`No subtask types found in project ${projectKey}.`));
|
|
@@ -1118,13 +1168,14 @@ Examples:
|
|
|
1118
1168
|
const issueBody = {
|
|
1119
1169
|
fields: {
|
|
1120
1170
|
project: { key: projectKey },
|
|
1121
|
-
parent: {
|
|
1171
|
+
parent: { id: parent.id }, // Use ID instead of Key
|
|
1122
1172
|
issuetype: { id: subtaskTypeId },
|
|
1123
1173
|
summary: summary
|
|
1124
1174
|
}
|
|
1125
1175
|
};
|
|
1126
1176
|
if (priorityName)
|
|
1127
1177
|
issueBody.fields.priority = { name: priorityName };
|
|
1178
|
+
// ... rest of assignee logic ...
|
|
1128
1179
|
if (assigneeId === 'me') {
|
|
1129
1180
|
const me = await api.get('/myself');
|
|
1130
1181
|
issueBody.fields.assignee = { accountId: me.accountId };
|
|
@@ -1133,7 +1184,7 @@ Examples:
|
|
|
1133
1184
|
issueBody.fields.assignee = { accountId: assigneeId };
|
|
1134
1185
|
}
|
|
1135
1186
|
const createSpinner = ora('Creating subtask...').start();
|
|
1136
|
-
const result = await api.post(
|
|
1187
|
+
const result = await api.post(API.ISSUE.BASE, issueBody);
|
|
1137
1188
|
createSpinner.succeed(chalk.green(`Subtask created: ${chalk.bold(result.key)}`));
|
|
1138
1189
|
}
|
|
1139
1190
|
catch (e) {
|