jira-ai 0.3.21 → 0.4.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/dist/cli.js CHANGED
@@ -27,14 +27,16 @@ import { CliError } from './types/errors.js';
27
27
  import { CommandError } from './lib/errors.js';
28
28
  import { ui } from './lib/ui.js';
29
29
  import { validateOptions, CreateTaskSchema, AddCommentSchema, UpdateDescriptionSchema, RunJqlSchema, IssueKeySchema, ProjectKeySchema } from './lib/validation.js';
30
+ import { realpathSync } from 'fs';
30
31
  // Load environment variables
32
+ // @ts-ignore - quiet option exists but is not in types
31
33
  dotenv.config({ quiet: true });
32
34
  // Create CLI program
33
35
  const program = new Command();
34
36
  program
35
37
  .name('jira-ai')
36
38
  .description('CLI tool for interacting with Atlassian Jira')
37
- .version('0.3.21')
39
+ .version('0.3.22')
38
40
  .option('-o, --organization <alias>', 'Override the active Jira organization');
39
41
  // Hook to handle the global option before any command runs
40
42
  program.on('option:organization', (alias) => {
@@ -289,7 +291,10 @@ export async function main() {
289
291
  }
290
292
  }
291
293
  }
292
- if (process.argv[1]?.endsWith('cli.ts') || process.argv[1]?.endsWith('cli.js')) {
294
+ // Check if this file is being run directly (handles symlinks)
295
+ const scriptPath = process.argv[1] ? realpathSync(process.argv[1]) : '';
296
+ const isMainModule = scriptPath.endsWith('cli.ts') || scriptPath.endsWith('cli.js');
297
+ if (isMainModule) {
293
298
  main();
294
299
  }
295
300
  export { program };
@@ -1,6 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  export async function aboutCommand() {
3
3
  console.log(chalk.bold.cyan('\nšŸ“‹ Jira AI\n'));
4
- console.log(`${chalk.bold('Version:')} 0.3.17`);
4
+ console.log(`${chalk.bold('Version:')} 0.4.0`);
5
5
  console.log(`${chalk.bold('GitHub:')} https://github.com/festoinc/jira-ai\n`);
6
6
  }
@@ -44,11 +44,12 @@ export async function addCommentCommand(options) {
44
44
  console.log(chalk.gray(`\nFile: ${absolutePath}`));
45
45
  }
46
46
  catch (error) {
47
+ const errorMsg = error.message?.toLowerCase() || '';
47
48
  const hints = [];
48
- if (error.message?.includes('404')) {
49
+ if (errorMsg.includes('404')) {
49
50
  hints.push('Check that the issue key is correct');
50
51
  }
51
- else if (error.message?.includes('403')) {
52
+ else if (errorMsg.includes('403')) {
52
53
  hints.push('You may not have permission to comment on this issue');
53
54
  }
54
55
  throw new CommandError(`Failed to add comment: ${error.message}`, { hints });
@@ -21,8 +21,9 @@ export async function addLabelCommand(taskId, labelsString) {
21
21
  console.log(chalk.gray(`\nLabels: ${labels.join(', ')}`));
22
22
  }
23
23
  catch (error) {
24
+ const errorMsg = error.message?.toLowerCase() || '';
24
25
  const hints = [];
25
- if (error.message?.includes('404')) {
26
+ if (errorMsg.includes('404')) {
26
27
  hints.push('Check that the issue ID/key is correct');
27
28
  }
28
29
  throw new CommandError(`Failed to add labels: ${error.message}`, { hints });
@@ -21,20 +21,18 @@ export async function createTaskCommand(options) {
21
21
  console.log(chalk.cyan(`\nIssue Key: ${result.key}`));
22
22
  }
23
23
  catch (error) {
24
+ const errorMsg = error.message?.toLowerCase() || '';
24
25
  const hints = [];
25
- if (error.message?.includes('project') || error.message?.includes('Project')) {
26
- hints.push('Check that the project key is correct');
27
- hints.push('Use "jira-ai projects" to see available projects');
26
+ if (errorMsg.includes('project')) {
27
+ hints.push('Check that the project key is correct', 'Use "jira-ai projects" to see available projects');
28
28
  }
29
- else if (error.message?.includes('issue type') || error.message?.includes('issuetype')) {
30
- hints.push('Check that the issue type is correct');
31
- hints.push(`Use "jira-ai list-issue-types ${project}" to see available issue types`);
29
+ else if (errorMsg.includes('issue type') || errorMsg.includes('issuetype')) {
30
+ hints.push('Check that the issue type is correct', `Use "jira-ai list-issue-types ${project}" to see available issue types`);
32
31
  }
33
- else if (error.message?.includes('parent') || error.message?.includes('Parent')) {
34
- hints.push('Check that the parent issue key is correct');
35
- hints.push('Parent issues are required for subtasks');
32
+ else if (errorMsg.includes('parent')) {
33
+ hints.push('Check that the parent issue key is correct', 'Parent issues are required for subtasks');
36
34
  }
37
- else if (error.message?.includes('403')) {
35
+ else if (errorMsg.includes('403')) {
38
36
  hints.push('You may not have permission to create issues in this project');
39
37
  }
40
38
  throw new CommandError(`Failed to create issue: ${error.message}`, { hints });
@@ -21,8 +21,9 @@ export async function deleteLabelCommand(taskId, labelsString) {
21
21
  console.log(chalk.gray(`\nLabels: ${labels.join(', ')}`));
22
22
  }
23
23
  catch (error) {
24
+ const errorMsg = error.message?.toLowerCase() || '';
24
25
  const hints = [];
25
- if (error.message?.includes('404')) {
26
+ if (errorMsg.includes('404')) {
26
27
  hints.push('Check that the issue ID/key is correct');
27
28
  }
28
29
  throw new CommandError(`Failed to remove labels: ${error.message}`, { hints });
@@ -15,11 +15,12 @@ export async function taskWithDetailsCommand(taskId, options = {}) {
15
15
  console.log(formatTaskDetails(task));
16
16
  }
17
17
  catch (error) {
18
+ const errorMsg = error.message?.toLowerCase() || '';
18
19
  const hints = [];
19
- if (error.response?.status === 404 || error.message?.includes('404')) {
20
+ if (error.response?.status === 404 || errorMsg.includes('404')) {
20
21
  hints.push('Check that the task ID is correct');
21
22
  }
22
- else if (error.response?.status === 403 || error.message?.includes('403')) {
23
+ else if (error.response?.status === 403 || errorMsg.includes('403')) {
23
24
  hints.push('You may not have permission to view this issue');
24
25
  }
25
26
  throw new CommandError(`Failed to fetch task details: ${error.message}`, { hints });
@@ -35,11 +35,12 @@ export async function transitionCommand(taskId, toStatus) {
35
35
  if (error instanceof CommandError) {
36
36
  throw error;
37
37
  }
38
+ const errorMsg = error.message?.toLowerCase() || '';
38
39
  const hints = [];
39
- if (error.message?.includes('403')) {
40
+ if (errorMsg.includes('403')) {
40
41
  hints.push('You may not have permission to transition this issue');
41
42
  }
42
- else if (error.message?.includes('required') || (error.response?.data?.errors && Object.keys(error.response.data.errors).length > 0)) {
43
+ else if (errorMsg.includes('required') || error.response?.data?.errors) {
43
44
  hints.push('This transition might require mandatory fields that are not yet supported by this command.');
44
45
  if (error.response?.data?.errors) {
45
46
  const fields = Object.keys(error.response.data.errors).join(', ');
@@ -46,11 +46,12 @@ export async function updateDescriptionCommand(taskId, options) {
46
46
  console.log(chalk.gray(`\nFile: ${absolutePath}`));
47
47
  }
48
48
  catch (error) {
49
+ const errorMsg = error.message?.toLowerCase() || '';
49
50
  const hints = [];
50
- if (error.message?.includes('404')) {
51
+ if (errorMsg.includes('404')) {
51
52
  hints.push('Check that the task ID is correct');
52
53
  }
53
- else if (error.message?.includes('403')) {
54
+ else if (errorMsg.includes('403')) {
54
55
  hints.push('You may not have permission to edit this issue');
55
56
  }
56
57
  throw new CommandError(`Failed to update description: ${error.message}`, { hints });
@@ -52,23 +52,21 @@ export function formatTaskDetails(task) {
52
52
  const infoTable = createTable(['Property', 'Value'], [15, 65]);
53
53
  infoTable.push(['Status', chalk.green(task.status.name)], ['Assignee', task.assignee?.displayName || chalk.gray('Unassigned')], ['Reporter', task.reporter?.displayName || chalk.gray('N/A')], ['Created', formatTimestamp(task.created)], ['Updated', formatTimestamp(task.updated)]);
54
54
  // Add Due Date
55
- let dueDateValue = chalk.gray('N/A');
56
55
  if (task.dueDate) {
57
56
  const today = new Date();
58
57
  today.setHours(0, 0, 0, 0);
59
58
  const dueDate = new Date(task.dueDate);
60
59
  dueDate.setHours(0, 0, 0, 0);
61
- if (dueDate < today) {
62
- // Overdue
63
- const isDone = task.status.category?.toLowerCase() === 'done';
64
- dueDateValue = isDone ? task.dueDate : chalk.red(task.dueDate);
65
- }
66
- else {
67
- // Today or future
68
- dueDateValue = chalk.green(task.dueDate);
69
- }
60
+ const isOverdue = dueDate < today;
61
+ const isDone = task.status.category?.toLowerCase() === 'done';
62
+ const dueDateValue = isOverdue && !isDone
63
+ ? chalk.red(task.dueDate)
64
+ : chalk.green(task.dueDate);
65
+ infoTable.push(['Due Date', dueDateValue]);
66
+ }
67
+ else {
68
+ infoTable.push(['Due Date', chalk.gray('N/A')]);
70
69
  }
71
- infoTable.push(['Due Date', dueDateValue]);
72
70
  // Add labels to basic info table if present
73
71
  if (task.labels && task.labels.length > 0) {
74
72
  const labelsString = task.labels
@@ -25,7 +25,7 @@ export function loadSettings() {
25
25
  try {
26
26
  const fileContents = fs.readFileSync(localSettingsPath, 'utf8');
27
27
  fs.writeFileSync(SETTINGS_FILE, fileContents);
28
- console.log(chalk?.cyan ? chalk.cyan(`Migrated settings.yaml to ${SETTINGS_FILE}`) : `Migrated settings.yaml to ${SETTINGS_FILE}`);
28
+ console.log(chalk.cyan(`Migrated settings.yaml to ${SETTINGS_FILE}`));
29
29
  }
30
30
  catch (error) {
31
31
  console.error('Error migrating settings.yaml:', error);
@@ -39,13 +39,11 @@ export const NumericStringSchema = z
39
39
  export function validateOptions(schema, data) {
40
40
  const result = schema.safeParse(data);
41
41
  if (!result.success) {
42
- const issues = result.error.issues || [];
43
- const simpleErrorMessages = issues
42
+ const simpleErrorMessages = result.error.issues
44
43
  .map((err) => {
45
- // If it's a simple string validation (no object path), just return the message
46
- if (err.path.length === 0)
44
+ if (err.path.length === 0) {
47
45
  return err.message;
48
- // For object validation, include the field name
46
+ }
49
47
  return `${err.path.join('.')}: ${err.message}`;
50
48
  })
51
49
  .join('\n');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jira-ai",
3
- "version": "0.3.21",
3
+ "version": "0.4.0",
4
4
  "description": "AI friendly Jira CLI to save context",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",