jira-pilot 2.0.4 → 2.1.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 +216 -173
- package/bin/{jira.js → jira.ts} +10 -1
- package/dist/bin/jira.js +64 -0
- package/package.json +21 -15
- package/src/commands/ai-actions/{plan.js → plan.ts} +9 -9
- package/src/commands/ai-actions/{review.js → review.ts} +2 -2
- package/src/commands/ai-actions/{standup.js → standup.ts} +4 -4
- package/src/commands/{ai.js → ai.ts} +11 -11
- package/src/commands/{board.js → board.ts} +11 -11
- package/src/commands/bulk.ts +230 -0
- package/src/commands/{config.js → config.ts} +57 -8
- package/src/commands/dashboard.ts +222 -0
- package/src/commands/filter.ts +84 -0
- package/src/commands/{git.js → git.ts} +4 -4
- package/src/commands/issue-attach.ts +44 -0
- package/src/commands/issue-pr.ts +87 -0
- package/src/commands/issue-worklog.ts +90 -0
- package/src/commands/{issue.js → issue.ts} +359 -68
- package/src/commands/{mcp.js → mcp.ts} +2 -2
- package/src/commands/{project.js → project.ts} +11 -11
- package/src/commands/sprint.ts +269 -0
- package/src/server/{mcp-server.js → mcp-server.ts} +235 -8
- package/src/services/{ai-service.js → ai-service.ts} +16 -16
- package/src/services/{api-service.js → api-service.ts} +33 -9
- package/src/services/config-service.ts +21 -0
- package/src/types.ts +68 -0
- package/src/utils/{adf-parser.js → adf-parser.ts} +12 -12
- package/src/utils/config-store.ts +109 -0
- package/src/utils/{config.js → config.ts} +14 -41
- package/src/utils/{error-handler.js → error-handler.ts} +2 -1
- package/src/utils/{text-to-adf.js → text-to-adf.ts} +1 -1
- package/src/utils/{validators.js → validators.ts} +4 -4
- package/src/commands/bulk.js +0 -108
- package/src/commands/dashboard.js +0 -89
- package/src/commands/sprint.js +0 -153
package/src/commands/bulk.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import ora from 'ora';
|
|
4
|
-
import enquirer from 'enquirer';
|
|
5
|
-
import { api } from '../services/api-service.js';
|
|
6
|
-
import { handleCommandError } from '../utils/error-handler.js';
|
|
7
|
-
|
|
8
|
-
export function registerBulkCommand(program) {
|
|
9
|
-
const bulkCmd = new Command('bulk')
|
|
10
|
-
.description('Bulk operations on Jira issues')
|
|
11
|
-
.addHelpText('after', `
|
|
12
|
-
Common Actions:
|
|
13
|
-
$ jira bulk transition -j "project = PROJ AND status = 'To Do'" -s "In Progress"
|
|
14
|
-
`);
|
|
15
|
-
|
|
16
|
-
// ── BULK TRANSITION ──────────────────────────────────────────────
|
|
17
|
-
bulkCmd
|
|
18
|
-
.command('transition')
|
|
19
|
-
.description('Transition multiple issues matching a JQL filter')
|
|
20
|
-
.requiredOption('-j, --jql <query>', 'JQL query to select issues')
|
|
21
|
-
.option('-s, --status <name>', 'Target status name')
|
|
22
|
-
.option('-y, --yes', 'Skip confirmation prompt')
|
|
23
|
-
.option('-l, --limit <n>', 'Max issues to process', '50')
|
|
24
|
-
.addHelpText('after', `
|
|
25
|
-
Examples:
|
|
26
|
-
$ jira bulk transition -j "project = PROJ AND status = 'To Do'" -s "In Progress"
|
|
27
|
-
$ jira bulk transition -j "assignee = currentUser() AND status = Review" -s Done -y
|
|
28
|
-
`)
|
|
29
|
-
.action(async (options) => {
|
|
30
|
-
const spinner = ora('Finding matching issues...').start();
|
|
31
|
-
try {
|
|
32
|
-
const data = await api.post('/search/jql', {
|
|
33
|
-
jql: options.jql,
|
|
34
|
-
maxResults: parseInt(options.limit),
|
|
35
|
-
fields: ['summary', 'status']
|
|
36
|
-
});
|
|
37
|
-
spinner.stop();
|
|
38
|
-
|
|
39
|
-
if (!data.issues || data.issues.length === 0) {
|
|
40
|
-
console.log(chalk.yellow('No issues match the query.'));
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
console.log(chalk.bold(`\nFound ${data.issues.length} issue(s):\n`));
|
|
45
|
-
data.issues.forEach(i => {
|
|
46
|
-
console.log(` ${chalk.cyan(i.key)} ${i.fields.summary} [${i.fields.status.name}]`);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
let targetStatus = options.status;
|
|
50
|
-
|
|
51
|
-
if (!targetStatus) {
|
|
52
|
-
// Get transitions from the first issue to show available statuses
|
|
53
|
-
const transData = await api.get(`/issue/${data.issues[0].key}/transitions`);
|
|
54
|
-
const { Select } = enquirer;
|
|
55
|
-
const statusSelect = new Select({
|
|
56
|
-
name: 'status',
|
|
57
|
-
message: 'Target status',
|
|
58
|
-
choices: transData.transitions.map(t => ({ name: t.name, message: t.name }))
|
|
59
|
-
});
|
|
60
|
-
targetStatus = await statusSelect.run();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (!options.yes) {
|
|
64
|
-
const { Confirm } = enquirer;
|
|
65
|
-
const confirm = new Confirm({
|
|
66
|
-
name: 'proceed',
|
|
67
|
-
message: `Transition ${data.issues.length} issue(s) to "${targetStatus}"?`
|
|
68
|
-
});
|
|
69
|
-
if (!await confirm.run()) {
|
|
70
|
-
console.log(chalk.yellow('Cancelled.'));
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const transSpinner = ora(`Transitioning ${data.issues.length} issue(s)...`).start();
|
|
76
|
-
let success = 0;
|
|
77
|
-
let failed = 0;
|
|
78
|
-
|
|
79
|
-
for (const issue of data.issues) {
|
|
80
|
-
try {
|
|
81
|
-
const transData = await api.get(`/issue/${issue.key}/transitions`);
|
|
82
|
-
const transition = transData.transitions.find(
|
|
83
|
-
t => t.name.toLowerCase() === targetStatus.toLowerCase()
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
if (transition) {
|
|
87
|
-
await api.post(`/issue/${issue.key}/transitions`, {
|
|
88
|
-
transition: { id: transition.id }
|
|
89
|
-
});
|
|
90
|
-
success++;
|
|
91
|
-
} else {
|
|
92
|
-
failed++;
|
|
93
|
-
}
|
|
94
|
-
} catch {
|
|
95
|
-
failed++;
|
|
96
|
-
}
|
|
97
|
-
transSpinner.text = `Transitioning... (${success + failed}/${data.issues.length})`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
transSpinner.succeed(`Done: ${chalk.green(`${success} succeeded`)}, ${failed > 0 ? chalk.red(`${failed} failed`) : '0 failed'}`);
|
|
101
|
-
|
|
102
|
-
} catch (e) {
|
|
103
|
-
handleCommandError(spinner, e, 'Bulk transition failed');
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
program.addCommand(bulkCmd);
|
|
108
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { table } from 'table';
|
|
4
|
-
import ora from 'ora';
|
|
5
|
-
import { api } from '../services/api-service.js';
|
|
6
|
-
import { handleCommandError } from '../utils/error-handler.js';
|
|
7
|
-
|
|
8
|
-
export function registerDashboardCommand(program) {
|
|
9
|
-
program
|
|
10
|
-
.command('dashboard')
|
|
11
|
-
.description('Show a quick overview of your Jira activity')
|
|
12
|
-
.option('-o, --output <format>', 'Output format (json)')
|
|
13
|
-
.addHelpText('after', `
|
|
14
|
-
Examples:
|
|
15
|
-
$ jira dashboard
|
|
16
|
-
$ jira dashboard --output json
|
|
17
|
-
`)
|
|
18
|
-
.action(async (options) => {
|
|
19
|
-
const spinner = ora('Loading dashboard...').start();
|
|
20
|
-
try {
|
|
21
|
-
// Fetch in parallel: my open issues + recently updated
|
|
22
|
-
const [myIssues, recentIssues] = await Promise.all([
|
|
23
|
-
api.post('/search/jql', {
|
|
24
|
-
jql: 'assignee = currentUser() AND statusCategory != Done ORDER BY priority ASC, updated DESC',
|
|
25
|
-
maxResults: 8,
|
|
26
|
-
fields: ['summary', 'status', 'priority', 'updated']
|
|
27
|
-
}),
|
|
28
|
-
api.post('/search/jql', {
|
|
29
|
-
jql: 'assignee = currentUser() ORDER BY updated DESC',
|
|
30
|
-
maxResults: 5,
|
|
31
|
-
fields: ['summary', 'status', 'updated']
|
|
32
|
-
})
|
|
33
|
-
]);
|
|
34
|
-
spinner.stop();
|
|
35
|
-
|
|
36
|
-
if (options.output === 'json') {
|
|
37
|
-
console.log(JSON.stringify({
|
|
38
|
-
openIssues: (myIssues.issues || []).map(i => ({
|
|
39
|
-
key: i.key, summary: i.fields.summary,
|
|
40
|
-
status: i.fields.status?.name, priority: i.fields.priority?.name
|
|
41
|
-
})),
|
|
42
|
-
recentActivity: (recentIssues.issues || []).map(i => ({
|
|
43
|
-
key: i.key, summary: i.fields.summary,
|
|
44
|
-
status: i.fields.status?.name, updated: i.fields.updated
|
|
45
|
-
}))
|
|
46
|
-
}, null, 2));
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// ── Open Issues ──────────────────────────────────────
|
|
51
|
-
console.log(chalk.bold('\n📋 Your Open Issues') + chalk.grey(` (${myIssues.total || 0} total)`));
|
|
52
|
-
|
|
53
|
-
if (myIssues.issues && myIssues.issues.length > 0) {
|
|
54
|
-
const openTable = [
|
|
55
|
-
[chalk.bold('Key'), chalk.bold('Summary'), chalk.bold('Status'), chalk.bold('Priority')]
|
|
56
|
-
];
|
|
57
|
-
myIssues.issues.forEach(i => {
|
|
58
|
-
const prio = i.fields.priority?.name || '';
|
|
59
|
-
const prioColor = prio === 'Highest' || prio === 'High' ? chalk.red(prio) : prio === 'Low' || prio === 'Lowest' ? chalk.blue(prio) : prio;
|
|
60
|
-
openTable.push([
|
|
61
|
-
chalk.cyan(i.key),
|
|
62
|
-
i.fields.summary ? (i.fields.summary.length > 50 ? i.fields.summary.substring(0, 47) + '...' : i.fields.summary) : '',
|
|
63
|
-
i.fields.status?.name || '',
|
|
64
|
-
prioColor
|
|
65
|
-
]);
|
|
66
|
-
});
|
|
67
|
-
console.log(table(openTable));
|
|
68
|
-
} else {
|
|
69
|
-
console.log(chalk.green(' 🎉 No open issues — nice work!\n'));
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ── Recent Activity ──────────────────────────────────
|
|
73
|
-
console.log(chalk.bold('🕐 Recent Activity'));
|
|
74
|
-
|
|
75
|
-
if (recentIssues.issues && recentIssues.issues.length > 0) {
|
|
76
|
-
recentIssues.issues.forEach(i => {
|
|
77
|
-
const updated = i.fields.updated ? new Date(i.fields.updated).toLocaleDateString() : '';
|
|
78
|
-
console.log(` ${chalk.cyan(i.key)} ${i.fields.summary || ''} ${chalk.grey(`[${i.fields.status?.name}] ${updated}`)}`);
|
|
79
|
-
});
|
|
80
|
-
} else {
|
|
81
|
-
console.log(chalk.grey(' No recent activity.'));
|
|
82
|
-
}
|
|
83
|
-
console.log('');
|
|
84
|
-
|
|
85
|
-
} catch (e) {
|
|
86
|
-
handleCommandError(spinner, e, 'Failed to load dashboard');
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
}
|
package/src/commands/sprint.js
DELETED
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { table } from 'table';
|
|
4
|
-
import { api } from '../services/api-service.js';
|
|
5
|
-
import ora from 'ora';
|
|
6
|
-
import { handleCommandError } from '../utils/error-handler.js';
|
|
7
|
-
|
|
8
|
-
export function registerSprintCommand(program) {
|
|
9
|
-
const sprintCmd = new Command('sprint')
|
|
10
|
-
.description('Manage Sprints')
|
|
11
|
-
.addHelpText('after', `
|
|
12
|
-
Common Actions:
|
|
13
|
-
$ jira sprint list --board <ID|Name> # List sprints for a board
|
|
14
|
-
`);
|
|
15
|
-
|
|
16
|
-
sprintCmd
|
|
17
|
-
.command('list')
|
|
18
|
-
.description('List sprints for a board')
|
|
19
|
-
.requiredOption('-b, --board <id>', 'Board ID or name')
|
|
20
|
-
.option('-s, --state <state>', 'State (active, future, closed)', 'active,future')
|
|
21
|
-
.action(async (options) => {
|
|
22
|
-
const spinner = ora(`Fetching sprints for board ${options.board}...`).start();
|
|
23
|
-
try {
|
|
24
|
-
let boardId = options.board;
|
|
25
|
-
|
|
26
|
-
// If board option is not a number, look it up by name
|
|
27
|
-
if (isNaN(boardId)) {
|
|
28
|
-
spinner.text = `Looking up board "${options.board}"...`;
|
|
29
|
-
const boardData = await api.agileGet(`/board?name=${encodeURIComponent(options.board)}`);
|
|
30
|
-
|
|
31
|
-
if (!boardData.values || boardData.values.length === 0) {
|
|
32
|
-
throw new Error(`Board with name "${options.board}" not found. Please provide the numeric Board ID.`);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (boardData.values.length > 1) {
|
|
36
|
-
const exact = boardData.values.find(b => b.name.toLowerCase() === options.board.toLowerCase());
|
|
37
|
-
if (exact) {
|
|
38
|
-
boardId = exact.id;
|
|
39
|
-
} else {
|
|
40
|
-
console.log(chalk.yellow(`\nMultiple boards found for "${options.board}". Using "${boardData.values[0].name}" (ID: ${boardData.values[0].id}).`));
|
|
41
|
-
boardId = boardData.values[0].id;
|
|
42
|
-
}
|
|
43
|
-
} else {
|
|
44
|
-
boardId = boardData.values[0].id;
|
|
45
|
-
}
|
|
46
|
-
spinner.text = `Fetching sprints for board ${options.board} (ID: ${boardId})...`;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const data = await api.agileGet(`/board/${boardId}/sprint?state=${options.state}`);
|
|
50
|
-
spinner.stop();
|
|
51
|
-
|
|
52
|
-
if (!data.values || data.values.length === 0) {
|
|
53
|
-
console.log(chalk.yellow('No sprints found.'));
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const tableData = [
|
|
58
|
-
[chalk.bold('ID'), chalk.bold('Name'), chalk.bold('State'), chalk.bold('Dates')]
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
data.values.forEach(s => {
|
|
62
|
-
tableData.push([
|
|
63
|
-
s.id,
|
|
64
|
-
s.name,
|
|
65
|
-
s.state === 'active' ? chalk.green(s.state) : s.state,
|
|
66
|
-
`${s.startDate ? s.startDate.split('T')[0] : ''} -> ${s.endDate ? s.endDate.split('T')[0] : ''}`
|
|
67
|
-
]);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
console.log(table(tableData));
|
|
71
|
-
|
|
72
|
-
} catch (e) {
|
|
73
|
-
handleCommandError(spinner, e, 'Failed to list sprints');
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
// ── SPRINT ISSUES ────────────────────────────────────────────────
|
|
78
|
-
sprintCmd
|
|
79
|
-
.command('issues')
|
|
80
|
-
.description('List issues in the active sprint')
|
|
81
|
-
.requiredOption('-b, --board <id>', 'Board ID or name')
|
|
82
|
-
.option('-o, --output <format>', 'Output format (json)')
|
|
83
|
-
.addHelpText('after', `
|
|
84
|
-
Examples:
|
|
85
|
-
$ jira sprint issues --board 5
|
|
86
|
-
$ jira sprint issues --board "My Board" --output json
|
|
87
|
-
`)
|
|
88
|
-
.action(async (options) => {
|
|
89
|
-
const spinner = ora('Fetching active sprint...').start();
|
|
90
|
-
try {
|
|
91
|
-
let boardId = options.board;
|
|
92
|
-
|
|
93
|
-
if (isNaN(boardId)) {
|
|
94
|
-
spinner.text = `Looking up board "${options.board}"...`;
|
|
95
|
-
const boardData = await api.agileGet(`/board?name=${encodeURIComponent(options.board)}`);
|
|
96
|
-
if (!boardData.values || boardData.values.length === 0) {
|
|
97
|
-
throw new Error(`Board "${options.board}" not found.`);
|
|
98
|
-
}
|
|
99
|
-
boardId = boardData.values[0].id;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Get active sprint
|
|
103
|
-
const sprints = await api.agileGet(`/board/${boardId}/sprint?state=active`);
|
|
104
|
-
if (!sprints.values || sprints.values.length === 0) {
|
|
105
|
-
spinner.stop();
|
|
106
|
-
console.log(chalk.yellow('No active sprint found.'));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const activeSprint = sprints.values[0];
|
|
111
|
-
spinner.text = `Fetching issues for sprint "${activeSprint.name}"...`;
|
|
112
|
-
|
|
113
|
-
const issues = await api.agileGet(`/sprint/${activeSprint.id}/issue?maxResults=50&fields=summary,status,assignee,priority`);
|
|
114
|
-
spinner.stop();
|
|
115
|
-
|
|
116
|
-
if (!issues.issues || issues.issues.length === 0) {
|
|
117
|
-
console.log(chalk.yellow('No issues in active sprint.'));
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
console.log(chalk.bold(`\n🏃 Sprint: ${activeSprint.name}\n`));
|
|
122
|
-
|
|
123
|
-
if (options.output === 'json') {
|
|
124
|
-
console.log(JSON.stringify(issues.issues.map(i => ({
|
|
125
|
-
key: i.key, summary: i.fields.summary,
|
|
126
|
-
status: i.fields.status?.name, assignee: i.fields.assignee?.displayName || null,
|
|
127
|
-
priority: i.fields.priority?.name
|
|
128
|
-
})), null, 2));
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const tableData = [
|
|
133
|
-
[chalk.bold('Key'), chalk.bold('Summary'), chalk.bold('Status'), chalk.bold('Assignee'), chalk.bold('Priority')]
|
|
134
|
-
];
|
|
135
|
-
issues.issues.forEach(i => {
|
|
136
|
-
tableData.push([
|
|
137
|
-
chalk.cyan(i.key),
|
|
138
|
-
i.fields.summary ? (i.fields.summary.length > 50 ? i.fields.summary.substring(0, 47) + '...' : i.fields.summary) : '',
|
|
139
|
-
i.fields.status?.name || '',
|
|
140
|
-
i.fields.assignee?.displayName || 'Unassigned',
|
|
141
|
-
i.fields.priority?.name || ''
|
|
142
|
-
]);
|
|
143
|
-
});
|
|
144
|
-
console.log(table(tableData));
|
|
145
|
-
console.log(chalk.grey(`${issues.issues.length} issue(s) in sprint`));
|
|
146
|
-
|
|
147
|
-
} catch (e) {
|
|
148
|
-
handleCommandError(spinner, e, 'Failed to list sprint issues');
|
|
149
|
-
}
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
program.addCommand(sprintCmd);
|
|
153
|
-
}
|