Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ A modern, extensible command-line interface for Atlassian JIRA built with Factor
## ✨ Features

- 📋 **Issue Management**: Create, read, update, and delete JIRA issues with full CRUD operations
- 📝 **Markdown Support**: Export issues to markdown files and create/update issues from markdown
- 📊 **Project Information**: View project details, statistics, and team insights
- 🏃 **Sprint Management**: Monitor sprint progress, burndown charts, and team velocity
- ⚙️ **Smart Configuration**: Environment variables and CLI options for flexible setup
Expand Down Expand Up @@ -139,14 +140,17 @@ export JIRA_API_TOKEN="your-api-token"
### Read an Issue

```bash
# Read by issue key
jira issue --get PROJ-123
# View in terminal
jira issue view PROJ-123

# View as markdown in terminal
jira issue view PROJ-123 --format markdown

# Get issue with full details
jira issue --get PROJ-123 --verbose
# Export to markdown file
jira issue view PROJ-123 --output ./issue.md

# Get issue in JSON format
jira issue --get PROJ-123 --format json
# Export with explicit markdown format
jira issue view PROJ-123 --format markdown --output ./issue.md
```

### List Issues
Expand Down Expand Up @@ -247,7 +251,7 @@ jira sprint list --board 123 --state active
| `config --server <url> --token <token>` | Configure CLI (Bearer auth) | Username optional; use `--username <email>` for Basic auth |
| `config --show` | Show current configuration | - |
| `config set <key> <value>` | Set individual config value | - |
| `issue get <key>` | Get issue details | `--format <json\|table>`, `--verbose` |
| `issue view <key>` | View issue details (alias: show) | `--format <terminal\|markdown>`, `--output <path>` |
| `issue list` | List issues | `--project <key>`, `--assignee <user>`, `--status <status>`, `--jql <query>`, `--limit <number>` |
| `issue create` | Create new issue | **Required:** `--project <key>`, `--type <type>`, `--summary <text>`<br>**Optional:** `--description <text>`, `--description-file <path>`, `--assignee <user>`, `--priority <level>` |
| `issue edit <key>` | Edit an existing issue (alias: update) | **At least one required:**<br>`--summary <text>`, `--description <text>`, `--description-file <path>`, `--assignee <user>`, `--priority <level>` |
Expand Down Expand Up @@ -280,14 +284,14 @@ jira config --server https://jira.company.com \
--username user@company.com \
--token your-api-token

# Read an issue
# View an issue in terminal
jira issue view PROJ-123

# Read an issue with full details
jira issue view PROJ-123 --verbose
# View as markdown in terminal
jira issue view PROJ-123 --format markdown

# Get issue in JSON format
jira issue view PROJ-123 --format json
# Export to markdown file
jira issue view PROJ-123 --output ./issue.md

# List issues with filters
jira issue list --project PROJ --status "In Progress" --limit 10
Expand Down Expand Up @@ -467,9 +471,10 @@ This project is licensed under the ISC License - see the [LICENSE](https://githu
- [x] Configuration management
- [x] Non-interactive, automation-friendly CLI
- [x] Analytics and reporting
- [x] Export issues to markdown format
- [x] Create/update issues from markdown files
- [ ] Issue templates
- [ ] Bulk operations
- [ ] Export issues to different formats
- [ ] Integration with other Atlassian tools
- [ ] Issue attachments management
- [ ] Comments and workflows
Expand Down
35 changes: 27 additions & 8 deletions bin/commands/issue.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { Command } = require('commander');
const {
createIssuesTable,
displayIssueDetails,
formatIssueAsMarkdown,
buildJQL
} = require('../../lib/utils');
const chalk = require('chalk');
Expand Down Expand Up @@ -47,14 +48,21 @@ function createIssueCommand(factory) {

command
.command('view <key>')
.description('view issue details')
.description('view issue details\n\n' +
'Examples:\n' +
' $ jira issue view PROJ-123 # View in terminal\n' +
' $ jira issue view PROJ-123 --format markdown # View as markdown\n' +
' $ jira issue view PROJ-123 --output ./issue.md # Save to file\n' +
' $ jira issue view PROJ-123 --format markdown --output ./issue.md')
.alias('show')
.action(async (key) => {
.option('--format <format>', 'output format (terminal, markdown)', 'terminal')
.option('--output <path>', 'save to file instead of displaying')
.action(async (key, options) => {
const io = factory.getIOStreams();
const client = await factory.getJiraClient();

try {
await getIssue(client, io, key);
await getIssue(client, io, key, options);
} catch (err) {
io.error(`Failed to get issue: ${err.message}`);
process.exit(1);
Expand Down Expand Up @@ -188,14 +196,25 @@ async function listIssues(client, io, options) {
}
}

async function getIssue(client, io, issueKey) {
async function getIssue(client, io, issueKey, options = {}) {
const spinner = io.spinner(`Fetching issue ${issueKey}...`);

try {
const issue = await client.getIssue(issueKey);
spinner.stop();

displayIssueDetails(issue);

if (options.output) {
const outputPath = path.resolve(options.output);
const content = formatIssueAsMarkdown(issue);

fs.writeFileSync(outputPath, content, 'utf8');
io.success(`Issue ${issueKey} saved to ${outputPath}`);
} else if (options.format === 'markdown') {
const markdown = formatIssueAsMarkdown(issue);
io.out('\n' + markdown);
} else {
displayIssueDetails(issue);
}

} catch (err) {
spinner.stop();
Expand Down
44 changes: 40 additions & 4 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,28 +107,63 @@ function createSprintsTable(sprints) {
return table;
}

// Format issue as markdown
function formatIssueAsMarkdown(issue) {
const lines = [];

lines.push(`# ${issue.key}: ${issue.fields.summary}`);
lines.push('');

lines.push('## Metadata');
lines.push('');
lines.push(`- **Status**: ${issue.fields.status.name}`);
lines.push(`- **Type**: ${issue.fields.issuetype.name}`);
lines.push(`- **Priority**: ${issue.fields.priority ? issue.fields.priority.name : 'N/A'}`);
lines.push(`- **Assignee**: ${issue.fields.assignee ? issue.fields.assignee.displayName : 'Unassigned'}`);
lines.push(`- **Reporter**: ${issue.fields.reporter ? issue.fields.reporter.displayName : 'N/A'}`);
lines.push(`- **Created**: ${formatDate(issue.fields.created)}`);
lines.push(`- **Updated**: ${formatDate(issue.fields.updated)}`);

if (issue.fields.labels && issue.fields.labels.length > 0) {
lines.push(`- **Labels**: ${issue.fields.labels.join(', ')}`);
}

const url = issue.self.replace(`/rest/api/2/issue/${issue.id}`, `/browse/${issue.key}`);
lines.push(`- **URL**: ${url}`);
lines.push('');

if (issue.fields.description) {
lines.push('## Description');
lines.push('');
lines.push(issue.fields.description);
lines.push('');
}

return lines.join('\n');
}

// Display issue details
function displayIssueDetails(issue) {
console.log(chalk.bold(`\n${issue.key}: ${issue.fields.summary}`));
console.log(chalk.gray('─'.repeat(60)));

console.log(`${chalk.bold('Status:')} ${chalk.yellow(issue.fields.status.name)}`);
console.log(`${chalk.bold('Type:')} ${issue.fields.issuetype.name}`);
console.log(`${chalk.bold('Priority:')} ${issue.fields.priority ? issue.fields.priority.name : 'N/A'}`);
console.log(`${chalk.bold('Assignee:')} ${issue.fields.assignee ? issue.fields.assignee.displayName : 'Unassigned'}`);
console.log(`${chalk.bold('Reporter:')} ${issue.fields.reporter ? issue.fields.reporter.displayName : 'N/A'}`);
console.log(`${chalk.bold('Created:')} ${formatDate(issue.fields.created)}`);
console.log(`${chalk.bold('Updated:')} ${formatDate(issue.fields.updated)}`);

if (issue.fields.description) {
console.log(`\n${chalk.bold('Description:')}`);
console.log(issue.fields.description);
}

if (issue.fields.labels && issue.fields.labels.length > 0) {
console.log(`\n${chalk.bold('Labels:')} ${issue.fields.labels.join(', ')}`);
}

console.log(`\n${chalk.bold('URL:')} ${issue.self.replace('/rest/api/2/issue/' + issue.id, '/browse/' + issue.key)}`);
}

Expand Down Expand Up @@ -181,6 +216,7 @@ module.exports = {
createProjectsTable,
createSprintsTable,
displayIssueDetails,
formatIssueAsMarkdown,
buildJQL,
success,
error,
Expand Down