jira-ai 0.3.7 → 0.3.9
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/dist/cli.js +46 -39
- package/dist/commands/about.js +17 -23
- package/dist/commands/add-comment.js +22 -61
- package/dist/commands/add-label.js +34 -0
- package/dist/commands/auth.js +32 -38
- package/dist/commands/create-task.js +24 -30
- package/dist/commands/delete-label.js +34 -0
- package/dist/commands/list-issue-types.js +11 -17
- package/dist/commands/me.js +11 -17
- package/dist/commands/project-statuses.js +11 -17
- package/dist/commands/projects.js +16 -22
- package/dist/commands/run-jql.js +14 -20
- package/dist/commands/task-with-details.js +11 -17
- package/dist/commands/update-description.js +22 -61
- package/dist/lib/auth-storage.js +17 -26
- package/dist/lib/formatters.js +71 -82
- package/dist/lib/jira-client.js +52 -33
- package/dist/lib/settings.js +26 -38
- package/dist/lib/utils.js +13 -22
- package/package.json +17 -16
package/dist/cli.js
CHANGED
|
@@ -1,36 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
};
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const about_1 = require("./commands/about");
|
|
21
|
-
const auth_1 = require("./commands/auth");
|
|
22
|
-
const settings_1 = require("./lib/settings");
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import dotenv from 'dotenv';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { validateEnvVars } from './lib/utils.js';
|
|
6
|
+
import { meCommand } from './commands/me.js';
|
|
7
|
+
import { projectsCommand } from './commands/projects.js';
|
|
8
|
+
import { taskWithDetailsCommand } from './commands/task-with-details.js';
|
|
9
|
+
import { projectStatusesCommand } from './commands/project-statuses.js';
|
|
10
|
+
import { listIssueTypesCommand } from './commands/list-issue-types.js';
|
|
11
|
+
import { runJqlCommand } from './commands/run-jql.js';
|
|
12
|
+
import { updateDescriptionCommand } from './commands/update-description.js';
|
|
13
|
+
import { addCommentCommand } from './commands/add-comment.js';
|
|
14
|
+
import { addLabelCommand } from './commands/add-label.js';
|
|
15
|
+
import { deleteLabelCommand } from './commands/delete-label.js';
|
|
16
|
+
import { createTaskCommand } from './commands/create-task.js';
|
|
17
|
+
import { aboutCommand } from './commands/about.js';
|
|
18
|
+
import { authCommand } from './commands/auth.js';
|
|
19
|
+
import { isCommandAllowed, getAllowedCommands } from './lib/settings.js';
|
|
23
20
|
// Load environment variables
|
|
24
|
-
|
|
21
|
+
dotenv.config();
|
|
25
22
|
// Create CLI program
|
|
26
|
-
const program = new
|
|
23
|
+
const program = new Command();
|
|
27
24
|
program
|
|
28
25
|
.name('jira-ai')
|
|
29
26
|
.description('CLI tool for interacting with Atlassian Jira')
|
|
30
27
|
.version('1.0.0');
|
|
31
28
|
// Middleware to validate credentials for commands that need them
|
|
32
29
|
const validateCredentials = () => {
|
|
33
|
-
|
|
30
|
+
validateEnvVars();
|
|
34
31
|
};
|
|
35
32
|
// Helper function to wrap commands with permission check and credential validation
|
|
36
33
|
function withPermission(commandName, commandFn, skipValidation = false) {
|
|
@@ -38,10 +35,10 @@ function withPermission(commandName, commandFn, skipValidation = false) {
|
|
|
38
35
|
if (!skipValidation) {
|
|
39
36
|
validateCredentials();
|
|
40
37
|
}
|
|
41
|
-
if (!
|
|
42
|
-
console.error(
|
|
43
|
-
console.log(
|
|
44
|
-
console.log(
|
|
38
|
+
if (!isCommandAllowed(commandName)) {
|
|
39
|
+
console.error(chalk.red(`\n❌ Command '${commandName}' is not allowed.`));
|
|
40
|
+
console.log(chalk.gray('Allowed commands: ' + getAllowedCommands().join(', ')));
|
|
41
|
+
console.log(chalk.gray('Update settings.yaml to enable this command.\n'));
|
|
45
42
|
process.exit(1);
|
|
46
43
|
}
|
|
47
44
|
return commandFn(...args);
|
|
@@ -53,51 +50,61 @@ program
|
|
|
53
50
|
.description('Set up Jira authentication credentials')
|
|
54
51
|
.option('--from-json <json_string>', 'Accepts a raw JSON string with credentials')
|
|
55
52
|
.option('--from-file <path>', 'Accepts a path to a file (typically .env) with credentials')
|
|
56
|
-
.action((options) =>
|
|
53
|
+
.action((options) => authCommand(options));
|
|
57
54
|
// Me command
|
|
58
55
|
program
|
|
59
56
|
.command('me')
|
|
60
57
|
.description('Show basic user information')
|
|
61
|
-
.action(withPermission('me',
|
|
58
|
+
.action(withPermission('me', meCommand));
|
|
62
59
|
// Projects command
|
|
63
60
|
program
|
|
64
61
|
.command('projects')
|
|
65
62
|
.description('Show list of projects')
|
|
66
|
-
.action(withPermission('projects',
|
|
63
|
+
.action(withPermission('projects', projectsCommand));
|
|
67
64
|
// Task with details command
|
|
68
65
|
program
|
|
69
66
|
.command('task-with-details <task-id>')
|
|
70
67
|
.description('Show task title, body, and comments')
|
|
71
|
-
.action(withPermission('task-with-details',
|
|
68
|
+
.action(withPermission('task-with-details', taskWithDetailsCommand));
|
|
72
69
|
// Project statuses command
|
|
73
70
|
program
|
|
74
71
|
.command('project-statuses <project-id>')
|
|
75
72
|
.description('Show all possible statuses for a project')
|
|
76
|
-
.action(withPermission('project-statuses',
|
|
73
|
+
.action(withPermission('project-statuses', projectStatusesCommand));
|
|
77
74
|
// List issue types command
|
|
78
75
|
program
|
|
79
76
|
.command('list-issue-types <project-key>')
|
|
80
77
|
.description('Show all issue types for a project')
|
|
81
|
-
.action(withPermission('list-issue-types',
|
|
78
|
+
.action(withPermission('list-issue-types', listIssueTypesCommand));
|
|
82
79
|
// Run JQL command
|
|
83
80
|
program
|
|
84
81
|
.command('run-jql <jql-query>')
|
|
85
82
|
.description('Execute JQL query and display results')
|
|
86
83
|
.option('-l, --limit <number>', 'Maximum number of results (default: 50)', '50')
|
|
87
|
-
.action(withPermission('run-jql',
|
|
84
|
+
.action(withPermission('run-jql', runJqlCommand));
|
|
88
85
|
// Update description command
|
|
89
86
|
program
|
|
90
87
|
.command('update-description <task-id>')
|
|
91
88
|
.description('Update task description from a Markdown file')
|
|
92
89
|
.requiredOption('--from-file <path>', 'Path to Markdown file')
|
|
93
|
-
.action(withPermission('update-description',
|
|
90
|
+
.action(withPermission('update-description', updateDescriptionCommand));
|
|
94
91
|
// Add comment command
|
|
95
92
|
program
|
|
96
93
|
.command('add-comment')
|
|
97
94
|
.description('Add a comment to a Jira issue from a Markdown file')
|
|
98
95
|
.requiredOption('--file-path <path>', 'Path to Markdown file')
|
|
99
96
|
.requiredOption('--issue-key <key>', 'Jira issue key (e.g., PS-123)')
|
|
100
|
-
.action(withPermission('add-comment',
|
|
97
|
+
.action(withPermission('add-comment', addCommentCommand));
|
|
98
|
+
// Add label command
|
|
99
|
+
program
|
|
100
|
+
.command('add-label-to-issue <task-id> <labels>')
|
|
101
|
+
.description('Add one or more labels to a Jira issue (comma-separated)')
|
|
102
|
+
.action(withPermission('add-label-to-issue', addLabelCommand));
|
|
103
|
+
// Delete label command
|
|
104
|
+
program
|
|
105
|
+
.command('delete-label-from-issue <task-id> <labels>')
|
|
106
|
+
.description('Remove one or more labels from a Jira issue (comma-separated)')
|
|
107
|
+
.action(withPermission('delete-label-from-issue', deleteLabelCommand));
|
|
101
108
|
// Create task command
|
|
102
109
|
program
|
|
103
110
|
.command('create-task')
|
|
@@ -106,11 +113,11 @@ program
|
|
|
106
113
|
.requiredOption('--project <project>', 'Project key (e.g., PROJ)')
|
|
107
114
|
.requiredOption('--issue-type <type>', 'Issue type (e.g., Task, Epic, Subtask)')
|
|
108
115
|
.option('--parent <key>', 'Parent issue key (required for subtasks)')
|
|
109
|
-
.action(withPermission('create-task',
|
|
116
|
+
.action(withPermission('create-task', createTaskCommand));
|
|
110
117
|
// About command (always allowed)
|
|
111
118
|
program
|
|
112
119
|
.command('about')
|
|
113
120
|
.description('Show information about available commands')
|
|
114
|
-
.action(
|
|
121
|
+
.action(aboutCommand);
|
|
115
122
|
// Parse command line arguments
|
|
116
123
|
program.parse();
|
package/dist/commands/about.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.aboutCommand = aboutCommand;
|
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const settings_1 = require("../lib/settings");
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getAllowedCommands, getAllowedProjects, isCommandAllowed, getSettingsPath } from '../lib/settings.js';
|
|
9
3
|
const ALL_COMMANDS = [
|
|
10
4
|
{
|
|
11
5
|
name: 'auth',
|
|
@@ -63,36 +57,36 @@ const ALL_COMMANDS = [
|
|
|
63
57
|
usage: 'jira-ai about'
|
|
64
58
|
}
|
|
65
59
|
];
|
|
66
|
-
async function aboutCommand() {
|
|
67
|
-
console.log(
|
|
68
|
-
console.log(
|
|
60
|
+
export async function aboutCommand() {
|
|
61
|
+
console.log(chalk.bold.cyan('\n📋 Jira AI - Available Commands\n'));
|
|
62
|
+
console.log(chalk.bold('Usage:'));
|
|
69
63
|
console.log(' jira-ai <command> [options]\n');
|
|
70
|
-
const allowedCommandsList =
|
|
64
|
+
const allowedCommandsList = getAllowedCommands();
|
|
71
65
|
const isAllAllowed = allowedCommandsList.includes('all');
|
|
72
66
|
// Filter commands based on settings (about is always shown)
|
|
73
|
-
const commandsToShow = ALL_COMMANDS.filter(cmd => cmd.name === 'about' || isAllAllowed ||
|
|
74
|
-
console.log(
|
|
67
|
+
const commandsToShow = ALL_COMMANDS.filter(cmd => cmd.name === 'about' || isAllAllowed || isCommandAllowed(cmd.name));
|
|
68
|
+
console.log(chalk.bold('Available Commands:\n'));
|
|
75
69
|
for (const cmd of commandsToShow) {
|
|
76
|
-
console.log(
|
|
70
|
+
console.log(chalk.yellow(` ${cmd.name}`));
|
|
77
71
|
console.log(` ${cmd.description}`);
|
|
78
72
|
console.log(` Usage: ${cmd.usage}\n`);
|
|
79
73
|
}
|
|
80
74
|
// Show disabled commands if not all are allowed
|
|
81
75
|
if (!isAllAllowed) {
|
|
82
|
-
const disabledCommands = ALL_COMMANDS.filter(cmd => cmd.name !== 'about' && !
|
|
76
|
+
const disabledCommands = ALL_COMMANDS.filter(cmd => cmd.name !== 'about' && !isCommandAllowed(cmd.name));
|
|
83
77
|
if (disabledCommands.length > 0) {
|
|
84
|
-
console.log(
|
|
78
|
+
console.log(chalk.bold('Disabled Commands:\n'));
|
|
85
79
|
for (const cmd of disabledCommands) {
|
|
86
|
-
console.log(
|
|
80
|
+
console.log(chalk.gray(` ${cmd.name} - ${cmd.description}`));
|
|
87
81
|
}
|
|
88
82
|
console.log();
|
|
89
83
|
}
|
|
90
84
|
}
|
|
91
|
-
console.log(
|
|
92
|
-
console.log(
|
|
93
|
-
console.log(
|
|
94
|
-
console.log(` Settings file: ${
|
|
95
|
-
const allowedProjects =
|
|
85
|
+
console.log(chalk.bold('For detailed help on any command, run:'));
|
|
86
|
+
console.log(chalk.green(' jira-ai <command> --help\n'));
|
|
87
|
+
console.log(chalk.bold('Configuration:'));
|
|
88
|
+
console.log(` Settings file: ${chalk.cyan(getSettingsPath())}`);
|
|
89
|
+
const allowedProjects = getAllowedProjects();
|
|
96
90
|
console.log(` - Projects: ${allowedProjects.includes('all') ? 'All allowed' : allowedProjects.join(', ')}`);
|
|
97
91
|
console.log(` - Commands: ${isAllAllowed ? 'All allowed' : allowedCommandsList.join(', ')}\n`);
|
|
98
92
|
}
|
|
@@ -1,65 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.addCommentCommand = addCommentCommand;
|
|
40
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
-
const ora_1 = __importDefault(require("ora"));
|
|
42
|
-
const fs = __importStar(require("fs"));
|
|
43
|
-
const path = __importStar(require("path"));
|
|
44
|
-
const marklassian_1 = require("marklassian");
|
|
45
|
-
const jira_client_1 = require("../lib/jira-client");
|
|
46
|
-
async function addCommentCommand(options) {
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
import { markdownToAdf } from 'marklassian';
|
|
6
|
+
import { addIssueComment } from '../lib/jira-client.js';
|
|
7
|
+
export async function addCommentCommand(options) {
|
|
47
8
|
const { filePath, issueKey } = options;
|
|
48
9
|
// Validate issueKey
|
|
49
10
|
if (!issueKey || issueKey.trim() === '') {
|
|
50
|
-
console.error(
|
|
11
|
+
console.error(chalk.red('\nError: Issue key is required (use --issue-key)'));
|
|
51
12
|
process.exit(1);
|
|
52
13
|
}
|
|
53
14
|
// Validate file path
|
|
54
15
|
if (!filePath || filePath.trim() === '') {
|
|
55
|
-
console.error(
|
|
16
|
+
console.error(chalk.red('\nError: File path is required (use --file-path)'));
|
|
56
17
|
process.exit(1);
|
|
57
18
|
}
|
|
58
19
|
// Resolve file path to absolute
|
|
59
20
|
const absolutePath = path.resolve(filePath);
|
|
60
21
|
// Check file exists
|
|
61
22
|
if (!fs.existsSync(absolutePath)) {
|
|
62
|
-
console.error(
|
|
23
|
+
console.error(chalk.red(`\nError: File not found: ${absolutePath}`));
|
|
63
24
|
process.exit(1);
|
|
64
25
|
}
|
|
65
26
|
// Read file
|
|
@@ -68,41 +29,41 @@ async function addCommentCommand(options) {
|
|
|
68
29
|
markdownContent = fs.readFileSync(absolutePath, 'utf-8');
|
|
69
30
|
}
|
|
70
31
|
catch (error) {
|
|
71
|
-
console.error(
|
|
32
|
+
console.error(chalk.red('\nError reading file: ' +
|
|
72
33
|
(error instanceof Error ? error.message : 'Unknown error')));
|
|
73
34
|
process.exit(1);
|
|
74
35
|
}
|
|
75
36
|
// Validate file is not empty
|
|
76
37
|
if (markdownContent.trim() === '') {
|
|
77
|
-
console.error(
|
|
38
|
+
console.error(chalk.red('\nError: File is empty'));
|
|
78
39
|
process.exit(1);
|
|
79
40
|
}
|
|
80
41
|
// Convert Markdown to ADF
|
|
81
42
|
let adfContent;
|
|
82
43
|
try {
|
|
83
|
-
adfContent =
|
|
44
|
+
adfContent = markdownToAdf(markdownContent);
|
|
84
45
|
}
|
|
85
46
|
catch (error) {
|
|
86
|
-
console.error(
|
|
47
|
+
console.error(chalk.red('\nError converting Markdown to ADF: ' +
|
|
87
48
|
(error instanceof Error ? error.message : 'Unknown error')));
|
|
88
49
|
process.exit(1);
|
|
89
50
|
}
|
|
90
51
|
// Add comment with spinner
|
|
91
|
-
const spinner = (
|
|
52
|
+
const spinner = ora(`Adding comment to ${issueKey}...`).start();
|
|
92
53
|
try {
|
|
93
|
-
await
|
|
94
|
-
spinner.succeed(
|
|
95
|
-
console.log(
|
|
54
|
+
await addIssueComment(issueKey, adfContent);
|
|
55
|
+
spinner.succeed(chalk.green(`Comment added successfully to ${issueKey}`));
|
|
56
|
+
console.log(chalk.gray(`\nFile: ${absolutePath}`));
|
|
96
57
|
}
|
|
97
58
|
catch (error) {
|
|
98
|
-
spinner.fail(
|
|
99
|
-
console.error(
|
|
59
|
+
spinner.fail(chalk.red('Failed to add comment'));
|
|
60
|
+
console.error(chalk.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
100
61
|
// Provide helpful hints based on error
|
|
101
62
|
if (error instanceof Error && error.message.includes('404')) {
|
|
102
|
-
console.log(
|
|
63
|
+
console.log(chalk.yellow('\nHint: Check that the issue key is correct'));
|
|
103
64
|
}
|
|
104
65
|
else if (error instanceof Error && error.message.includes('403')) {
|
|
105
|
-
console.log(
|
|
66
|
+
console.log(chalk.yellow('\nHint: You may not have permission to comment on this issue'));
|
|
106
67
|
}
|
|
107
68
|
process.exit(1);
|
|
108
69
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { addIssueLabels } from '../lib/jira-client.js';
|
|
4
|
+
export async function addLabelCommand(taskId, labelsString) {
|
|
5
|
+
// Validate input
|
|
6
|
+
if (!taskId || taskId.trim() === '') {
|
|
7
|
+
console.error(chalk.red('\nError: Task ID is required'));
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
if (!labelsString || labelsString.trim() === '') {
|
|
11
|
+
console.error(chalk.red('\nError: Labels are required (comma-separated)'));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
// Parse labels
|
|
15
|
+
const labels = labelsString.split(',').map(l => l.trim()).filter(l => l !== '');
|
|
16
|
+
if (labels.length === 0) {
|
|
17
|
+
console.error(chalk.red('\nError: No valid labels provided'));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const spinner = ora(`Adding labels to ${taskId}...`).start();
|
|
21
|
+
try {
|
|
22
|
+
await addIssueLabels(taskId, labels);
|
|
23
|
+
spinner.succeed(chalk.green(`Labels added successfully to ${taskId}`));
|
|
24
|
+
console.log(chalk.gray(`\nLabels: ${labels.join(', ')}`));
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
spinner.fail(chalk.red('Failed to add labels'));
|
|
28
|
+
console.error(chalk.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
29
|
+
if (error instanceof Error && error.message.includes('404')) {
|
|
30
|
+
console.log(chalk.yellow('\nHint: Check that the issue ID/key is correct'));
|
|
31
|
+
}
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,17 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const ora_1 = __importDefault(require("ora"));
|
|
10
|
-
const fs_1 = __importDefault(require("fs"));
|
|
11
|
-
const dotenv_1 = __importDefault(require("dotenv"));
|
|
12
|
-
const jira_client_1 = require("../lib/jira-client");
|
|
13
|
-
const auth_storage_1 = require("../lib/auth-storage");
|
|
14
|
-
const rl = readline_1.default.createInterface({
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import dotenv from 'dotenv';
|
|
6
|
+
import { createTemporaryClient } from '../lib/jira-client.js';
|
|
7
|
+
import { saveCredentials } from '../lib/auth-storage.js';
|
|
8
|
+
const rl = readline.createInterface({
|
|
15
9
|
input: process.stdin,
|
|
16
10
|
output: process.stdout,
|
|
17
11
|
});
|
|
@@ -35,7 +29,7 @@ function askSecret(question) {
|
|
|
35
29
|
});
|
|
36
30
|
});
|
|
37
31
|
}
|
|
38
|
-
async function authCommand(options = {}) {
|
|
32
|
+
export async function authCommand(options = {}) {
|
|
39
33
|
let host = '';
|
|
40
34
|
let email = '';
|
|
41
35
|
let apiToken = '';
|
|
@@ -46,53 +40,53 @@ async function authCommand(options = {}) {
|
|
|
46
40
|
email = data.email;
|
|
47
41
|
apiToken = data.apikey || data.apiToken;
|
|
48
42
|
if (!host || !email || !apiToken) {
|
|
49
|
-
console.error(
|
|
43
|
+
console.error(chalk.red('Error: Missing required fields in JSON. Required: url/host, email, apikey/apiToken.'));
|
|
50
44
|
process.exit(1);
|
|
51
45
|
}
|
|
52
46
|
}
|
|
53
47
|
catch (error) {
|
|
54
|
-
console.error(
|
|
48
|
+
console.error(chalk.red(`Error: Invalid JSON string: ${error.message}`));
|
|
55
49
|
process.exit(1);
|
|
56
50
|
}
|
|
57
51
|
}
|
|
58
52
|
else if (options.fromFile) {
|
|
59
|
-
if (!
|
|
60
|
-
console.error(
|
|
53
|
+
if (!fs.existsSync(options.fromFile)) {
|
|
54
|
+
console.error(chalk.red(`Error: File not found: ${options.fromFile}`));
|
|
61
55
|
process.exit(1);
|
|
62
56
|
}
|
|
63
57
|
try {
|
|
64
|
-
const content =
|
|
65
|
-
const config =
|
|
58
|
+
const content = fs.readFileSync(options.fromFile, 'utf8');
|
|
59
|
+
const config = dotenv.parse(content);
|
|
66
60
|
host = config.JIRA_HOST;
|
|
67
61
|
email = config.JIRA_USER_EMAIL;
|
|
68
62
|
apiToken = config.JIRA_API_TOKEN;
|
|
69
63
|
if (!host || !email || !apiToken) {
|
|
70
|
-
console.error(
|
|
64
|
+
console.error(chalk.red('Error: Missing required environment variables in file. Required: JIRA_HOST, JIRA_USER_EMAIL, JIRA_API_TOKEN.'));
|
|
71
65
|
process.exit(1);
|
|
72
66
|
}
|
|
73
67
|
}
|
|
74
68
|
catch (error) {
|
|
75
|
-
console.error(
|
|
69
|
+
console.error(chalk.red(`Error: Failed to parse file: ${error.message}`));
|
|
76
70
|
process.exit(1);
|
|
77
71
|
}
|
|
78
72
|
}
|
|
79
73
|
if (!host || !email || !apiToken) {
|
|
80
|
-
console.log(
|
|
74
|
+
console.log(chalk.cyan('\n--- Jira Authentication Setup ---\n'));
|
|
81
75
|
try {
|
|
82
76
|
host = await ask('Jira URL (e.g., https://your-domain.atlassian.net): ');
|
|
83
77
|
if (!host) {
|
|
84
|
-
console.error(
|
|
78
|
+
console.error(chalk.red('URL is required.'));
|
|
85
79
|
process.exit(1);
|
|
86
80
|
}
|
|
87
81
|
email = await ask('Email: ');
|
|
88
82
|
if (!email) {
|
|
89
|
-
console.error(
|
|
83
|
+
console.error(chalk.red('Email is required.'));
|
|
90
84
|
process.exit(1);
|
|
91
85
|
}
|
|
92
|
-
console.log(
|
|
86
|
+
console.log(chalk.gray('Get your API token from: https://id.atlassian.com/manage-profile/security/api-tokens'));
|
|
93
87
|
apiToken = await ask('API Token: ');
|
|
94
88
|
if (!apiToken) {
|
|
95
|
-
console.error(
|
|
89
|
+
console.error(chalk.red('API Token is required.'));
|
|
96
90
|
process.exit(1);
|
|
97
91
|
}
|
|
98
92
|
}
|
|
@@ -104,21 +98,21 @@ async function authCommand(options = {}) {
|
|
|
104
98
|
// Non-interactive mode, just close readline
|
|
105
99
|
rl.close();
|
|
106
100
|
}
|
|
107
|
-
const spinner = (
|
|
101
|
+
const spinner = ora('Verifying credentials...').start();
|
|
108
102
|
try {
|
|
109
|
-
const tempClient =
|
|
103
|
+
const tempClient = createTemporaryClient(host, email, apiToken);
|
|
110
104
|
const user = await tempClient.myself.getCurrentUser();
|
|
111
|
-
spinner.succeed(
|
|
112
|
-
console.log(
|
|
113
|
-
|
|
114
|
-
console.log(
|
|
115
|
-
console.log(
|
|
105
|
+
spinner.succeed(chalk.green('Authentication successful!'));
|
|
106
|
+
console.log(chalk.blue(`\nWelcome, ${user.displayName} (${user.emailAddress})`));
|
|
107
|
+
saveCredentials({ host, email, apiToken });
|
|
108
|
+
console.log(chalk.green('\nCredentials saved successfully to ~/.jira-ai/config.json'));
|
|
109
|
+
console.log(chalk.gray('These credentials will be used for future commands on this machine.'));
|
|
116
110
|
}
|
|
117
111
|
catch (error) {
|
|
118
|
-
spinner.fail(
|
|
119
|
-
console.error(
|
|
112
|
+
spinner.fail(chalk.red('Authentication failed.'));
|
|
113
|
+
console.error(chalk.red(`Error: ${error.message || 'Invalid credentials'}`));
|
|
120
114
|
if (error.response && error.response.status === 401) {
|
|
121
|
-
console.error(
|
|
115
|
+
console.error(chalk.yellow('Hint: Check if your email and API token are correct.'));
|
|
122
116
|
}
|
|
123
117
|
process.exit(1);
|
|
124
118
|
}
|
|
@@ -1,59 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.createTaskCommand = createTaskCommand;
|
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const ora_1 = __importDefault(require("ora"));
|
|
9
|
-
const jira_client_1 = require("../lib/jira-client");
|
|
10
|
-
async function createTaskCommand(options) {
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createIssue } from '../lib/jira-client.js';
|
|
4
|
+
export async function createTaskCommand(options) {
|
|
11
5
|
const { title, project, issueType, parent } = options;
|
|
12
6
|
// Validate required fields
|
|
13
7
|
if (!title || title.trim() === '') {
|
|
14
|
-
console.error(
|
|
8
|
+
console.error(chalk.red('\nError: Title is required (use --title)'));
|
|
15
9
|
process.exit(1);
|
|
16
10
|
}
|
|
17
11
|
if (!project || project.trim() === '') {
|
|
18
|
-
console.error(
|
|
12
|
+
console.error(chalk.red('\nError: Project is required (use --project)'));
|
|
19
13
|
process.exit(1);
|
|
20
14
|
}
|
|
21
15
|
if (!issueType || issueType.trim() === '') {
|
|
22
|
-
console.error(
|
|
16
|
+
console.error(chalk.red('\nError: Issue type is required (use --issue-type)'));
|
|
23
17
|
process.exit(1);
|
|
24
18
|
}
|
|
25
19
|
// Create issue with spinner
|
|
26
|
-
const spinner = (
|
|
20
|
+
const spinner = ora(`Creating ${issueType} in project ${project}...`).start();
|
|
27
21
|
try {
|
|
28
|
-
const result = await
|
|
29
|
-
spinner.succeed(
|
|
30
|
-
console.log(
|
|
31
|
-
console.log(
|
|
32
|
-
console.log(
|
|
22
|
+
const result = await createIssue(project, title, issueType, parent);
|
|
23
|
+
spinner.succeed(chalk.green(`Issue created successfully: ${result.key}`));
|
|
24
|
+
console.log(chalk.gray(`\nTitle: ${title}`));
|
|
25
|
+
console.log(chalk.gray(`Project: ${project}`));
|
|
26
|
+
console.log(chalk.gray(`Issue Type: ${issueType}`));
|
|
33
27
|
if (parent) {
|
|
34
|
-
console.log(
|
|
28
|
+
console.log(chalk.gray(`Parent: ${parent}`));
|
|
35
29
|
}
|
|
36
|
-
console.log(
|
|
30
|
+
console.log(chalk.cyan(`\nIssue Key: ${result.key}`));
|
|
37
31
|
}
|
|
38
32
|
catch (error) {
|
|
39
|
-
spinner.fail(
|
|
40
|
-
console.error(
|
|
33
|
+
spinner.fail(chalk.red('Failed to create issue'));
|
|
34
|
+
console.error(chalk.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
|
|
41
35
|
// Provide helpful hints based on error
|
|
42
36
|
if (error instanceof Error) {
|
|
43
37
|
if (error.message.includes('project') || error.message.includes('Project')) {
|
|
44
|
-
console.log(
|
|
45
|
-
console.log(
|
|
38
|
+
console.log(chalk.yellow('\nHint: Check that the project key is correct'));
|
|
39
|
+
console.log(chalk.yellow('Use "jira-ai projects" to see available projects'));
|
|
46
40
|
}
|
|
47
41
|
else if (error.message.includes('issue type') || error.message.includes('issuetype')) {
|
|
48
|
-
console.log(
|
|
49
|
-
console.log(
|
|
42
|
+
console.log(chalk.yellow('\nHint: Check that the issue type is correct'));
|
|
43
|
+
console.log(chalk.yellow(`Use "jira-ai list-issue-types ${project}" to see available issue types`));
|
|
50
44
|
}
|
|
51
45
|
else if (error.message.includes('parent') || error.message.includes('Parent')) {
|
|
52
|
-
console.log(
|
|
53
|
-
console.log(
|
|
46
|
+
console.log(chalk.yellow('\nHint: Check that the parent issue key is correct'));
|
|
47
|
+
console.log(chalk.yellow('Parent issues are required for subtasks'));
|
|
54
48
|
}
|
|
55
49
|
else if (error.message.includes('403')) {
|
|
56
|
-
console.log(
|
|
50
|
+
console.log(chalk.yellow('\nHint: You may not have permission to create issues in this project'));
|
|
57
51
|
}
|
|
58
52
|
}
|
|
59
53
|
process.exit(1);
|