jira-ai 0.3.6 → 0.3.8

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 CHANGED
@@ -16,6 +16,8 @@ const list_issue_types_1 = require("./commands/list-issue-types");
16
16
  const run_jql_1 = require("./commands/run-jql");
17
17
  const update_description_1 = require("./commands/update-description");
18
18
  const add_comment_1 = require("./commands/add-comment");
19
+ const add_label_1 = require("./commands/add-label");
20
+ const delete_label_1 = require("./commands/delete-label");
19
21
  const create_task_1 = require("./commands/create-task");
20
22
  const about_1 = require("./commands/about");
21
23
  const auth_1 = require("./commands/auth");
@@ -51,7 +53,9 @@ function withPermission(commandName, commandFn, skipValidation = false) {
51
53
  program
52
54
  .command('auth')
53
55
  .description('Set up Jira authentication credentials')
54
- .action(() => (0, auth_1.authCommand)());
56
+ .option('--from-json <json_string>', 'Accepts a raw JSON string with credentials')
57
+ .option('--from-file <path>', 'Accepts a path to a file (typically .env) with credentials')
58
+ .action((options) => (0, auth_1.authCommand)(options));
55
59
  // Me command
56
60
  program
57
61
  .command('me')
@@ -96,6 +100,16 @@ program
96
100
  .requiredOption('--file-path <path>', 'Path to Markdown file')
97
101
  .requiredOption('--issue-key <key>', 'Jira issue key (e.g., PS-123)')
98
102
  .action(withPermission('add-comment', add_comment_1.addCommentCommand));
103
+ // Add label command
104
+ program
105
+ .command('add-label-to-issue <task-id> <labels>')
106
+ .description('Add one or more labels to a Jira issue (comma-separated)')
107
+ .action(withPermission('add-label-to-issue', add_label_1.addLabelCommand));
108
+ // Delete label command
109
+ program
110
+ .command('delete-label-from-issue <task-id> <labels>')
111
+ .description('Remove one or more labels from a Jira issue (comma-separated)')
112
+ .action(withPermission('delete-label-from-issue', delete_label_1.deleteLabelCommand));
99
113
  // Create task command
100
114
  program
101
115
  .command('create-task')
@@ -0,0 +1,40 @@
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.addLabelCommand = addLabelCommand;
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 addLabelCommand(taskId, labelsString) {
11
+ // Validate input
12
+ if (!taskId || taskId.trim() === '') {
13
+ console.error(chalk_1.default.red('\nError: Task ID is required'));
14
+ process.exit(1);
15
+ }
16
+ if (!labelsString || labelsString.trim() === '') {
17
+ console.error(chalk_1.default.red('\nError: Labels are required (comma-separated)'));
18
+ process.exit(1);
19
+ }
20
+ // Parse labels
21
+ const labels = labelsString.split(',').map(l => l.trim()).filter(l => l !== '');
22
+ if (labels.length === 0) {
23
+ console.error(chalk_1.default.red('\nError: No valid labels provided'));
24
+ process.exit(1);
25
+ }
26
+ const spinner = (0, ora_1.default)(`Adding labels to ${taskId}...`).start();
27
+ try {
28
+ await (0, jira_client_1.addIssueLabels)(taskId, labels);
29
+ spinner.succeed(chalk_1.default.green(`Labels added successfully to ${taskId}`));
30
+ console.log(chalk_1.default.gray(`\nLabels: ${labels.join(', ')}`));
31
+ }
32
+ catch (error) {
33
+ spinner.fail(chalk_1.default.red('Failed to add labels'));
34
+ console.error(chalk_1.default.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
35
+ if (error instanceof Error && error.message.includes('404')) {
36
+ console.log(chalk_1.default.yellow('\nHint: Check that the issue ID/key is correct'));
37
+ }
38
+ process.exit(1);
39
+ }
40
+ }
@@ -7,6 +7,8 @@ exports.authCommand = authCommand;
7
7
  const readline_1 = __importDefault(require("readline"));
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
9
  const ora_1 = __importDefault(require("ora"));
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const dotenv_1 = __importDefault(require("dotenv"));
10
12
  const jira_client_1 = require("../lib/jira-client");
11
13
  const auth_storage_1 = require("../lib/auth-storage");
12
14
  const rl = readline_1.default.createInterface({
@@ -33,45 +35,91 @@ function askSecret(question) {
33
35
  });
34
36
  });
35
37
  }
36
- async function authCommand() {
37
- console.log(chalk_1.default.cyan('\n--- Jira Authentication Setup ---\n'));
38
- try {
39
- const host = await ask('Jira URL (e.g., https://your-domain.atlassian.net): ');
40
- if (!host) {
41
- console.error(chalk_1.default.red('URL is required.'));
42
- process.exit(1);
38
+ async function authCommand(options = {}) {
39
+ let host = '';
40
+ let email = '';
41
+ let apiToken = '';
42
+ if (options.fromJson) {
43
+ try {
44
+ const data = JSON.parse(options.fromJson);
45
+ host = data.url || data.host;
46
+ email = data.email;
47
+ apiToken = data.apikey || data.apiToken;
48
+ if (!host || !email || !apiToken) {
49
+ console.error(chalk_1.default.red('Error: Missing required fields in JSON. Required: url/host, email, apikey/apiToken.'));
50
+ process.exit(1);
51
+ }
43
52
  }
44
- const email = await ask('Email: ');
45
- if (!email) {
46
- console.error(chalk_1.default.red('Email is required.'));
53
+ catch (error) {
54
+ console.error(chalk_1.default.red(`Error: Invalid JSON string: ${error.message}`));
47
55
  process.exit(1);
48
56
  }
49
- console.log(chalk_1.default.gray('Get your API token from: https://id.atlassian.com/manage-profile/security/api-tokens'));
50
- const apiToken = await ask('API Token: ');
51
- if (!apiToken) {
52
- console.error(chalk_1.default.red('API Token is required.'));
57
+ }
58
+ else if (options.fromFile) {
59
+ if (!fs_1.default.existsSync(options.fromFile)) {
60
+ console.error(chalk_1.default.red(`Error: File not found: ${options.fromFile}`));
53
61
  process.exit(1);
54
62
  }
55
- const spinner = (0, ora_1.default)('Verifying credentials...').start();
56
63
  try {
57
- const tempClient = (0, jira_client_1.createTemporaryClient)(host, email, apiToken);
58
- const user = await tempClient.myself.getCurrentUser();
59
- spinner.succeed(chalk_1.default.green('Authentication successful!'));
60
- console.log(chalk_1.default.blue(`\nWelcome, ${user.displayName} (${user.emailAddress})`));
61
- (0, auth_storage_1.saveCredentials)({ host, email, apiToken });
62
- console.log(chalk_1.default.green('\nCredentials saved successfully to ~/.jira-ai/config.json'));
63
- console.log(chalk_1.default.gray('These credentials will be used for future commands on this machine.'));
64
+ const content = fs_1.default.readFileSync(options.fromFile, 'utf8');
65
+ const config = dotenv_1.default.parse(content);
66
+ host = config.JIRA_HOST;
67
+ email = config.JIRA_USER_EMAIL;
68
+ apiToken = config.JIRA_API_TOKEN;
69
+ if (!host || !email || !apiToken) {
70
+ console.error(chalk_1.default.red('Error: Missing required environment variables in file. Required: JIRA_HOST, JIRA_USER_EMAIL, JIRA_API_TOKEN.'));
71
+ process.exit(1);
72
+ }
64
73
  }
65
74
  catch (error) {
66
- spinner.fail(chalk_1.default.red('Authentication failed.'));
67
- console.error(chalk_1.default.red(`Error: ${error.message || 'Invalid credentials'}`));
68
- if (error.response && error.response.status === 401) {
69
- console.error(chalk_1.default.yellow('Hint: Check if your email and API token are correct.'));
70
- }
75
+ console.error(chalk_1.default.red(`Error: Failed to parse file: ${error.message}`));
71
76
  process.exit(1);
72
77
  }
73
78
  }
74
- finally {
79
+ if (!host || !email || !apiToken) {
80
+ console.log(chalk_1.default.cyan('\n--- Jira Authentication Setup ---\n'));
81
+ try {
82
+ host = await ask('Jira URL (e.g., https://your-domain.atlassian.net): ');
83
+ if (!host) {
84
+ console.error(chalk_1.default.red('URL is required.'));
85
+ process.exit(1);
86
+ }
87
+ email = await ask('Email: ');
88
+ if (!email) {
89
+ console.error(chalk_1.default.red('Email is required.'));
90
+ process.exit(1);
91
+ }
92
+ console.log(chalk_1.default.gray('Get your API token from: https://id.atlassian.com/manage-profile/security/api-tokens'));
93
+ apiToken = await ask('API Token: ');
94
+ if (!apiToken) {
95
+ console.error(chalk_1.default.red('API Token is required.'));
96
+ process.exit(1);
97
+ }
98
+ }
99
+ finally {
100
+ rl.close();
101
+ }
102
+ }
103
+ else {
104
+ // Non-interactive mode, just close readline
75
105
  rl.close();
76
106
  }
107
+ const spinner = (0, ora_1.default)('Verifying credentials...').start();
108
+ try {
109
+ const tempClient = (0, jira_client_1.createTemporaryClient)(host, email, apiToken);
110
+ const user = await tempClient.myself.getCurrentUser();
111
+ spinner.succeed(chalk_1.default.green('Authentication successful!'));
112
+ console.log(chalk_1.default.blue(`\nWelcome, ${user.displayName} (${user.emailAddress})`));
113
+ (0, auth_storage_1.saveCredentials)({ host, email, apiToken });
114
+ console.log(chalk_1.default.green('\nCredentials saved successfully to ~/.jira-ai/config.json'));
115
+ console.log(chalk_1.default.gray('These credentials will be used for future commands on this machine.'));
116
+ }
117
+ catch (error) {
118
+ spinner.fail(chalk_1.default.red('Authentication failed.'));
119
+ console.error(chalk_1.default.red(`Error: ${error.message || 'Invalid credentials'}`));
120
+ if (error.response && error.response.status === 401) {
121
+ console.error(chalk_1.default.yellow('Hint: Check if your email and API token are correct.'));
122
+ }
123
+ process.exit(1);
124
+ }
77
125
  }
@@ -0,0 +1,40 @@
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.deleteLabelCommand = deleteLabelCommand;
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 deleteLabelCommand(taskId, labelsString) {
11
+ // Validate input
12
+ if (!taskId || taskId.trim() === '') {
13
+ console.error(chalk_1.default.red('\nError: Task ID is required'));
14
+ process.exit(1);
15
+ }
16
+ if (!labelsString || labelsString.trim() === '') {
17
+ console.error(chalk_1.default.red('\nError: Labels are required (comma-separated)'));
18
+ process.exit(1);
19
+ }
20
+ // Parse labels
21
+ const labels = labelsString.split(',').map(l => l.trim()).filter(l => l !== '');
22
+ if (labels.length === 0) {
23
+ console.error(chalk_1.default.red('\nError: No valid labels provided'));
24
+ process.exit(1);
25
+ }
26
+ const spinner = (0, ora_1.default)(`Removing labels from ${taskId}...`).start();
27
+ try {
28
+ await (0, jira_client_1.removeIssueLabels)(taskId, labels);
29
+ spinner.succeed(chalk_1.default.green(`Labels removed successfully from ${taskId}`));
30
+ console.log(chalk_1.default.gray(`\nLabels: ${labels.join(', ')}`));
31
+ }
32
+ catch (error) {
33
+ spinner.fail(chalk_1.default.red('Failed to remove labels'));
34
+ console.error(chalk_1.default.red('\nError: ' + (error instanceof Error ? error.message : 'Unknown error')));
35
+ if (error instanceof Error && error.message.includes('404')) {
36
+ console.log(chalk_1.default.yellow('\nHint: Check that the issue ID/key is correct'));
37
+ }
38
+ process.exit(1);
39
+ }
40
+ }
@@ -62,6 +62,13 @@ function formatTaskDetails(task) {
62
62
  // Basic info table
63
63
  const infoTable = createTable(['Property', 'Value'], [15, 65]);
64
64
  infoTable.push(['Status', chalk_1.default.green(task.status.name)], ['Assignee', task.assignee?.displayName || chalk_1.default.gray('Unassigned')], ['Reporter', task.reporter?.displayName || chalk_1.default.gray('N/A')], ['Created', (0, utils_1.formatTimestamp)(task.created)], ['Updated', (0, utils_1.formatTimestamp)(task.updated)]);
65
+ // Add labels to basic info table if present
66
+ if (task.labels && task.labels.length > 0) {
67
+ const labelsString = task.labels
68
+ .map((label) => chalk_1.default.bgBlue.white.bold(` ${label} `))
69
+ .join(' ');
70
+ infoTable.push(['Labels', labelsString]);
71
+ }
65
72
  output += infoTable.toString() + '\n\n';
66
73
  // Parent Task
67
74
  if (task.parent) {
@@ -11,6 +11,8 @@ exports.updateIssueDescription = updateIssueDescription;
11
11
  exports.addIssueComment = addIssueComment;
12
12
  exports.getProjectIssueTypes = getProjectIssueTypes;
13
13
  exports.createIssue = createIssue;
14
+ exports.addIssueLabels = addIssueLabels;
15
+ exports.removeIssueLabels = removeIssueLabels;
14
16
  const jira_js_1 = require("jira.js");
15
17
  const utils_1 = require("./utils");
16
18
  const auth_storage_1 = require("./auth-storage");
@@ -119,6 +121,7 @@ async function getTaskWithDetails(taskId) {
119
121
  'comment',
120
122
  'parent',
121
123
  'subtasks',
124
+ 'labels',
122
125
  ],
123
126
  });
124
127
  // Extract comments
@@ -169,6 +172,7 @@ async function getTaskWithDetails(taskId) {
169
172
  } : undefined,
170
173
  created: issue.fields.created || '',
171
174
  updated: issue.fields.updated || '',
175
+ labels: issue.fields.labels || [],
172
176
  comments,
173
177
  parent,
174
178
  subtasks,
@@ -303,3 +307,35 @@ async function createIssue(projectKey, summary, issueTypeName, parentKey) {
303
307
  id: response.id || '',
304
308
  };
305
309
  }
310
+ /**
311
+ * Add labels to a Jira issue
312
+ * @param taskId - The issue key (e.g., "PROJ-123")
313
+ * @param labels - Array of labels to add
314
+ */
315
+ async function addIssueLabels(taskId, labels) {
316
+ const client = getJiraClient();
317
+ await client.issues.editIssue({
318
+ issueIdOrKey: taskId,
319
+ update: {
320
+ labels: labels.map(label => ({
321
+ add: label,
322
+ })),
323
+ },
324
+ });
325
+ }
326
+ /**
327
+ * Remove labels from a Jira issue
328
+ * @param taskId - The issue key (e.g., "PROJ-123")
329
+ * @param labels - Array of labels to remove
330
+ */
331
+ async function removeIssueLabels(taskId, labels) {
332
+ const client = getJiraClient();
333
+ await client.issues.editIssue({
334
+ issueIdOrKey: taskId,
335
+ update: {
336
+ labels: labels.map(label => ({
337
+ remove: label,
338
+ })),
339
+ },
340
+ });
341
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jira-ai",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "description": "AI friendly Jira CLI to save context",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
package/settings.yaml CHANGED
@@ -13,14 +13,12 @@ projects:
13
13
  # Commands: List of allowed commands (use "all" to allow all commands)
14
14
  # Available commands: me, projects, task-with-details, project-statuses, list-issue-types, run-jql, update-description, add-comment, create-task, about
15
15
  commands:
16
- - me
17
- - projects
18
- - run-jql
16
+ - all
19
17
  # Uncomment below to allow all commands
20
18
  # - all
21
- - task-with-details
19
+ # - task-with-details
22
20
  # - project-statuses
23
- - list-issue-types
24
- - update-description
25
- - add-comment
26
- - create-task
21
+ # - list-issue-types
22
+ # - update-description
23
+ # - add-comment
24
+ # - create-task