jira-ai 0.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 +364 -0
- package/dist/cli.js +87 -0
- package/dist/commands/about.js +83 -0
- package/dist/commands/add-comment.js +109 -0
- package/dist/commands/me.js +23 -0
- package/dist/commands/project-statuses.js +23 -0
- package/dist/commands/projects.js +35 -0
- package/dist/commands/run-jql.js +44 -0
- package/dist/commands/task-with-details.js +23 -0
- package/dist/commands/update-description.js +109 -0
- package/dist/lib/formatters.js +193 -0
- package/dist/lib/jira-client.js +216 -0
- package/dist/lib/settings.js +68 -0
- package/dist/lib/utils.js +79 -0
- package/jest.config.js +21 -0
- package/package.json +47 -0
- package/settings.yaml +24 -0
- package/src/cli.ts +97 -0
- package/src/commands/about.ts +98 -0
- package/src/commands/add-comment.ts +94 -0
- package/src/commands/me.ts +18 -0
- package/src/commands/project-statuses.ts +18 -0
- package/src/commands/projects.ts +32 -0
- package/src/commands/run-jql.ts +40 -0
- package/src/commands/task-with-details.ts +18 -0
- package/src/commands/update-description.ts +94 -0
- package/src/lib/formatters.ts +224 -0
- package/src/lib/jira-client.ts +319 -0
- package/src/lib/settings.ts +77 -0
- package/src/lib/utils.ts +76 -0
- package/src/types/md-to-adf.d.ts +14 -0
- package/tests/README.md +97 -0
- package/tests/__mocks__/jira.js.ts +4 -0
- package/tests/__mocks__/md-to-adf.ts +7 -0
- package/tests/__mocks__/mdast-util-from-adf.ts +4 -0
- package/tests/__mocks__/mdast-util-to-markdown.ts +1 -0
- package/tests/add-comment.test.ts +226 -0
- package/tests/cli-permissions.test.ts +156 -0
- package/tests/jira-client.test.ts +123 -0
- package/tests/projects.test.ts +205 -0
- package/tests/settings.test.ts +288 -0
- package/tests/task-with-details.test.ts +83 -0
- package/tests/update-description.test.ts +262 -0
- package/to-do.md +9 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.loadSettings = loadSettings;
|
|
7
|
+
exports.isProjectAllowed = isProjectAllowed;
|
|
8
|
+
exports.isCommandAllowed = isCommandAllowed;
|
|
9
|
+
exports.getAllowedProjects = getAllowedProjects;
|
|
10
|
+
exports.getAllowedCommands = getAllowedCommands;
|
|
11
|
+
exports.__resetCache__ = __resetCache__;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const js_yaml_1 = __importDefault(require("js-yaml"));
|
|
15
|
+
let cachedSettings = null;
|
|
16
|
+
function loadSettings() {
|
|
17
|
+
if (cachedSettings) {
|
|
18
|
+
return cachedSettings;
|
|
19
|
+
}
|
|
20
|
+
const settingsPath = path_1.default.join(process.cwd(), 'settings.yaml');
|
|
21
|
+
if (!fs_1.default.existsSync(settingsPath)) {
|
|
22
|
+
console.warn('Warning: settings.yaml not found. Using default settings (all allowed).');
|
|
23
|
+
cachedSettings = {
|
|
24
|
+
projects: ['all'],
|
|
25
|
+
commands: ['all']
|
|
26
|
+
};
|
|
27
|
+
return cachedSettings;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const fileContents = fs_1.default.readFileSync(settingsPath, 'utf8');
|
|
31
|
+
const settings = js_yaml_1.default.load(fileContents);
|
|
32
|
+
cachedSettings = {
|
|
33
|
+
projects: settings.projects || ['all'],
|
|
34
|
+
commands: settings.commands || ['all']
|
|
35
|
+
};
|
|
36
|
+
return cachedSettings;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.error('Error loading settings.yaml:', error);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function isProjectAllowed(projectKey) {
|
|
44
|
+
const settings = loadSettings();
|
|
45
|
+
if (settings.projects.includes('all')) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
return settings.projects.includes(projectKey);
|
|
49
|
+
}
|
|
50
|
+
function isCommandAllowed(commandName) {
|
|
51
|
+
const settings = loadSettings();
|
|
52
|
+
if (settings.commands.includes('all')) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
return settings.commands.includes(commandName);
|
|
56
|
+
}
|
|
57
|
+
function getAllowedProjects() {
|
|
58
|
+
const settings = loadSettings();
|
|
59
|
+
return settings.projects;
|
|
60
|
+
}
|
|
61
|
+
function getAllowedCommands() {
|
|
62
|
+
const settings = loadSettings();
|
|
63
|
+
return settings.commands;
|
|
64
|
+
}
|
|
65
|
+
// For testing purposes only
|
|
66
|
+
function __resetCache__() {
|
|
67
|
+
cachedSettings = null;
|
|
68
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.validateEnvVars = validateEnvVars;
|
|
7
|
+
exports.formatTimestamp = formatTimestamp;
|
|
8
|
+
exports.truncate = truncate;
|
|
9
|
+
exports.convertADFToMarkdown = convertADFToMarkdown;
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const mdast_util_from_adf_1 = require("mdast-util-from-adf");
|
|
12
|
+
const mdast_util_to_markdown_1 = require("mdast-util-to-markdown");
|
|
13
|
+
/**
|
|
14
|
+
* Validate required environment variables
|
|
15
|
+
*/
|
|
16
|
+
function validateEnvVars() {
|
|
17
|
+
const required = [
|
|
18
|
+
'JIRA_HOST',
|
|
19
|
+
'JIRA_USER_EMAIL',
|
|
20
|
+
'JIRA_API_TOKEN',
|
|
21
|
+
];
|
|
22
|
+
const missing = required.filter((key) => !process.env[key]);
|
|
23
|
+
if (missing.length > 0) {
|
|
24
|
+
console.error(chalk_1.default.red('✗ Missing required environment variables:\n'));
|
|
25
|
+
missing.forEach((key) => console.error(chalk_1.default.red(` - ${key}`)));
|
|
26
|
+
console.log('\nPlease create a .env file with the following variables:');
|
|
27
|
+
console.log(' JIRA_HOST=https://your-domain.atlassian.net');
|
|
28
|
+
console.log(' JIRA_USER_EMAIL=your-email@example.com');
|
|
29
|
+
console.log(' JIRA_API_TOKEN=your-api-token');
|
|
30
|
+
console.log('\nGet your API token from: https://id.atlassian.com/manage-profile/security/api-tokens');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Format timestamp for display
|
|
36
|
+
*/
|
|
37
|
+
function formatTimestamp(date) {
|
|
38
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
39
|
+
return d.toLocaleString('en-US', {
|
|
40
|
+
year: 'numeric',
|
|
41
|
+
month: 'short',
|
|
42
|
+
day: 'numeric',
|
|
43
|
+
hour: '2-digit',
|
|
44
|
+
minute: '2-digit',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Truncate text to max length with ellipsis
|
|
49
|
+
*/
|
|
50
|
+
function truncate(text, maxLength) {
|
|
51
|
+
if (text.length <= maxLength)
|
|
52
|
+
return text;
|
|
53
|
+
return text.substring(0, maxLength - 3) + '...';
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Convert Atlassian Document Format (ADF) to Markdown
|
|
57
|
+
* Handles both string content and ADF JSON objects
|
|
58
|
+
*/
|
|
59
|
+
function convertADFToMarkdown(content) {
|
|
60
|
+
// If content is already a string, return it as-is
|
|
61
|
+
if (typeof content === 'string') {
|
|
62
|
+
return content;
|
|
63
|
+
}
|
|
64
|
+
// If content is null or undefined, return empty string
|
|
65
|
+
if (!content) {
|
|
66
|
+
return '';
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
// Convert ADF to mdast, then mdast to markdown
|
|
70
|
+
const mdastTree = (0, mdast_util_from_adf_1.fromADF)(content);
|
|
71
|
+
const markdown = (0, mdast_util_to_markdown_1.toMarkdown)(mdastTree);
|
|
72
|
+
return markdown.trim();
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
// If conversion fails, fall back to JSON string representation
|
|
76
|
+
console.error('Failed to convert ADF to Markdown:', error);
|
|
77
|
+
return JSON.stringify(content, null, 2);
|
|
78
|
+
}
|
|
79
|
+
}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: 'ts-jest',
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
roots: ['<rootDir>/src', '<rootDir>/tests'],
|
|
5
|
+
testMatch: ['**/__tests__/**/*.ts', '**/*.test.ts', '**/*.spec.ts'],
|
|
6
|
+
collectCoverageFrom: [
|
|
7
|
+
'src/**/*.ts',
|
|
8
|
+
'!src/**/*.d.ts',
|
|
9
|
+
'!src/cli.ts'
|
|
10
|
+
],
|
|
11
|
+
coverageDirectory: 'coverage',
|
|
12
|
+
coverageReporters: ['text', 'lcov', 'html'],
|
|
13
|
+
moduleFileExtensions: ['ts', 'js', 'json'],
|
|
14
|
+
verbose: true,
|
|
15
|
+
moduleNameMapper: {
|
|
16
|
+
'^jira\\.js$': '<rootDir>/tests/__mocks__/jira.js.ts',
|
|
17
|
+
'^mdast-util-from-adf$': '<rootDir>/tests/__mocks__/mdast-util-from-adf.ts',
|
|
18
|
+
'^mdast-util-to-markdown$': '<rootDir>/tests/__mocks__/mdast-util-to-markdown.ts',
|
|
19
|
+
'^md-to-adf$': '<rootDir>/tests/__mocks__/md-to-adf.ts'
|
|
20
|
+
}
|
|
21
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "jira-ai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI tool for interacting with Atlassian Jira",
|
|
5
|
+
"main": "dist/cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"jira-ai": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "ts-node src/cli.ts",
|
|
11
|
+
"build": "tsc && chmod +x dist/cli.js",
|
|
12
|
+
"start": "node dist/cli.js",
|
|
13
|
+
"link": "npm run build && npm link",
|
|
14
|
+
"test": "jest",
|
|
15
|
+
"test:watch": "jest --watch",
|
|
16
|
+
"test:coverage": "jest --coverage",
|
|
17
|
+
"test:verbose": "jest --verbose"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"jira",
|
|
21
|
+
"cli",
|
|
22
|
+
"atlassian"
|
|
23
|
+
],
|
|
24
|
+
"author": "",
|
|
25
|
+
"license": "ISC",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"chalk": "^4.1.2",
|
|
28
|
+
"cli-table3": "^0.6.5",
|
|
29
|
+
"commander": "^11.0.0",
|
|
30
|
+
"dotenv": "^17.2.3",
|
|
31
|
+
"jira.js": "^5.2.2",
|
|
32
|
+
"js-yaml": "^4.1.1",
|
|
33
|
+
"md-to-adf": "^0.6.4",
|
|
34
|
+
"mdast-util-from-adf": "^2.2.0",
|
|
35
|
+
"mdast-util-to-markdown": "^2.1.2",
|
|
36
|
+
"ora": "^5.4.1"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/jest": "^30.0.0",
|
|
40
|
+
"@types/js-yaml": "^4.0.9",
|
|
41
|
+
"@types/node": "^25.0.3",
|
|
42
|
+
"jest": "^30.2.0",
|
|
43
|
+
"ts-jest": "^29.4.6",
|
|
44
|
+
"ts-node": "^10.9.2",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/settings.yaml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Jira AI Settings
|
|
2
|
+
|
|
3
|
+
# Projects: List of allowed projects (use "all" to allow all projects)
|
|
4
|
+
# Examples: BP, PM, PS
|
|
5
|
+
projects:
|
|
6
|
+
- all
|
|
7
|
+
#- BP
|
|
8
|
+
#- PM
|
|
9
|
+
#- PS
|
|
10
|
+
# Uncomment below to allow all projects
|
|
11
|
+
# - all
|
|
12
|
+
|
|
13
|
+
# Commands: List of allowed commands (use "all" to allow all commands)
|
|
14
|
+
# Available commands: me, projects, task-with-details, project-statuses, run-jql, update-description, about
|
|
15
|
+
commands:
|
|
16
|
+
- me
|
|
17
|
+
- projects
|
|
18
|
+
- run-jql
|
|
19
|
+
# Uncomment below to allow all commands
|
|
20
|
+
# - all
|
|
21
|
+
- task-with-details
|
|
22
|
+
# - project-statuses
|
|
23
|
+
- update-description
|
|
24
|
+
- add-comment
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import { validateEnvVars } from './lib/utils';
|
|
7
|
+
import { meCommand } from './commands/me';
|
|
8
|
+
import { projectsCommand } from './commands/projects';
|
|
9
|
+
import { taskWithDetailsCommand } from './commands/task-with-details';
|
|
10
|
+
import { projectStatusesCommand } from './commands/project-statuses';
|
|
11
|
+
import { runJqlCommand } from './commands/run-jql';
|
|
12
|
+
import { updateDescriptionCommand } from './commands/update-description';
|
|
13
|
+
import { addCommentCommand } from './commands/add-comment';
|
|
14
|
+
import { aboutCommand } from './commands/about';
|
|
15
|
+
import { isCommandAllowed, getAllowedCommands } from './lib/settings';
|
|
16
|
+
|
|
17
|
+
// Load environment variables
|
|
18
|
+
dotenv.config();
|
|
19
|
+
|
|
20
|
+
// Validate environment variables
|
|
21
|
+
validateEnvVars();
|
|
22
|
+
|
|
23
|
+
// Helper function to wrap commands with permission check
|
|
24
|
+
function withPermission(commandName: string, commandFn: (...args: any[]) => Promise<void>) {
|
|
25
|
+
return async (...args: any[]) => {
|
|
26
|
+
if (!isCommandAllowed(commandName)) {
|
|
27
|
+
console.error(chalk.red(`\n❌ Command '${commandName}' is not allowed.`));
|
|
28
|
+
console.log(chalk.gray('Allowed commands: ' + getAllowedCommands().join(', ')));
|
|
29
|
+
console.log(chalk.gray('Update settings.yaml to enable this command.\n'));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
return commandFn(...args);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Create CLI program
|
|
37
|
+
const program = new Command();
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.name('jira-ai')
|
|
41
|
+
.description('CLI tool for interacting with Atlassian Jira')
|
|
42
|
+
.version('1.0.0');
|
|
43
|
+
|
|
44
|
+
// Me command
|
|
45
|
+
program
|
|
46
|
+
.command('me')
|
|
47
|
+
.description('Show basic user information')
|
|
48
|
+
.action(withPermission('me', meCommand));
|
|
49
|
+
|
|
50
|
+
// Projects command
|
|
51
|
+
program
|
|
52
|
+
.command('projects')
|
|
53
|
+
.description('Show list of projects')
|
|
54
|
+
.action(withPermission('projects', projectsCommand));
|
|
55
|
+
|
|
56
|
+
// Task with details command
|
|
57
|
+
program
|
|
58
|
+
.command('task-with-details <task-id>')
|
|
59
|
+
.description('Show task title, body, and comments')
|
|
60
|
+
.action(withPermission('task-with-details', taskWithDetailsCommand));
|
|
61
|
+
|
|
62
|
+
// Project statuses command
|
|
63
|
+
program
|
|
64
|
+
.command('project-statuses <project-id>')
|
|
65
|
+
.description('Show all possible statuses for a project')
|
|
66
|
+
.action(withPermission('project-statuses', projectStatusesCommand));
|
|
67
|
+
|
|
68
|
+
// Run JQL command
|
|
69
|
+
program
|
|
70
|
+
.command('run-jql <jql-query>')
|
|
71
|
+
.description('Execute JQL query and display results')
|
|
72
|
+
.option('-l, --limit <number>', 'Maximum number of results (default: 50)', '50')
|
|
73
|
+
.action(withPermission('run-jql', runJqlCommand));
|
|
74
|
+
|
|
75
|
+
// Update description command
|
|
76
|
+
program
|
|
77
|
+
.command('update-description <task-id>')
|
|
78
|
+
.description('Update task description from a Markdown file')
|
|
79
|
+
.requiredOption('--from-file <path>', 'Path to Markdown file')
|
|
80
|
+
.action(withPermission('update-description', updateDescriptionCommand));
|
|
81
|
+
|
|
82
|
+
// Add comment command
|
|
83
|
+
program
|
|
84
|
+
.command('add-comment')
|
|
85
|
+
.description('Add a comment to a Jira issue from a Markdown file')
|
|
86
|
+
.requiredOption('--file-path <path>', 'Path to Markdown file')
|
|
87
|
+
.requiredOption('--issue-key <key>', 'Jira issue key (e.g., PS-123)')
|
|
88
|
+
.action(withPermission('add-comment', addCommentCommand));
|
|
89
|
+
|
|
90
|
+
// About command (always allowed)
|
|
91
|
+
program
|
|
92
|
+
.command('about')
|
|
93
|
+
.description('Show information about available commands')
|
|
94
|
+
.action(aboutCommand);
|
|
95
|
+
|
|
96
|
+
// Parse command line arguments
|
|
97
|
+
program.parse();
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getAllowedCommands, getAllowedProjects, isCommandAllowed } from '../lib/settings';
|
|
3
|
+
|
|
4
|
+
interface CommandInfo {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
usage: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const ALL_COMMANDS: CommandInfo[] = [
|
|
11
|
+
{
|
|
12
|
+
name: 'me',
|
|
13
|
+
description: 'Show details of current user',
|
|
14
|
+
usage: 'jira-ai me --help'
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: 'projects',
|
|
18
|
+
description: 'List available projects',
|
|
19
|
+
usage: 'jira-ai projects --help'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'task-with-details',
|
|
23
|
+
description: 'Show task title, body, and comments',
|
|
24
|
+
usage: 'jira-ai task-with-details --help'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'project-statuses',
|
|
28
|
+
description: 'Show all possible statuses for a project',
|
|
29
|
+
usage: 'jira-ai project-statuses --help'
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'run-jql',
|
|
33
|
+
description: 'Execute JQL query and display results',
|
|
34
|
+
usage: 'jira-ai run-jql "<jql-query>" [-l <limit>]'
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'update-description',
|
|
38
|
+
description: 'Update task description from a Markdown file',
|
|
39
|
+
usage: 'jira-ai update-description <task-id> --from-file <path>'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'add-comment',
|
|
43
|
+
description: 'Add a comment to a Jira issue from a Markdown file',
|
|
44
|
+
usage: 'jira-ai add-comment --file-path <path> --issue-key <key>'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'about',
|
|
48
|
+
description: 'Show this help message',
|
|
49
|
+
usage: 'jira-ai about'
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
export async function aboutCommand() {
|
|
54
|
+
console.log(chalk.bold.cyan('\n📋 Jira AI - Available Commands\n'));
|
|
55
|
+
|
|
56
|
+
console.log(chalk.bold('Usage:'));
|
|
57
|
+
console.log(' jira-ai <command> [options]\n');
|
|
58
|
+
|
|
59
|
+
const allowedCommandsList = getAllowedCommands();
|
|
60
|
+
const isAllAllowed = allowedCommandsList.includes('all');
|
|
61
|
+
|
|
62
|
+
// Filter commands based on settings (about is always shown)
|
|
63
|
+
const commandsToShow = ALL_COMMANDS.filter(cmd =>
|
|
64
|
+
cmd.name === 'about' || isAllAllowed || isCommandAllowed(cmd.name)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
console.log(chalk.bold('Available Commands:\n'));
|
|
68
|
+
|
|
69
|
+
for (const cmd of commandsToShow) {
|
|
70
|
+
console.log(chalk.yellow(` ${cmd.name}`));
|
|
71
|
+
console.log(` ${cmd.description}`);
|
|
72
|
+
console.log(` Usage: ${cmd.usage}\n`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Show disabled commands if not all are allowed
|
|
76
|
+
if (!isAllAllowed) {
|
|
77
|
+
const disabledCommands = ALL_COMMANDS.filter(cmd =>
|
|
78
|
+
cmd.name !== 'about' && !isCommandAllowed(cmd.name)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (disabledCommands.length > 0) {
|
|
82
|
+
console.log(chalk.bold('Disabled Commands:\n'));
|
|
83
|
+
for (const cmd of disabledCommands) {
|
|
84
|
+
console.log(chalk.gray(` ${cmd.name} - ${cmd.description}`));
|
|
85
|
+
}
|
|
86
|
+
console.log();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(chalk.bold('For detailed help on any command, run:'));
|
|
91
|
+
console.log(chalk.green(' jira-ai <command> --help\n'));
|
|
92
|
+
|
|
93
|
+
console.log(chalk.bold('Configuration:'));
|
|
94
|
+
console.log(' Settings are managed in settings.yaml');
|
|
95
|
+
const allowedProjects = getAllowedProjects();
|
|
96
|
+
console.log(` - Projects: ${allowedProjects.includes('all') ? 'All allowed' : allowedProjects.join(', ')}`);
|
|
97
|
+
console.log(` - Commands: ${isAllAllowed ? 'All allowed' : allowedCommandsList.join(', ')}\n`);
|
|
98
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import mdToAdf from 'md-to-adf';
|
|
6
|
+
import { addIssueComment } from '../lib/jira-client';
|
|
7
|
+
|
|
8
|
+
export async function addCommentCommand(
|
|
9
|
+
options: { filePath: string; issueKey: string }
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
const { filePath, issueKey } = options;
|
|
12
|
+
|
|
13
|
+
// Validate issueKey
|
|
14
|
+
if (!issueKey || issueKey.trim() === '') {
|
|
15
|
+
console.error(chalk.red('\nError: Issue key is required (use --issue-key)'));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Validate file path
|
|
20
|
+
if (!filePath || filePath.trim() === '') {
|
|
21
|
+
console.error(chalk.red('\nError: File path is required (use --file-path)'));
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Resolve file path to absolute
|
|
26
|
+
const absolutePath = path.resolve(filePath);
|
|
27
|
+
|
|
28
|
+
// Check file exists
|
|
29
|
+
if (!fs.existsSync(absolutePath)) {
|
|
30
|
+
console.error(chalk.red(`\nError: File not found: ${absolutePath}`));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Read file
|
|
35
|
+
let markdownContent: string;
|
|
36
|
+
try {
|
|
37
|
+
markdownContent = fs.readFileSync(absolutePath, 'utf-8');
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.error(
|
|
40
|
+
chalk.red(
|
|
41
|
+
'\nError reading file: ' +
|
|
42
|
+
(error instanceof Error ? error.message : 'Unknown error')
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate file is not empty
|
|
49
|
+
if (markdownContent.trim() === '') {
|
|
50
|
+
console.error(chalk.red('\nError: File is empty'));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Convert Markdown to ADF
|
|
55
|
+
let adfContent: any;
|
|
56
|
+
try {
|
|
57
|
+
adfContent = mdToAdf(markdownContent);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error(
|
|
60
|
+
chalk.red(
|
|
61
|
+
'\nError converting Markdown to ADF: ' +
|
|
62
|
+
(error instanceof Error ? error.message : 'Unknown error')
|
|
63
|
+
)
|
|
64
|
+
);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Add comment with spinner
|
|
69
|
+
const spinner = ora(`Adding comment to ${issueKey}...`).start();
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await addIssueComment(issueKey, adfContent);
|
|
73
|
+
spinner.succeed(chalk.green(`Comment added successfully to ${issueKey}`));
|
|
74
|
+
console.log(chalk.gray(`\nFile: ${absolutePath}`));
|
|
75
|
+
} catch (error) {
|
|
76
|
+
spinner.fail(chalk.red('Failed to add comment'));
|
|
77
|
+
console.error(
|
|
78
|
+
chalk.red(
|
|
79
|
+
'\nError: ' + (error instanceof Error ? error.message : 'Unknown error')
|
|
80
|
+
)
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Provide helpful hints based on error
|
|
84
|
+
if (error instanceof Error && error.message.includes('404')) {
|
|
85
|
+
console.log(chalk.yellow('\nHint: Check that the issue key is correct'));
|
|
86
|
+
} else if (error instanceof Error && error.message.includes('403')) {
|
|
87
|
+
console.log(
|
|
88
|
+
chalk.yellow('\nHint: You may not have permission to comment on this issue')
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getCurrentUser } from '../lib/jira-client';
|
|
4
|
+
import { formatUserInfo } from '../lib/formatters';
|
|
5
|
+
|
|
6
|
+
export async function meCommand(): Promise<void> {
|
|
7
|
+
const spinner = ora('Fetching user information...').start();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const user = await getCurrentUser();
|
|
11
|
+
spinner.succeed(chalk.green('User information retrieved'));
|
|
12
|
+
console.log(formatUserInfo(user));
|
|
13
|
+
} catch (error) {
|
|
14
|
+
spinner.fail(chalk.red('Failed to fetch user information'));
|
|
15
|
+
console.error(chalk.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getProjectStatuses } from '../lib/jira-client';
|
|
4
|
+
import { formatProjectStatuses } from '../lib/formatters';
|
|
5
|
+
|
|
6
|
+
export async function projectStatusesCommand(projectId: string): Promise<void> {
|
|
7
|
+
const spinner = ora(`Fetching statuses for project ${projectId}...`).start();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const statuses = await getProjectStatuses(projectId);
|
|
11
|
+
spinner.succeed(chalk.green('Project statuses retrieved'));
|
|
12
|
+
console.log(formatProjectStatuses(projectId, statuses));
|
|
13
|
+
} catch (error) {
|
|
14
|
+
spinner.fail(chalk.red('Failed to fetch project statuses'));
|
|
15
|
+
console.error(chalk.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getProjects } from '../lib/jira-client';
|
|
4
|
+
import { formatProjects } from '../lib/formatters';
|
|
5
|
+
import { getAllowedProjects, isProjectAllowed } from '../lib/settings';
|
|
6
|
+
|
|
7
|
+
export async function projectsCommand(): Promise<void> {
|
|
8
|
+
const spinner = ora('Fetching projects...').start();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const allProjects = await getProjects();
|
|
12
|
+
const allowedProjectKeys = getAllowedProjects();
|
|
13
|
+
|
|
14
|
+
// Filter projects based on settings
|
|
15
|
+
const filteredProjects = allowedProjectKeys.includes('all')
|
|
16
|
+
? allProjects
|
|
17
|
+
: allProjects.filter(project => isProjectAllowed(project.key));
|
|
18
|
+
|
|
19
|
+
spinner.succeed(chalk.green('Projects retrieved'));
|
|
20
|
+
|
|
21
|
+
if (filteredProjects.length === 0) {
|
|
22
|
+
console.log(chalk.yellow('\nNo projects match your settings configuration.'));
|
|
23
|
+
console.log(chalk.gray('Allowed projects: ' + allowedProjectKeys.join(', ')));
|
|
24
|
+
} else {
|
|
25
|
+
console.log(formatProjects(filteredProjects));
|
|
26
|
+
}
|
|
27
|
+
} catch (error) {
|
|
28
|
+
spinner.fail(chalk.red('Failed to fetch projects'));
|
|
29
|
+
console.error(chalk.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { searchIssuesByJql } from '../lib/jira-client';
|
|
4
|
+
import { formatJqlResults } from '../lib/formatters';
|
|
5
|
+
|
|
6
|
+
export async function runJqlCommand(jqlQuery: string, options: { limit: string }): Promise<void> {
|
|
7
|
+
// Validate JQL query is not empty
|
|
8
|
+
if (!jqlQuery || jqlQuery.trim() === '') {
|
|
9
|
+
console.error(chalk.red('\nError: JQL query cannot be empty'));
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Parse and validate limit parameter
|
|
14
|
+
let maxResults = 50; // default
|
|
15
|
+
if (options.limit) {
|
|
16
|
+
const parsedLimit = parseInt(options.limit, 10);
|
|
17
|
+
if (isNaN(parsedLimit) || parsedLimit < 1) {
|
|
18
|
+
console.error(chalk.red('\nError: Limit must be a positive number'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
if (parsedLimit > 1000) {
|
|
22
|
+
console.warn(chalk.yellow('\nWarning: Limit is very high. Using 1000 as maximum.'));
|
|
23
|
+
maxResults = 1000;
|
|
24
|
+
} else {
|
|
25
|
+
maxResults = parsedLimit;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const spinner = ora('Executing JQL query...').start();
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const issues = await searchIssuesByJql(jqlQuery, maxResults);
|
|
33
|
+
spinner.succeed(chalk.green('Query executed successfully'));
|
|
34
|
+
console.log(formatJqlResults(issues));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
spinner.fail(chalk.red('Failed to execute JQL query'));
|
|
37
|
+
console.error(chalk.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { getTaskWithDetails } from '../lib/jira-client';
|
|
4
|
+
import { formatTaskDetails } from '../lib/formatters';
|
|
5
|
+
|
|
6
|
+
export async function taskWithDetailsCommand(taskId: string): Promise<void> {
|
|
7
|
+
const spinner = ora(`Fetching details for ${taskId}...`).start();
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const task = await getTaskWithDetails(taskId);
|
|
11
|
+
spinner.succeed(chalk.green('Task details retrieved'));
|
|
12
|
+
console.log(formatTaskDetails(task));
|
|
13
|
+
} catch (error) {
|
|
14
|
+
spinner.fail(chalk.red('Failed to fetch task details'));
|
|
15
|
+
console.error(chalk.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
}
|