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
package/src/commands/issue.ts
CHANGED
|
@@ -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';
|
|
@@ -14,6 +14,7 @@ import { registerWorklogCommand } from './issue-worklog.js';
|
|
|
14
14
|
import { registerPrCommand } from './issue-pr.js';
|
|
15
15
|
import { registerAttachCommand } from './issue-attach.js';
|
|
16
16
|
import { ConfigService } from '../services/config-service.js';
|
|
17
|
+
import { API } from '../utils/api-paths.js';
|
|
17
18
|
|
|
18
19
|
export function registerIssueCommand(program: Command) {
|
|
19
20
|
const issueCmd = new Command('issue')
|
|
@@ -68,23 +69,19 @@ Examples:
|
|
|
68
69
|
if (options.jql) jqlParts.push(options.jql);
|
|
69
70
|
|
|
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
|
-
} else if (jqlParts.length > 0 && !options.jql) {
|
|
74
|
-
// Add order if not custom jql
|
|
75
|
-
// jqlParts.push('order by updated DESC');
|
|
76
|
-
}
|
|
77
|
-
|
|
78
72
|
const jql = jqlParts.join(' AND ');
|
|
79
73
|
|
|
80
|
-
|
|
74
|
+
// Default to last 30 days if no filter provided to satisfy "unbounded" check
|
|
75
|
+
const defaultJql = 'updated >= -30d ORDER BY updated DESC';
|
|
76
|
+
const finalJql = jql || defaultJql;
|
|
77
|
+
|
|
81
78
|
const body = {
|
|
82
|
-
jql:
|
|
79
|
+
jql: finalJql,
|
|
83
80
|
maxResults: parseInt(options.limit),
|
|
84
|
-
fields: ['summary', 'status', 'assignee', 'created', 'updated', 'description']
|
|
81
|
+
fields: ['summary', 'status', 'assignee', 'created', 'updated', 'description', 'priority', 'issuetype', 'project', 'reporter']
|
|
85
82
|
};
|
|
86
83
|
|
|
87
|
-
const data = await api.post(
|
|
84
|
+
const data = await api.post(API.SEARCH.JQL, body);
|
|
88
85
|
spinner.stop();
|
|
89
86
|
|
|
90
87
|
if (!data.issues || data.issues.length === 0) {
|
|
@@ -134,11 +131,18 @@ Examples:
|
|
|
134
131
|
}
|
|
135
132
|
|
|
136
133
|
const table = new Table({
|
|
137
|
-
|
|
134
|
+
columns: [
|
|
135
|
+
{ name: chalk.bold('Key') },
|
|
136
|
+
{ name: chalk.bold('Summary') },
|
|
137
|
+
{ name: chalk.bold('Status') },
|
|
138
|
+
{ name: chalk.bold('Assignee') },
|
|
139
|
+
{ name: chalk.bold('Created') },
|
|
140
|
+
{ name: chalk.bold('Updated') }
|
|
141
|
+
]
|
|
138
142
|
});
|
|
139
143
|
|
|
140
144
|
data.issues.forEach((i: any) => {
|
|
141
|
-
table.
|
|
145
|
+
table.addRow([
|
|
142
146
|
chalk.cyan(i.key),
|
|
143
147
|
i.fields.summary ? (i.fields.summary.length > 50 ? i.fields.summary.substring(0, 47) + '...' : i.fields.summary) : '',
|
|
144
148
|
i.fields.status ? i.fields.status.name : '',
|
|
@@ -148,7 +152,7 @@ Examples:
|
|
|
148
152
|
]);
|
|
149
153
|
});
|
|
150
154
|
|
|
151
|
-
console.log(table.
|
|
155
|
+
console.log(table.render());
|
|
152
156
|
|
|
153
157
|
} catch (e: any) {
|
|
154
158
|
handleCommandError(spinner, e, 'Failed to list issues');
|
|
@@ -170,7 +174,7 @@ Examples:
|
|
|
170
174
|
if (!check.valid) { console.error(chalk.red(check.message)); return; }
|
|
171
175
|
const spinner = ora(`Fetching issue ${issueKey}...`).start();
|
|
172
176
|
try {
|
|
173
|
-
const issue = await api.get(
|
|
177
|
+
const issue = await api.get(API.ISSUE.GET(issueKey));
|
|
174
178
|
spinner.stop();
|
|
175
179
|
|
|
176
180
|
if (options.output === 'json') {
|
|
@@ -213,7 +217,7 @@ Examples:
|
|
|
213
217
|
if (issue.fields.comment && issue.fields.comment.comments.length > 0) {
|
|
214
218
|
console.log(chalk.bold('\nComments:'));
|
|
215
219
|
issue.fields.comment.comments.forEach((c: any) => {
|
|
216
|
-
console.log(chalk.cyan(c.author.displayName) + ': ' + c.body);
|
|
220
|
+
console.log(chalk.cyan(c.author.displayName) + ': ' + (parseADF(c.body) || ''));
|
|
217
221
|
});
|
|
218
222
|
}
|
|
219
223
|
console.log('');
|
|
@@ -232,6 +236,11 @@ Examples:
|
|
|
232
236
|
.option('-d, --description <text>', 'Issue description')
|
|
233
237
|
.option('--priority <name>', 'Priority name (e.g., High, Medium, Low)')
|
|
234
238
|
.option('-a, --assignee <id>', 'Assignee account ID (use "me" for self)')
|
|
239
|
+
.option('-l, --labels <list>', 'Labels (comma separated)')
|
|
240
|
+
.option('-c, --components <list>', 'Component IDs (comma separated)', (v: string, l: string[]) => l.concat([v]), [])
|
|
241
|
+
.option('--fix-versions <list>', 'Fix Version IDs (comma separated)', (v: string, l: string[]) => l.concat([v]), [])
|
|
242
|
+
.option('--due-date <date>', 'Due Date (YYYY-MM-DD)')
|
|
243
|
+
.option('--no-input', 'Disable interactive prompts for optional fields')
|
|
235
244
|
.option('--custom <key=value>', 'Custom fields (key=value, repeatable)', (v: string, l: string[]) => l.concat([v]), [])
|
|
236
245
|
.addHelpText('after', `
|
|
237
246
|
Examples:
|
|
@@ -248,7 +257,7 @@ Examples:
|
|
|
248
257
|
let projectKey = options.project;
|
|
249
258
|
if (!projectKey) {
|
|
250
259
|
const spinner = ora('Fetching projects...').start();
|
|
251
|
-
const projectData = await api.get(
|
|
260
|
+
const projectData = await api.get(API.PROJECT.SEARCH);
|
|
252
261
|
spinner.stop();
|
|
253
262
|
|
|
254
263
|
if (!projectData.values || projectData.values.length === 0) {
|
|
@@ -277,12 +286,12 @@ Examples:
|
|
|
277
286
|
let issueTypes = [];
|
|
278
287
|
try {
|
|
279
288
|
// Jira Cloud v3 - createmeta endpoint
|
|
280
|
-
const metaData = await api.get(
|
|
289
|
+
const metaData = await api.get(API.ISSUE.CREATEMETA(projectKey));
|
|
281
290
|
issueTypes = metaData.issueTypes || metaData.values || [];
|
|
282
291
|
} catch (metaErr) {
|
|
283
292
|
// Fallback: use project-level issue types
|
|
284
293
|
try {
|
|
285
|
-
const projectInfo = await api.get(
|
|
294
|
+
const projectInfo = await api.get(API.PROJECT.GET(projectKey));
|
|
286
295
|
issueTypes = projectInfo.issueTypes || [];
|
|
287
296
|
} catch {
|
|
288
297
|
issueTypes = [
|
|
@@ -339,10 +348,10 @@ Examples:
|
|
|
339
348
|
|
|
340
349
|
// ── Step 5: Priority ────────────────────────────────
|
|
341
350
|
let priorityName = options.priority;
|
|
342
|
-
if (!priorityName) {
|
|
351
|
+
if (!priorityName && !options.noInput) {
|
|
343
352
|
const spinner = ora('Fetching priorities...').start();
|
|
344
353
|
try {
|
|
345
|
-
const priorities = await api.get(
|
|
354
|
+
const priorities = await api.get(API.PRIORITY.ALL);
|
|
346
355
|
spinner.stop();
|
|
347
356
|
|
|
348
357
|
if (Array.isArray(priorities) && priorities.length > 0) {
|
|
@@ -366,73 +375,87 @@ Examples:
|
|
|
366
375
|
}
|
|
367
376
|
|
|
368
377
|
// ── Step 5.5: Components ────────────────────────────
|
|
369
|
-
let componentIds: string[] = [];
|
|
370
|
-
// Interactive only
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
378
|
+
let componentIds: string[] = options.components || [];
|
|
379
|
+
// Interactive only if components not provided and input allowed
|
|
380
|
+
if (componentIds.length === 0 && !options.noInput) {
|
|
381
|
+
const compSpinner = ora('Fetching components...').start();
|
|
382
|
+
try {
|
|
383
|
+
const components = await api.get(API.PROJECT.COMPONENTS(projectKey));
|
|
384
|
+
compSpinner.stop();
|
|
385
|
+
|
|
386
|
+
if (Array.isArray(components) && components.length > 0) {
|
|
387
|
+
const { selectedComponents } = await enquirer.prompt({
|
|
388
|
+
type: 'multiselect',
|
|
389
|
+
name: 'selectedComponents',
|
|
390
|
+
message: 'Select Components (Space to select, Enter to confirm):',
|
|
391
|
+
choices: components.map((c: any) => ({ name: c.id, message: c.name }))
|
|
392
|
+
}) as any;
|
|
393
|
+
componentIds = selectedComponents;
|
|
394
|
+
}
|
|
395
|
+
} catch {
|
|
396
|
+
compSpinner.stop();
|
|
384
397
|
}
|
|
385
|
-
} catch {
|
|
386
|
-
compSpinner.stop();
|
|
387
398
|
}
|
|
388
399
|
|
|
389
400
|
// ── Step 5.6: Labels ────────────────────────────────
|
|
390
401
|
let labels: string[] = [];
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
402
|
+
if (options.labels) {
|
|
403
|
+
labels = options.labels.split(',').map((l: string) => l.trim()).filter((l: string) => l.length > 0);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (labels.length === 0 && !options.noInput) {
|
|
407
|
+
const { inputLabels } = await enquirer.prompt({
|
|
408
|
+
type: 'input',
|
|
409
|
+
name: 'inputLabels',
|
|
410
|
+
message: 'Labels (comma-separated, optional):'
|
|
411
|
+
}) as any;
|
|
412
|
+
|
|
413
|
+
if (inputLabels && inputLabels.trim().length > 0) {
|
|
414
|
+
labels = inputLabels.split(',').map((l: string) => l.trim()).filter((l: string) => l.length > 0);
|
|
415
|
+
}
|
|
399
416
|
}
|
|
400
417
|
|
|
401
418
|
// ── Step 5.7: Fix Versions ──────────────────────────
|
|
402
|
-
let fixVersionIds: string[] = [];
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
+
let fixVersionIds: string[] = options.fixVersions || [];
|
|
420
|
+
|
|
421
|
+
if (fixVersionIds.length === 0 && !options.noInput) {
|
|
422
|
+
const verSpinner = ora('Fetching versions...').start();
|
|
423
|
+
try {
|
|
424
|
+
const versions = await api.get(API.PROJECT.VERSIONS(projectKey));
|
|
425
|
+
verSpinner.stop();
|
|
426
|
+
|
|
427
|
+
// Filter unreleased versions usually
|
|
428
|
+
const unreleased = versions.filter((v: any) => !v.released);
|
|
429
|
+
|
|
430
|
+
if (Array.isArray(unreleased) && unreleased.length > 0) {
|
|
431
|
+
const { selectedVersions } = await enquirer.prompt({
|
|
432
|
+
type: 'multiselect',
|
|
433
|
+
name: 'selectedVersions',
|
|
434
|
+
message: 'Fix Versions:',
|
|
435
|
+
choices: unreleased.map((v: any) => ({ name: v.id, message: v.name }))
|
|
436
|
+
}) as any;
|
|
437
|
+
fixVersionIds = selectedVersions;
|
|
438
|
+
}
|
|
439
|
+
} catch {
|
|
440
|
+
verSpinner.stop();
|
|
419
441
|
}
|
|
420
|
-
} catch {
|
|
421
|
-
verSpinner.stop();
|
|
422
442
|
}
|
|
423
443
|
|
|
424
444
|
// ── Step 5.8: Due Date ──────────────────────────────
|
|
425
|
-
let duedate: string | null = null;
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
445
|
+
let duedate: string | null = options.dueDate || null;
|
|
446
|
+
|
|
447
|
+
if (!duedate && !options.noInput) {
|
|
448
|
+
const { inputDueDate } = await enquirer.prompt({
|
|
449
|
+
type: 'input',
|
|
450
|
+
name: 'inputDueDate',
|
|
451
|
+
message: 'Due Date (YYYY-MM-DD, optional):',
|
|
452
|
+
validate: (val: string) => {
|
|
453
|
+
if (!val) return true;
|
|
454
|
+
return /^\d{4}-\d{2}-\d{2}$/.test(val) || 'Format must be YYYY-MM-DD';
|
|
455
|
+
}
|
|
456
|
+
}) as any;
|
|
457
|
+
if (inputDueDate) duedate = inputDueDate;
|
|
458
|
+
}
|
|
436
459
|
|
|
437
460
|
// ── Step 6: Assignee ────────────────────────────────
|
|
438
461
|
let assigneeId = options.assignee;
|
|
@@ -451,7 +474,7 @@ Examples:
|
|
|
451
474
|
if (assigneeChoice === 'me') {
|
|
452
475
|
const spinner = ora('Fetching your account...').start();
|
|
453
476
|
try {
|
|
454
|
-
const myself = await api.get(
|
|
477
|
+
const myself = await api.get(API.USER.MYSELF);
|
|
455
478
|
assigneeId = myself.accountId;
|
|
456
479
|
spinner.stop();
|
|
457
480
|
} catch {
|
|
@@ -468,7 +491,7 @@ Examples:
|
|
|
468
491
|
if (searchQuery.trim()) {
|
|
469
492
|
const spinner = ora('Searching users...').start();
|
|
470
493
|
try {
|
|
471
|
-
const users = await api.get(
|
|
494
|
+
const users = await api.get(`${API.USER.SEARCH}?query=${encodeURIComponent(searchQuery)}`);
|
|
472
495
|
spinner.stop();
|
|
473
496
|
|
|
474
497
|
if (Array.isArray(users) && users.length > 0) {
|
|
@@ -500,7 +523,7 @@ Examples:
|
|
|
500
523
|
// --assignee me flag: resolve to account ID
|
|
501
524
|
const spinner = ora('Fetching your account...').start();
|
|
502
525
|
try {
|
|
503
|
-
const myself = await api.get(
|
|
526
|
+
const myself = await api.get(API.USER.MYSELF);
|
|
504
527
|
assigneeId = myself.accountId;
|
|
505
528
|
spinner.stop();
|
|
506
529
|
} catch {
|
|
@@ -510,25 +533,27 @@ Examples:
|
|
|
510
533
|
}
|
|
511
534
|
|
|
512
535
|
// ── Confirmation ────────────────────────────────────
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
536
|
+
if (!options.noInput) {
|
|
537
|
+
console.log(chalk.blue('\n── Issue Summary ──────────────────'));
|
|
538
|
+
console.log(` Project: ${chalk.cyan(projectKey)}`);
|
|
539
|
+
console.log(` Type: ${issueTypeName}`);
|
|
540
|
+
console.log(` Summary: ${summary}`);
|
|
541
|
+
console.log(` Description: ${description || chalk.grey('(none)')}`);
|
|
542
|
+
console.log(` Priority: ${priorityName || chalk.grey('(default)')}`);
|
|
543
|
+
console.log(` Assignee: ${assigneeId || chalk.grey('Unassigned')}`);
|
|
544
|
+
console.log(chalk.blue('──────────────────────────────────\n'));
|
|
545
|
+
|
|
546
|
+
const { confirmed } = await enquirer.prompt({
|
|
547
|
+
type: 'confirm',
|
|
548
|
+
name: 'confirmed',
|
|
549
|
+
message: 'Create this issue?',
|
|
550
|
+
initial: true
|
|
551
|
+
}) as any;
|
|
552
|
+
|
|
553
|
+
if (!confirmed) {
|
|
554
|
+
console.log(chalk.yellow('Issue creation cancelled.'));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
532
557
|
}
|
|
533
558
|
|
|
534
559
|
// ── Build Request Body ──────────────────────────────
|
|
@@ -583,7 +608,7 @@ Examples:
|
|
|
583
608
|
|
|
584
609
|
// ── Create Issue ────────────────────────────────────
|
|
585
610
|
const spinner = ora('Creating issue...').start();
|
|
586
|
-
const result = await api.post(
|
|
611
|
+
const result = await api.post(API.ISSUE.BASE, issueBody);
|
|
587
612
|
spinner.succeed(chalk.green(`Issue created: ${chalk.bold(result.key)}`));
|
|
588
613
|
|
|
589
614
|
console.log(chalk.grey(`View it: jira issue view ${result.key}`));
|
|
@@ -611,11 +636,11 @@ Examples:
|
|
|
611
636
|
const spinner = ora(`Fetching transitions for ${issueKey}...`).start();
|
|
612
637
|
try {
|
|
613
638
|
// Fetch current issue to show context
|
|
614
|
-
const issue = await api.get(
|
|
639
|
+
const issue = await api.get(`${API.ISSUE.GET(issueKey)}?fields=summary,status`);
|
|
615
640
|
const currentStatus = issue.fields.status.name;
|
|
616
641
|
|
|
617
642
|
// Fetch available transitions
|
|
618
|
-
const transData = await api.get(
|
|
643
|
+
const transData = await api.get(API.ISSUE.TRANSITIONS(issueKey));
|
|
619
644
|
spinner.stop();
|
|
620
645
|
|
|
621
646
|
if (!transData.transitions || transData.transitions.length === 0) {
|
|
@@ -662,7 +687,7 @@ Examples:
|
|
|
662
687
|
|
|
663
688
|
// Execute transition
|
|
664
689
|
const execSpinner = ora(`Transitioning to "${targetTransition.to.name}"...`).start();
|
|
665
|
-
await api.post(
|
|
690
|
+
await api.post(API.ISSUE.TRANSITIONS(issueKey), {
|
|
666
691
|
transition: { id: targetTransition.id }
|
|
667
692
|
});
|
|
668
693
|
execSpinner.succeed(chalk.green(`${issueKey} transitioned: ${currentStatus} → ${chalk.bold(targetTransition.to.name)}`));
|
|
@@ -693,7 +718,7 @@ Examples:
|
|
|
693
718
|
if (!assigneeId) {
|
|
694
719
|
// Interactive selection
|
|
695
720
|
const spinner = ora(`Fetching issue ${issueKey}...`).start();
|
|
696
|
-
const issue = await api.get(
|
|
721
|
+
const issue = await api.get(`${API.ISSUE.GET(issueKey)}?fields=summary,assignee`);
|
|
697
722
|
spinner.stop();
|
|
698
723
|
|
|
699
724
|
const currentAssignee = issue.fields.assignee?.displayName || 'Unassigned';
|
|
@@ -715,7 +740,7 @@ Examples:
|
|
|
715
740
|
|
|
716
741
|
if (assigneeId === 'me') {
|
|
717
742
|
const spinner = ora('Fetching your account...').start();
|
|
718
|
-
const myself = await api.get(
|
|
743
|
+
const myself = await api.get(API.USER.MYSELF);
|
|
719
744
|
assigneeId = myself.accountId;
|
|
720
745
|
spinner.stop();
|
|
721
746
|
}
|
|
@@ -728,7 +753,7 @@ Examples:
|
|
|
728
753
|
}) as any;
|
|
729
754
|
|
|
730
755
|
const spinner = ora('Searching users...').start();
|
|
731
|
-
const users = await api.get(
|
|
756
|
+
const users = await api.get(`${API.USER.SEARCH}?query=${encodeURIComponent(searchQuery)}`);
|
|
732
757
|
spinner.stop();
|
|
733
758
|
|
|
734
759
|
if (!Array.isArray(users) || users.length === 0) {
|
|
@@ -753,7 +778,7 @@ Examples:
|
|
|
753
778
|
? { accountId: null }
|
|
754
779
|
: { accountId: assigneeId };
|
|
755
780
|
|
|
756
|
-
await api.put(
|
|
781
|
+
await api.put(API.ISSUE.ASSIGNEE(issueKey), body);
|
|
757
782
|
spinner.succeed(chalk.green(`${issueKey} ${assigneeId === 'none' ? 'unassigned' : 'assigned'} successfully.`));
|
|
758
783
|
|
|
759
784
|
} catch (e: any) {
|
|
@@ -798,7 +823,7 @@ Examples:
|
|
|
798
823
|
}
|
|
799
824
|
|
|
800
825
|
const spinner = ora('Adding comment...').start();
|
|
801
|
-
await api.post(
|
|
826
|
+
await api.post(API.ISSUE.COMMENT(issueKey), {
|
|
802
827
|
body: textToADF(commentText)
|
|
803
828
|
});
|
|
804
829
|
spinner.succeed(chalk.green(`Comment added to ${issueKey}.`));
|
|
@@ -828,7 +853,7 @@ Examples:
|
|
|
828
853
|
if (!check.valid) { console.error(chalk.red(check.message)); return; }
|
|
829
854
|
const spinner = ora(`Fetching issue ${issueKey}...`).start();
|
|
830
855
|
try {
|
|
831
|
-
const issue = await api.get(
|
|
856
|
+
const issue = await api.get(`${API.ISSUE.GET(issueKey)}?fields=summary,description,priority`);
|
|
832
857
|
spinner.stop();
|
|
833
858
|
|
|
834
859
|
const updateBody: any = { fields: {} };
|
|
@@ -886,7 +911,7 @@ Examples:
|
|
|
886
911
|
if (desc) updateBody.fields.description = textToADF(desc);
|
|
887
912
|
}
|
|
888
913
|
if (field === 'priority') {
|
|
889
|
-
const priorities = await api.get(
|
|
914
|
+
const priorities = await api.get(API.PRIORITY.ALL);
|
|
890
915
|
const prioSelect = new Select({
|
|
891
916
|
name: 'priority',
|
|
892
917
|
message: 'Select priority',
|
|
@@ -895,7 +920,7 @@ Examples:
|
|
|
895
920
|
updateBody.fields.priority = { name: await prioSelect.run() };
|
|
896
921
|
}
|
|
897
922
|
if (field === 'components') {
|
|
898
|
-
const components = await api.get(
|
|
923
|
+
const components = await api.get(API.PROJECT.COMPONENTS(issue.fields.project.key));
|
|
899
924
|
if (components.length > 0) {
|
|
900
925
|
const compSelect = new Select({ // Using Enquirer directly via 'any' above, but actually Select is single select?
|
|
901
926
|
// Wait, fieldSelect was initialized from enquirer as any.
|
|
@@ -931,7 +956,7 @@ Examples:
|
|
|
931
956
|
updateBody.fields.labels = labelStr.split(',').map((l: string) => l.trim()).filter((l: string) => l.length > 0);
|
|
932
957
|
}
|
|
933
958
|
if (field === 'fixVersions') {
|
|
934
|
-
const versions = await api.get(
|
|
959
|
+
const versions = await api.get(API.PROJECT.VERSIONS(issue.fields.project.key));
|
|
935
960
|
const unreleased = versions.filter((v: any) => !v.released);
|
|
936
961
|
if (unreleased.length > 0) {
|
|
937
962
|
const { selectedVersions } = await enquirer.prompt({
|
|
@@ -961,7 +986,7 @@ Examples:
|
|
|
961
986
|
}
|
|
962
987
|
|
|
963
988
|
const updateSpinner = ora('Updating issue...').start();
|
|
964
|
-
await api.put(
|
|
989
|
+
await api.put(API.ISSUE.GET(issueKey), updateBody);
|
|
965
990
|
updateSpinner.succeed(`${chalk.cyan(issueKey)} updated successfully`);
|
|
966
991
|
|
|
967
992
|
} catch (e: any) {
|
|
@@ -990,7 +1015,7 @@ Examples:
|
|
|
990
1015
|
if (options.project) jqlParts.push(`project = "${options.project}"`);
|
|
991
1016
|
const jql = jqlParts.join(' AND ') + ' ORDER BY updated DESC';
|
|
992
1017
|
|
|
993
|
-
const data = await api.post(
|
|
1018
|
+
const data = await api.post(API.SEARCH.JQL, {
|
|
994
1019
|
jql,
|
|
995
1020
|
maxResults: parseInt(options.limit),
|
|
996
1021
|
fields: ['summary', 'status', 'assignee', 'updated']
|
|
@@ -1012,17 +1037,22 @@ Examples:
|
|
|
1012
1037
|
}
|
|
1013
1038
|
|
|
1014
1039
|
const table = new Table({
|
|
1015
|
-
|
|
1040
|
+
columns: [
|
|
1041
|
+
{ name: chalk.bold('Key') },
|
|
1042
|
+
{ name: chalk.bold('Summary') },
|
|
1043
|
+
{ name: chalk.bold('Status') },
|
|
1044
|
+
{ name: chalk.bold('Assignee') }
|
|
1045
|
+
]
|
|
1016
1046
|
});
|
|
1017
1047
|
data.issues.forEach((i: any) => {
|
|
1018
|
-
table.
|
|
1048
|
+
table.addRow([
|
|
1019
1049
|
chalk.cyan(i.key),
|
|
1020
1050
|
i.fields.summary ? (i.fields.summary.length > 55 ? i.fields.summary.substring(0, 52) + '...' : i.fields.summary) : '',
|
|
1021
1051
|
i.fields.status?.name || '',
|
|
1022
1052
|
i.fields.assignee?.displayName || 'Unassigned'
|
|
1023
1053
|
]);
|
|
1024
1054
|
});
|
|
1025
|
-
console.log(table.
|
|
1055
|
+
console.log(table.render());
|
|
1026
1056
|
console.log(chalk.grey(`Found ${data.issues.length} result(s)`));
|
|
1027
1057
|
|
|
1028
1058
|
} catch (e) {
|
|
@@ -1136,14 +1166,39 @@ Examples:
|
|
|
1136
1166
|
|
|
1137
1167
|
const spinner = ora(`Fetching parent ${parentKey}...`).start();
|
|
1138
1168
|
try {
|
|
1139
|
-
const parent = await api.get(`/issue/${parentKey}?fields=project,summary`);
|
|
1169
|
+
const parent = await api.get(`/issue/${parentKey}?fields=project,summary,issuetype,id`);
|
|
1140
1170
|
const projectKey = parent.fields.project.key;
|
|
1171
|
+
|
|
1172
|
+
if (parent.fields.issuetype.subtask) {
|
|
1173
|
+
spinner.fail(chalk.red(`Issue ${parentKey} is already a subtask. Cannot create a subtask of a subtask.`));
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
if (parent.fields.issuetype.name === 'Epic') {
|
|
1178
|
+
spinner.fail(chalk.red(`Issue ${parentKey} is an Epic. Epics cannot have sub-tasks.`));
|
|
1179
|
+
console.log(chalk.yellow('Tip: To add work to an Epic, create a standard issue (Story, Task) and link it to the Epic.'));
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1141
1183
|
spinner.text = 'Fetching subtask types...';
|
|
1142
1184
|
|
|
1143
1185
|
// Get valid subtask types for project
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1186
|
+
let subtaskTypes: any[] = [];
|
|
1187
|
+
try {
|
|
1188
|
+
// Correct V3 endpoint for creation metadata
|
|
1189
|
+
const meta = await api.get(`/issue/createmeta?projectKeys=${projectKey}`);
|
|
1190
|
+
if (meta.projects && meta.projects.length > 0) {
|
|
1191
|
+
subtaskTypes = meta.projects[0].issuetypes.filter((t: any) => t.subtask);
|
|
1192
|
+
}
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
// Fallback to project fetch
|
|
1195
|
+
try {
|
|
1196
|
+
const proj = await api.get(API.PROJECT.GET(projectKey));
|
|
1197
|
+
subtaskTypes = (proj.issueTypes || []).filter((t: any) => t.subtask);
|
|
1198
|
+
} catch (e) {
|
|
1199
|
+
console.error(chalk.red('Failed to fetch project issue types.'));
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1147
1202
|
spinner.stop();
|
|
1148
1203
|
|
|
1149
1204
|
if (subtaskTypes.length === 0) {
|
|
@@ -1183,13 +1238,15 @@ Examples:
|
|
|
1183
1238
|
const issueBody: any = {
|
|
1184
1239
|
fields: {
|
|
1185
1240
|
project: { key: projectKey },
|
|
1186
|
-
parent: {
|
|
1241
|
+
parent: { id: parent.id }, // Use ID instead of Key
|
|
1187
1242
|
issuetype: { id: subtaskTypeId },
|
|
1188
1243
|
summary: summary
|
|
1189
1244
|
}
|
|
1190
1245
|
};
|
|
1191
1246
|
|
|
1192
1247
|
if (priorityName) issueBody.fields.priority = { name: priorityName };
|
|
1248
|
+
// ... rest of assignee logic ...
|
|
1249
|
+
|
|
1193
1250
|
if (assigneeId === 'me') {
|
|
1194
1251
|
const me = await api.get('/myself');
|
|
1195
1252
|
issueBody.fields.assignee = { accountId: me.accountId };
|
|
@@ -1198,7 +1255,7 @@ Examples:
|
|
|
1198
1255
|
}
|
|
1199
1256
|
|
|
1200
1257
|
const createSpinner = ora('Creating subtask...').start();
|
|
1201
|
-
const result = await api.post(
|
|
1258
|
+
const result = await api.post(API.ISSUE.BASE, issueBody);
|
|
1202
1259
|
createSpinner.succeed(chalk.green(`Subtask created: ${chalk.bold(result.key)}`));
|
|
1203
1260
|
|
|
1204
1261
|
} catch (e: any) {
|
package/src/commands/project.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
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
|
-
import ora from '
|
|
5
|
+
import ora from '../utils/spinner.js';
|
|
6
6
|
import { handleCommandError } from '../utils/error-handler.js';
|
|
7
7
|
|
|
8
8
|
export function registerProjectCommand(program: Command) {
|
|
@@ -37,11 +37,16 @@ Common Actions:
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const table = new Table({
|
|
40
|
-
|
|
40
|
+
columns: [
|
|
41
|
+
{ name: chalk.bold('Key') },
|
|
42
|
+
{ name: chalk.bold('Name') },
|
|
43
|
+
{ name: chalk.bold('Leader') },
|
|
44
|
+
{ name: chalk.bold('Style') }
|
|
45
|
+
]
|
|
41
46
|
});
|
|
42
47
|
|
|
43
48
|
data.values.forEach((p: any) => {
|
|
44
|
-
table.
|
|
49
|
+
table.addRow([
|
|
45
50
|
chalk.cyan(p.key),
|
|
46
51
|
p.name,
|
|
47
52
|
p.lead ? p.lead.displayName : 'N/A',
|
|
@@ -49,7 +54,7 @@ Common Actions:
|
|
|
49
54
|
]);
|
|
50
55
|
});
|
|
51
56
|
|
|
52
|
-
console.log(table.
|
|
57
|
+
console.log(table.render());
|
|
53
58
|
} catch (e: any) {
|
|
54
59
|
handleCommandError(spinner, e, 'Failed to list projects');
|
|
55
60
|
}
|