jira-pilot 2.1.2 → 2.1.3

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.
Files changed (107) hide show
  1. package/README.md +23 -0
  2. package/bin/jira.ts +2 -0
  3. package/dist/bin/jira.js +2 -0
  4. package/dist/bin/jira.js.map +1 -1
  5. package/dist/src/commands/ai-actions/plan.js +1 -1
  6. package/dist/src/commands/ai-actions/plan.js.map +1 -1
  7. package/dist/src/commands/ai-actions/review.js +5 -4
  8. package/dist/src/commands/ai-actions/review.js.map +1 -1
  9. package/dist/src/commands/ai-actions/standup.js +1 -1
  10. package/dist/src/commands/ai-actions/standup.js.map +1 -1
  11. package/dist/src/commands/ai.js +1 -1
  12. package/dist/src/commands/ai.js.map +1 -1
  13. package/dist/src/commands/board.js +10 -5
  14. package/dist/src/commands/board.js.map +1 -1
  15. package/dist/src/commands/bulk.js +1 -1
  16. package/dist/src/commands/bulk.js.map +1 -1
  17. package/dist/src/commands/config.js +1 -1
  18. package/dist/src/commands/config.js.map +1 -1
  19. package/dist/src/commands/dashboard.js +11 -5
  20. package/dist/src/commands/dashboard.js.map +1 -1
  21. package/dist/src/commands/filter.js +7 -4
  22. package/dist/src/commands/filter.js.map +1 -1
  23. package/dist/src/commands/git.js +1 -1
  24. package/dist/src/commands/git.js.map +1 -1
  25. package/dist/src/commands/issue-attach.js +1 -1
  26. package/dist/src/commands/issue-attach.js.map +1 -1
  27. package/dist/src/commands/issue-pr.js +1 -1
  28. package/dist/src/commands/issue-pr.js.map +1 -1
  29. package/dist/src/commands/issue-worklog.js +10 -5
  30. package/dist/src/commands/issue-worklog.js.map +1 -1
  31. package/dist/src/commands/issue.js +142 -87
  32. package/dist/src/commands/issue.js.map +1 -1
  33. package/dist/src/commands/project.js +10 -5
  34. package/dist/src/commands/project.js.map +1 -1
  35. package/dist/src/commands/sprint.js +19 -8
  36. package/dist/src/commands/sprint.js.map +1 -1
  37. package/dist/src/commands/tui.d.ts +2 -0
  38. package/dist/src/commands/tui.js +10 -0
  39. package/dist/src/commands/tui.js.map +1 -0
  40. package/dist/src/services/ai-service.js +7 -4
  41. package/dist/src/services/ai-service.js.map +1 -1
  42. package/dist/src/services/api-service.d.ts +2 -0
  43. package/dist/src/services/api-service.js +29 -20
  44. package/dist/src/services/api-service.js.map +1 -1
  45. package/dist/src/tui/App.d.ts +1 -0
  46. package/dist/src/tui/App.js +26 -0
  47. package/dist/src/tui/App.js.map +1 -0
  48. package/dist/src/tui/index.d.ts +1 -0
  49. package/dist/src/tui/index.js +8 -0
  50. package/dist/src/tui/index.js.map +1 -0
  51. package/dist/src/tui/screens/BoardList.d.ts +1 -0
  52. package/dist/src/tui/screens/BoardList.js +71 -0
  53. package/dist/src/tui/screens/BoardList.js.map +1 -0
  54. package/dist/src/tui/screens/Dashboard.d.ts +1 -0
  55. package/dist/src/tui/screens/Dashboard.js +41 -0
  56. package/dist/src/tui/screens/Dashboard.js.map +1 -0
  57. package/dist/src/tui/screens/IssueDetail.d.ts +6 -0
  58. package/dist/src/tui/screens/IssueDetail.js +40 -0
  59. package/dist/src/tui/screens/IssueDetail.js.map +1 -0
  60. package/dist/src/tui/screens/IssueList.d.ts +1 -0
  61. package/dist/src/tui/screens/IssueList.js +72 -0
  62. package/dist/src/tui/screens/IssueList.js.map +1 -0
  63. package/dist/src/tui/screens/KanbanBoard.d.ts +6 -0
  64. package/dist/src/tui/screens/KanbanBoard.js +86 -0
  65. package/dist/src/tui/screens/KanbanBoard.js.map +1 -0
  66. package/dist/src/tui/utils/adf-render.d.ts +1 -0
  67. package/dist/src/tui/utils/adf-render.js +29 -0
  68. package/dist/src/tui/utils/adf-render.js.map +1 -0
  69. package/dist/src/utils/error-handler.d.ts +2 -2
  70. package/dist/src/utils/error-handler.js.map +1 -1
  71. package/dist/src/utils/http.d.ts +27 -0
  72. package/dist/src/utils/http.js +95 -0
  73. package/dist/src/utils/http.js.map +1 -0
  74. package/dist/src/utils/spinner.d.ts +21 -0
  75. package/dist/src/utils/spinner.js +79 -0
  76. package/dist/src/utils/spinner.js.map +1 -0
  77. package/package.json +10 -5
  78. package/src/commands/ai-actions/plan.ts +1 -1
  79. package/src/commands/ai-actions/review.ts +5 -4
  80. package/src/commands/ai-actions/standup.ts +1 -1
  81. package/src/commands/ai.ts +1 -1
  82. package/src/commands/board.ts +10 -5
  83. package/src/commands/bulk.ts +1 -1
  84. package/src/commands/config.ts +1 -1
  85. package/src/commands/dashboard.ts +11 -5
  86. package/src/commands/filter.ts +8 -5
  87. package/src/commands/git.ts +1 -1
  88. package/src/commands/issue-attach.ts +1 -1
  89. package/src/commands/issue-pr.ts +1 -1
  90. package/src/commands/issue-worklog.ts +10 -5
  91. package/src/commands/issue.ts +149 -89
  92. package/src/commands/project.ts +10 -5
  93. package/src/commands/sprint.ts +19 -8
  94. package/src/commands/tui.ts +11 -0
  95. package/src/services/ai-service.ts +7 -4
  96. package/src/services/api-service.ts +29 -21
  97. package/src/tui/App.tsx +61 -0
  98. package/src/tui/index.tsx +8 -0
  99. package/src/tui/screens/BoardList.tsx +102 -0
  100. package/src/tui/screens/Dashboard.tsx +75 -0
  101. package/src/tui/screens/IssueDetail.tsx +93 -0
  102. package/src/tui/screens/IssueList.tsx +116 -0
  103. package/src/tui/screens/KanbanBoard.tsx +133 -0
  104. package/src/tui/utils/adf-render.ts +30 -0
  105. package/src/utils/error-handler.ts +2 -2
  106. package/src/utils/http.ts +128 -0
  107. package/src/utils/spinner.ts +87 -0
@@ -1,8 +1,8 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import Table from 'cli-table3';
3
+ import { Table } from 'cmd-table';
4
4
  import { api } from '../services/api-service.js';
5
- import ora from 'ora';
5
+ import ora from '../utils/spinner.js';
6
6
  import { handleCommandError } from '../utils/error-handler.js';
7
7
 
8
8
  export function registerBoardCommand(program: Command) {
@@ -51,11 +51,16 @@ Common Actions:
51
51
  }
52
52
 
53
53
  const table = new Table({
54
- head: [chalk.bold('ID'), chalk.bold('Name'), chalk.bold('Type'), chalk.bold('Project')]
54
+ columns: [
55
+ { name: chalk.bold('ID') },
56
+ { name: chalk.bold('Name') },
57
+ { name: chalk.bold('Type') },
58
+ { name: chalk.bold('Project') }
59
+ ]
55
60
  });
56
61
 
57
62
  data.values.forEach((b: any) => {
58
- table.push([
63
+ table.addRow([
59
64
  b.id,
60
65
  b.name,
61
66
  b.type,
@@ -63,7 +68,7 @@ Common Actions:
63
68
  ]);
64
69
  });
65
70
 
66
- console.log(table.toString());
71
+ console.log(table.render());
67
72
  console.log(chalk.grey(`Showing ${data.values.length} board(s)`));
68
73
 
69
74
  } catch (e: any) {
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import ora from 'ora';
3
+ import ora from '../utils/spinner.js';
4
4
  import enquirer from 'enquirer';
5
5
  import { api } from '../services/api-service.js';
6
6
  import { handleCommandError } from '../utils/error-handler.js';
@@ -3,7 +3,7 @@ import chalk from 'chalk';
3
3
  import enquirer from 'enquirer';
4
4
  import { setCredentials, getCredentials, clearCredentials, saveProfile, loadProfile, deleteProfile, listProfiles, getActiveProfile } from '../utils/config.js';
5
5
  import ConfigStore from '../utils/config-store.js';
6
- import ora from 'ora';
6
+ import ora from '../utils/spinner.js';
7
7
  import { api } from '../services/api-service.js';
8
8
 
9
9
  export function registerConfigCommand(program: Command) {
@@ -1,7 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import Table from 'cli-table3';
4
- import ora from 'ora';
3
+ import { Table } from 'cmd-table';
4
+ import ora from '../utils/spinner.js';
5
5
  import enquirer from 'enquirer';
6
6
  import { api } from '../services/api-service.js';
7
7
  import { handleCommandError } from '../utils/error-handler.js';
@@ -96,11 +96,17 @@ export function registerDashboardCommand(program: Command) {
96
96
 
97
97
  if (issues.length > 0) {
98
98
  const table = new Table({
99
- head: [chalk.bold('Key'), chalk.bold('Type'), chalk.bold('Summary'), chalk.bold('Status'), chalk.bold('Priority')]
99
+ columns: [
100
+ { name: chalk.bold('Key') },
101
+ { name: chalk.bold('Type') },
102
+ { name: chalk.bold('Summary') },
103
+ { name: chalk.bold('Status') },
104
+ { name: chalk.bold('Priority') }
105
+ ]
100
106
  });
101
107
 
102
108
  issues.forEach((i: any) => {
103
- table.push([
109
+ table.addRow([
104
110
  chalk.cyan(i.key),
105
111
  i.fields.issuetype?.name || '',
106
112
  i.fields.summary ? (i.fields.summary.length > 40 ? i.fields.summary.substring(0, 37) + '...' : i.fields.summary) : '',
@@ -108,7 +114,7 @@ export function registerDashboardCommand(program: Command) {
108
114
  getPriorityColor(i.fields.priority?.name || '', i.fields.priority?.name || '')
109
115
  ]);
110
116
  });
111
- console.log(table.toString());
117
+ console.log(table.render());
112
118
  } else {
113
119
  console.log(chalk.green(' 🎉 No open issues — nice work!'));
114
120
  }
@@ -1,8 +1,8 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import Table from 'cli-table3';
3
+ import { Table } from 'cmd-table';
4
4
  import { api } from '../services/api-service.js';
5
- import ora from 'ora';
5
+ import ora from '../utils/spinner.js';
6
6
  import enquirer from 'enquirer';
7
7
  import { handleCommandError } from '../utils/error-handler.js';
8
8
  import { ConfigService } from '../services/config-service.js';
@@ -30,14 +30,17 @@ Examples:
30
30
  }
31
31
 
32
32
  const table = new Table({
33
- head: [chalk.bold('Name'), chalk.bold('JQL')]
33
+ columns: [
34
+ { name: chalk.bold('Name') },
35
+ { name: chalk.bold('JQL') }
36
+ ]
34
37
  });
35
38
 
36
39
  for (const [name, jql] of Object.entries(filters)) {
37
- table.push([chalk.cyan(name), jql as string] as any);
40
+ table.addRow([chalk.cyan(name), jql as string] as any);
38
41
  }
39
42
 
40
- console.log(table.toString());
43
+ console.log(table.render());
41
44
  });
42
45
 
43
46
  filterCmd
@@ -2,7 +2,7 @@ import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { execSync } from 'child_process';
4
4
  import { api } from '../services/api-service.js';
5
- import ora from 'ora';
5
+ import ora from '../utils/spinner.js';
6
6
  import enquirer from 'enquirer';
7
7
  import { validateIssueKey } from '../utils/validators.js';
8
8
  import { handleCommandError } from '../utils/error-handler.js';
@@ -1,7 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import { api } from '../services/api-service.js';
4
- import ora from 'ora';
4
+ import ora from '../utils/spinner.js';
5
5
  import fs from 'fs';
6
6
  import path from 'path';
7
7
  import { validateIssueKey } from '../utils/validators.js';
@@ -2,7 +2,7 @@ import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import enquirer from 'enquirer';
4
4
  import { api } from '../services/api-service.js';
5
- import ora from 'ora';
5
+ import ora from '../utils/spinner.js';
6
6
  import { exec } from 'child_process';
7
7
  import { promisify } from 'util';
8
8
  import { validateIssueKey } from '../utils/validators.js';
@@ -1,8 +1,8 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import Table from 'cli-table3';
3
+ import { Table } from 'cmd-table';
4
4
  import { api } from '../services/api-service.js';
5
- import ora from 'ora';
5
+ import ora from '../utils/spinner.js';
6
6
  import enquirer from 'enquirer';
7
7
  import { textToADF } from '../utils/text-to-adf.js';
8
8
  import { validateIssueKey } from '../utils/validators.js';
@@ -67,11 +67,16 @@ Examples:
67
67
  console.log(chalk.bold(`\nWorklogs for ${chalk.cyan(issueKey)}:`));
68
68
 
69
69
  const table = new Table({
70
- head: [chalk.bold('Author'), chalk.bold('Time Spent'), chalk.bold('Date'), chalk.bold('Comment')]
70
+ columns: [
71
+ { name: chalk.bold('Author') },
72
+ { name: chalk.bold('Time Spent') },
73
+ { name: chalk.bold('Date') },
74
+ { name: chalk.bold('Comment') }
75
+ ]
71
76
  });
72
77
 
73
78
  data.worklogs.forEach((w: any) => {
74
- table.push([
79
+ table.addRow([
75
80
  w.author?.displayName || 'Unknown',
76
81
  w.timeSpent,
77
82
  w.started.split('T')[0],
@@ -79,7 +84,7 @@ Examples:
79
84
  ]);
80
85
  });
81
86
 
82
- console.log(table.toString());
87
+ console.log(table.render());
83
88
 
84
89
  } catch (e: any) {
85
90
  handleCommandError(spinner, e, 'Failed to list worklogs');
@@ -1,9 +1,9 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import Table from 'cli-table3';
3
+ import { Table } from 'cmd-table';
4
4
  import { api } from '../services/api-service.js';
5
5
  import { aiService } from '../services/ai-service.js';
6
- import ora from 'ora';
6
+ import ora from '../utils/spinner.js';
7
7
  import enquirer from 'enquirer';
8
8
  import { parseADF } from '../utils/adf-parser.js';
9
9
  import { textToADF } from '../utils/text-to-adf.js';
@@ -134,11 +134,18 @@ Examples:
134
134
  }
135
135
 
136
136
  const table = new Table({
137
- head: [chalk.bold('Key'), chalk.bold('Summary'), chalk.bold('Status'), chalk.bold('Assignee'), chalk.bold('Created'), chalk.bold('Updated')]
137
+ columns: [
138
+ { name: chalk.bold('Key') },
139
+ { name: chalk.bold('Summary') },
140
+ { name: chalk.bold('Status') },
141
+ { name: chalk.bold('Assignee') },
142
+ { name: chalk.bold('Created') },
143
+ { name: chalk.bold('Updated') }
144
+ ]
138
145
  });
139
146
 
140
147
  data.issues.forEach((i: any) => {
141
- table.push([
148
+ table.addRow([
142
149
  chalk.cyan(i.key),
143
150
  i.fields.summary ? (i.fields.summary.length > 50 ? i.fields.summary.substring(0, 47) + '...' : i.fields.summary) : '',
144
151
  i.fields.status ? i.fields.status.name : '',
@@ -148,7 +155,7 @@ Examples:
148
155
  ]);
149
156
  });
150
157
 
151
- console.log(table.toString());
158
+ console.log(table.render());
152
159
 
153
160
  } catch (e: any) {
154
161
  handleCommandError(spinner, e, 'Failed to list issues');
@@ -213,7 +220,7 @@ Examples:
213
220
  if (issue.fields.comment && issue.fields.comment.comments.length > 0) {
214
221
  console.log(chalk.bold('\nComments:'));
215
222
  issue.fields.comment.comments.forEach((c: any) => {
216
- console.log(chalk.cyan(c.author.displayName) + ': ' + c.body);
223
+ console.log(chalk.cyan(c.author.displayName) + ': ' + (parseADF(c.body) || ''));
217
224
  });
218
225
  }
219
226
  console.log('');
@@ -232,6 +239,11 @@ Examples:
232
239
  .option('-d, --description <text>', 'Issue description')
233
240
  .option('--priority <name>', 'Priority name (e.g., High, Medium, Low)')
234
241
  .option('-a, --assignee <id>', 'Assignee account ID (use "me" for self)')
242
+ .option('-l, --labels <list>', 'Labels (comma separated)')
243
+ .option('-c, --components <list>', 'Component IDs (comma separated)', (v: string, l: string[]) => l.concat([v]), [])
244
+ .option('--fix-versions <list>', 'Fix Version IDs (comma separated)', (v: string, l: string[]) => l.concat([v]), [])
245
+ .option('--due-date <date>', 'Due Date (YYYY-MM-DD)')
246
+ .option('--no-input', 'Disable interactive prompts for optional fields')
235
247
  .option('--custom <key=value>', 'Custom fields (key=value, repeatable)', (v: string, l: string[]) => l.concat([v]), [])
236
248
  .addHelpText('after', `
237
249
  Examples:
@@ -339,7 +351,7 @@ Examples:
339
351
 
340
352
  // ── Step 5: Priority ────────────────────────────────
341
353
  let priorityName = options.priority;
342
- if (!priorityName) {
354
+ if (!priorityName && !options.noInput) {
343
355
  const spinner = ora('Fetching priorities...').start();
344
356
  try {
345
357
  const priorities = await api.get('/priority');
@@ -366,73 +378,87 @@ Examples:
366
378
  }
367
379
 
368
380
  // ── Step 5.5: Components ────────────────────────────
369
- let componentIds: string[] = [];
370
- // Interactive only for now (TODO: add flags)
371
- const compSpinner = ora('Fetching components...').start();
372
- try {
373
- const components = await api.get(`/project/${projectKey}/components`);
374
- compSpinner.stop();
375
-
376
- if (Array.isArray(components) && components.length > 0) {
377
- const { selectedComponents } = await enquirer.prompt({
378
- type: 'multiselect',
379
- name: 'selectedComponents',
380
- message: 'Select Components (Space to select, Enter to confirm):',
381
- choices: components.map((c: any) => ({ name: c.id, message: c.name }))
382
- }) as any;
383
- componentIds = selectedComponents;
381
+ let componentIds: string[] = options.components || [];
382
+ // Interactive only if components not provided and input allowed
383
+ if (componentIds.length === 0 && !options.noInput) {
384
+ const compSpinner = ora('Fetching components...').start();
385
+ try {
386
+ const components = await api.get(`/project/${projectKey}/components`);
387
+ compSpinner.stop();
388
+
389
+ if (Array.isArray(components) && components.length > 0) {
390
+ const { selectedComponents } = await enquirer.prompt({
391
+ type: 'multiselect',
392
+ name: 'selectedComponents',
393
+ message: 'Select Components (Space to select, Enter to confirm):',
394
+ choices: components.map((c: any) => ({ name: c.id, message: c.name }))
395
+ }) as any;
396
+ componentIds = selectedComponents;
397
+ }
398
+ } catch {
399
+ compSpinner.stop();
384
400
  }
385
- } catch {
386
- compSpinner.stop();
387
401
  }
388
402
 
389
403
  // ── Step 5.6: Labels ────────────────────────────────
390
404
  let labels: string[] = [];
391
- const { inputLabels } = await enquirer.prompt({
392
- type: 'input',
393
- name: 'inputLabels',
394
- message: 'Labels (comma-separated, optional):'
395
- }) as any;
396
-
397
- if (inputLabels && inputLabels.trim().length > 0) {
398
- labels = inputLabels.split(',').map((l: string) => l.trim()).filter((l: string) => l.length > 0);
405
+ if (options.labels) {
406
+ labels = options.labels.split(',').map((l: string) => l.trim()).filter((l: string) => l.length > 0);
407
+ }
408
+
409
+ if (labels.length === 0 && !options.noInput) {
410
+ const { inputLabels } = await enquirer.prompt({
411
+ type: 'input',
412
+ name: 'inputLabels',
413
+ message: 'Labels (comma-separated, optional):'
414
+ }) as any;
415
+
416
+ if (inputLabels && inputLabels.trim().length > 0) {
417
+ labels = inputLabels.split(',').map((l: string) => l.trim()).filter((l: string) => l.length > 0);
418
+ }
399
419
  }
400
420
 
401
421
  // ── Step 5.7: Fix Versions ──────────────────────────
402
- let fixVersionIds: string[] = [];
403
- const verSpinner = ora('Fetching versions...').start();
404
- try {
405
- const versions = await api.get(`/project/${projectKey}/versions`);
406
- verSpinner.stop();
407
-
408
- // Filter unreleased versions usually
409
- const unreleased = versions.filter((v: any) => !v.released);
410
-
411
- if (Array.isArray(unreleased) && unreleased.length > 0) {
412
- const { selectedVersions } = await enquirer.prompt({
413
- type: 'multiselect',
414
- name: 'selectedVersions',
415
- message: 'Fix Versions:',
416
- choices: unreleased.map((v: any) => ({ name: v.id, message: v.name }))
417
- }) as any;
418
- fixVersionIds = selectedVersions;
422
+ let fixVersionIds: string[] = options.fixVersions || [];
423
+
424
+ if (fixVersionIds.length === 0 && !options.noInput) {
425
+ const verSpinner = ora('Fetching versions...').start();
426
+ try {
427
+ const versions = await api.get(`/project/${projectKey}/versions`);
428
+ verSpinner.stop();
429
+
430
+ // Filter unreleased versions usually
431
+ const unreleased = versions.filter((v: any) => !v.released);
432
+
433
+ if (Array.isArray(unreleased) && unreleased.length > 0) {
434
+ const { selectedVersions } = await enquirer.prompt({
435
+ type: 'multiselect',
436
+ name: 'selectedVersions',
437
+ message: 'Fix Versions:',
438
+ choices: unreleased.map((v: any) => ({ name: v.id, message: v.name }))
439
+ }) as any;
440
+ fixVersionIds = selectedVersions;
441
+ }
442
+ } catch {
443
+ verSpinner.stop();
419
444
  }
420
- } catch {
421
- verSpinner.stop();
422
445
  }
423
446
 
424
447
  // ── Step 5.8: Due Date ──────────────────────────────
425
- let duedate: string | null = null;
426
- const { inputDueDate } = await enquirer.prompt({
427
- type: 'input',
428
- name: 'inputDueDate',
429
- message: 'Due Date (YYYY-MM-DD, optional):',
430
- validate: (val: string) => {
431
- if (!val) return true;
432
- return /^\d{4}-\d{2}-\d{2}$/.test(val) || 'Format must be YYYY-MM-DD';
433
- }
434
- }) as any;
435
- if (inputDueDate) duedate = inputDueDate;
448
+ let duedate: string | null = options.dueDate || null;
449
+
450
+ if (!duedate && !options.noInput) {
451
+ const { inputDueDate } = await enquirer.prompt({
452
+ type: 'input',
453
+ name: 'inputDueDate',
454
+ message: 'Due Date (YYYY-MM-DD, optional):',
455
+ validate: (val: string) => {
456
+ if (!val) return true;
457
+ return /^\d{4}-\d{2}-\d{2}$/.test(val) || 'Format must be YYYY-MM-DD';
458
+ }
459
+ }) as any;
460
+ if (inputDueDate) duedate = inputDueDate;
461
+ }
436
462
 
437
463
  // ── Step 6: Assignee ────────────────────────────────
438
464
  let assigneeId = options.assignee;
@@ -510,25 +536,27 @@ Examples:
510
536
  }
511
537
 
512
538
  // ── Confirmation ────────────────────────────────────
513
- console.log(chalk.blue('\n── Issue Summary ──────────────────'));
514
- console.log(` Project: ${chalk.cyan(projectKey)}`);
515
- console.log(` Type: ${issueTypeName}`);
516
- console.log(` Summary: ${summary}`);
517
- console.log(` Description: ${description || chalk.grey('(none)')}`);
518
- console.log(` Priority: ${priorityName || chalk.grey('(default)')}`);
519
- console.log(` Assignee: ${assigneeId || chalk.grey('Unassigned')}`);
520
- console.log(chalk.blue('──────────────────────────────────\n'));
521
-
522
- const { confirmed } = await enquirer.prompt({
523
- type: 'confirm',
524
- name: 'confirmed',
525
- message: 'Create this issue?',
526
- initial: true
527
- }) as any;
528
-
529
- if (!confirmed) {
530
- console.log(chalk.yellow('Issue creation cancelled.'));
531
- return;
539
+ if (!options.noInput) {
540
+ console.log(chalk.blue('\n── Issue Summary ──────────────────'));
541
+ console.log(` Project: ${chalk.cyan(projectKey)}`);
542
+ console.log(` Type: ${issueTypeName}`);
543
+ console.log(` Summary: ${summary}`);
544
+ console.log(` Description: ${description || chalk.grey('(none)')}`);
545
+ console.log(` Priority: ${priorityName || chalk.grey('(default)')}`);
546
+ console.log(` Assignee: ${assigneeId || chalk.grey('Unassigned')}`);
547
+ console.log(chalk.blue('──────────────────────────────────\n'));
548
+
549
+ const { confirmed } = await enquirer.prompt({
550
+ type: 'confirm',
551
+ name: 'confirmed',
552
+ message: 'Create this issue?',
553
+ initial: true
554
+ }) as any;
555
+
556
+ if (!confirmed) {
557
+ console.log(chalk.yellow('Issue creation cancelled.'));
558
+ return;
559
+ }
532
560
  }
533
561
 
534
562
  // ── Build Request Body ──────────────────────────────
@@ -1012,17 +1040,22 @@ Examples:
1012
1040
  }
1013
1041
 
1014
1042
  const table = new Table({
1015
- head: [chalk.bold('Key'), chalk.bold('Summary'), chalk.bold('Status'), chalk.bold('Assignee')]
1043
+ columns: [
1044
+ { name: chalk.bold('Key') },
1045
+ { name: chalk.bold('Summary') },
1046
+ { name: chalk.bold('Status') },
1047
+ { name: chalk.bold('Assignee') }
1048
+ ]
1016
1049
  });
1017
1050
  data.issues.forEach((i: any) => {
1018
- table.push([
1051
+ table.addRow([
1019
1052
  chalk.cyan(i.key),
1020
1053
  i.fields.summary ? (i.fields.summary.length > 55 ? i.fields.summary.substring(0, 52) + '...' : i.fields.summary) : '',
1021
1054
  i.fields.status?.name || '',
1022
1055
  i.fields.assignee?.displayName || 'Unassigned'
1023
1056
  ]);
1024
1057
  });
1025
- console.log(table.toString());
1058
+ console.log(table.render());
1026
1059
  console.log(chalk.grey(`Found ${data.issues.length} result(s)`));
1027
1060
 
1028
1061
  } catch (e) {
@@ -1136,14 +1169,39 @@ Examples:
1136
1169
 
1137
1170
  const spinner = ora(`Fetching parent ${parentKey}...`).start();
1138
1171
  try {
1139
- const parent = await api.get(`/issue/${parentKey}?fields=project,summary`);
1172
+ const parent = await api.get(`/issue/${parentKey}?fields=project,summary,issuetype,id`);
1140
1173
  const projectKey = parent.fields.project.key;
1174
+
1175
+ if (parent.fields.issuetype.subtask) {
1176
+ spinner.fail(chalk.red(`Issue ${parentKey} is already a subtask. Cannot create a subtask of a subtask.`));
1177
+ return;
1178
+ }
1179
+
1180
+ if (parent.fields.issuetype.name === 'Epic') {
1181
+ spinner.fail(chalk.red(`Issue ${parentKey} is an Epic. Epics cannot have sub-tasks.`));
1182
+ console.log(chalk.yellow('Tip: To add work to an Epic, create a standard issue (Story, Task) and link it to the Epic.'));
1183
+ return;
1184
+ }
1185
+
1141
1186
  spinner.text = 'Fetching subtask types...';
1142
1187
 
1143
1188
  // Get valid subtask types for project
1144
- const meta = await api.get(`/issue/createmeta/${projectKey}/issuetypes`);
1145
- const allTypes = meta.issueTypes || meta.values || [];
1146
- const subtaskTypes = allTypes.filter((t: any) => t.subtask);
1189
+ let subtaskTypes: any[] = [];
1190
+ try {
1191
+ // Correct V3 endpoint for creation metadata
1192
+ const meta = await api.get(`/issue/createmeta?projectKeys=${projectKey}`);
1193
+ if (meta.projects && meta.projects.length > 0) {
1194
+ subtaskTypes = meta.projects[0].issuetypes.filter((t: any) => t.subtask);
1195
+ }
1196
+ } catch (err) {
1197
+ // Fallback to project fetch
1198
+ try {
1199
+ const proj = await api.get(`/project/${projectKey}`);
1200
+ subtaskTypes = (proj.issueTypes || []).filter((t: any) => t.subtask);
1201
+ } catch (e) {
1202
+ console.error(chalk.red('Failed to fetch project issue types.'));
1203
+ }
1204
+ }
1147
1205
  spinner.stop();
1148
1206
 
1149
1207
  if (subtaskTypes.length === 0) {
@@ -1183,13 +1241,15 @@ Examples:
1183
1241
  const issueBody: any = {
1184
1242
  fields: {
1185
1243
  project: { key: projectKey },
1186
- parent: { key: parentKey },
1244
+ parent: { id: parent.id }, // Use ID instead of Key
1187
1245
  issuetype: { id: subtaskTypeId },
1188
1246
  summary: summary
1189
1247
  }
1190
1248
  };
1191
1249
 
1192
1250
  if (priorityName) issueBody.fields.priority = { name: priorityName };
1251
+ // ... rest of assignee logic ...
1252
+
1193
1253
  if (assigneeId === 'me') {
1194
1254
  const me = await api.get('/myself');
1195
1255
  issueBody.fields.assignee = { accountId: me.accountId };
@@ -1,8 +1,8 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
- import Table from 'cli-table3';
3
+ import { Table } from 'cmd-table';
4
4
  import { api } from '../services/api-service.js';
5
- import ora from 'ora';
5
+ import ora from '../utils/spinner.js';
6
6
  import { handleCommandError } from '../utils/error-handler.js';
7
7
 
8
8
  export function registerProjectCommand(program: Command) {
@@ -37,11 +37,16 @@ Common Actions:
37
37
  }
38
38
 
39
39
  const table = new Table({
40
- head: [chalk.bold('Key'), chalk.bold('Name'), chalk.bold('Leader'), chalk.bold('Style')]
40
+ columns: [
41
+ { name: chalk.bold('Key') },
42
+ { name: chalk.bold('Name') },
43
+ { name: chalk.bold('Leader') },
44
+ { name: chalk.bold('Style') }
45
+ ]
41
46
  });
42
47
 
43
48
  data.values.forEach((p: any) => {
44
- table.push([
49
+ table.addRow([
45
50
  chalk.cyan(p.key),
46
51
  p.name,
47
52
  p.lead ? p.lead.displayName : 'N/A',
@@ -49,7 +54,7 @@ Common Actions:
49
54
  ]);
50
55
  });
51
56
 
52
- console.log(table.toString());
57
+ console.log(table.render());
53
58
  } catch (e: any) {
54
59
  handleCommandError(spinner, e, 'Failed to list projects');
55
60
  }