jiradc-cli 1.0.17 → 1.0.18-g2f6cdb4.1

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 (3) hide show
  1. package/README.md +43 -7
  2. package/dist/index.js +237 -59
  3. package/package.json +4 -4
package/README.md CHANGED
@@ -15,6 +15,12 @@ export JIRA_URL="https://jira.example.com" # Base URL of your Jira instance
15
15
  export JIRA_TOKEN="your-personal-access-token" # HTTP Access Token from Jira
16
16
  ```
17
17
 
18
+ ## Breaking changes in this release
19
+
20
+ - `issue transitions` now returns a bare JSON array of transitions (previously wrapped in `{ transitions: [...] }`). Each transition is reduced to `{ id, name, to, fields? }` — verbose fields like `to.iconUrl`, `to.statusCategory`, `isGlobal`, `isInitial`, `isConditional`, `hasScreen` are stripped.
21
+ - `component list` now requires `--project <key>` instead of a positional `<projectKey>` argument.
22
+ - `issue link-epic` is now variadic on the issue argument: `link-epic <issueKey...> --epic <epicKey>`. Single-issue calls keep working.
23
+
18
24
  ## Commands
19
25
 
20
26
  All commands output JSON. Add `--pretty` to pretty-print.
@@ -26,16 +32,17 @@ All commands output JSON. Add `--pretty` to pretty-print.
26
32
  | `jiradc issue get <key>` | Get issue details (`--fields` to select, `--expand` for changelog/transitions) |
27
33
  | `jiradc issue search <jql>` | Search issues with JQL |
28
34
  | `jiradc issue create` | Create an issue (`--project`, `--type`, `--summary`, `--description`, `--custom-fields`) |
29
- | `jiradc issue update <key>` | Update an issue |
35
+ | `jiradc issue update <key>` | Update an issue (`--summary`, `--description`, `--priority`, `--assignee`, `--labels`, `--components`, `--fix-versions`, or `--fields` JSON) |
30
36
  | `jiradc issue delete <key>` | Delete an issue |
31
- | `jiradc issue transition <key> <transitionId>` | Transition issue to a new status |
37
+ | `jiradc issue assign <key> <user>` | Assign issue (user can be a username, `me`, or `none` to unassign) |
38
+ | `jiradc issue transition <key>` | Transition issue to a new status (`--to` accepts ID or status name, `--comment` to add a note) |
32
39
  | `jiradc issue transitions <key>` | List available transitions |
33
40
  | `jiradc issue comment <key>` | Add a comment |
34
41
  | `jiradc issue comment-edit <key> <commentId>` | Edit a comment |
35
42
  | `jiradc issue link <key> <targetKey>` | Link two issues (`--type` link type name) |
36
43
  | `jiradc issue unlink <linkId>` | Remove a link |
37
44
  | `jiradc issue link-types` | List available link types |
38
- | `jiradc issue link-epic <epicKey> <issueKey>` | Link an issue to an epic |
45
+ | `jiradc issue link-epic <keys...>` | Link one or more issues to an epic (`--epic <epicKey>`) |
39
46
  | `jiradc issue worklog <key>` | Add a work log entry |
40
47
  | `jiradc issue get-worklog <key>` | Get work log entries |
41
48
  | `jiradc issue changelog <key>` | Get issue changelog |
@@ -60,7 +67,16 @@ All commands output JSON. Add `--pretty` to pretty-print.
60
67
  |---------|-------------|
61
68
  | `jiradc project list` | List projects |
62
69
  | `jiradc project versions <key>` | List versions for a project |
63
- | `jiradc project components <key>` | List components for a project |
70
+
71
+ ### component
72
+
73
+ | Command | Description |
74
+ |---------|-------------|
75
+ | `jiradc component list` | List components for a project (`--project <key>`) |
76
+ | `jiradc component get <id>` | Get a component by ID |
77
+ | `jiradc component create` | Create a component (`--project`, `--name`, `--description`, `--lead`) |
78
+ | `jiradc component update <id>` | Update a component |
79
+ | `jiradc component issue-count <id>` | Get the number of issues using a component |
64
80
 
65
81
  ### board
66
82
 
@@ -93,7 +109,7 @@ All commands output JSON. Add `--pretty` to pretty-print.
93
109
 
94
110
  ## Pagination
95
111
 
96
- Search and list commands accept `--max` to control page size (Jira DC caps at 50 for agile endpoints). Responses include `nextPage` — pass it back as `--start-at` to fetch the next page. When `nextPage` is `null`, there are no more results.
112
+ Search and list commands accept `--limit` to control page size (Jira DC caps at 50 for agile endpoints). Responses include `nextPage` — pass it back as `--start` to fetch the next page. When `nextPage` is `null`, there are no more results.
97
113
 
98
114
  ## Examples
99
115
 
@@ -113,9 +129,26 @@ jiradc issue create --project AI --type Task --summary "Implement feature X" --d
113
129
  # Create with custom fields
114
130
  jiradc issue create --project AI --type Story --summary "User login" --custom-fields '{"customfield_10100": "value"}'
115
131
 
116
- # Transition an issue (find transition ID first)
132
+ # Transition by ID or by status name
117
133
  jiradc issue transitions AI-123
118
- jiradc issue transition AI-123 31
134
+ jiradc issue transition AI-123 --to 31
135
+ jiradc issue transition AI-123 --to "In Review" --comment "Ready for review"
136
+
137
+ # Assign
138
+ jiradc issue assign AI-123 me
139
+ jiradc issue assign AI-123 jsmith
140
+ jiradc issue assign AI-123 none
141
+
142
+ # Update with shortcuts (instead of --fields JSON)
143
+ jiradc issue update AI-123 --summary "New title" --priority High
144
+ jiradc issue update AI-123 --assignee me
145
+ jiradc issue update AI-123 --labels backend,urgent # set mode
146
+ jiradc issue update AI-123 --labels +urgent,-backend # mutate mode
147
+ jiradc issue update AI-123 --components +Frontend
148
+ jiradc issue update AI-123 --fix-versions 1.0,2.0
149
+
150
+ # Link multiple issues to an epic in one call
151
+ jiradc issue link-epic AI-456 AI-457 AI-458 --epic AI-100
119
152
 
120
153
  # Add a comment
121
154
  jiradc issue comment AI-123 --body "Fixed in commit abc123"
@@ -140,4 +173,7 @@ jiradc issue clone AI-123
140
173
 
141
174
  # Get dev status (linked branches, PRs)
142
175
  jiradc issue dev-status AI-123
176
+
177
+ # List components for a project
178
+ jiradc component list --project AI
143
179
  ```
package/dist/index.js CHANGED
@@ -432,17 +432,33 @@ function transformIssueFields(fields) {
432
432
  };
433
433
  }
434
434
 
435
+ // src/utils/transformers/transition.ts
436
+ function transformTransition(t) {
437
+ const out = {
438
+ id: t.id,
439
+ name: t.name,
440
+ to: t.to.name
441
+ };
442
+ if (t.fields && Object.keys(t.fields).length > 0) {
443
+ out.fields = t.fields;
444
+ }
445
+ return out;
446
+ }
447
+ function transformTransitions(response) {
448
+ return response.transitions.map(transformTransition);
449
+ }
450
+
435
451
  // src/commands/board/issues.ts
436
452
  function issues(parent) {
437
- parent.command("issues").description("Get issues for a board").addArgument(new Argument("<id>", "Board ID").argParser(positiveInt)).option("--max <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start-at <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the board").addHelpText(
453
+ parent.command("issues").description("Get issues for a board").addArgument(new Argument("<id>", "Board ID").argParser(positiveInt)).option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the board").addHelpText(
438
454
  "after",
439
- '\nExamples:\n jiradc board issues 42\n jiradc board issues 42 --max 10\n jiradc board issues 42 --jql "status = Open" --fields summary,status\n jiradc board issues 42 --start-at 50 --max 25'
455
+ '\nExamples:\n jiradc board issues 42\n jiradc board issues 42 --limit 10\n jiradc board issues 42 --jql "status = Open" --fields summary,status\n jiradc board issues 42 --start 50 --limit 25'
440
456
  ).action(async (id, opts) => {
441
457
  const client = getClient();
442
458
  const result = await client.agile.getBoardIssues({
443
459
  boardId: id,
444
- startAt: opts.startAt,
445
- maxResults: opts.max,
460
+ startAt: opts.start,
461
+ maxResults: opts.limit,
446
462
  fields: opts.fields?.split(",").map((f) => f.trim()),
447
463
  jql: opts.jql
448
464
  });
@@ -454,13 +470,13 @@ function issues(parent) {
454
470
  import { Option } from "commander";
455
471
  var BOARD_TYPES = ["scrum", "kanban", "simple"];
456
472
  function list(parent) {
457
- parent.command("list").description("List agile boards").option("--max <number>", "Max results (1-1000)", intInRange(1, 1e3), 25).option("--project <key>", "Filter boards by project key or ID").addOption(new Option("--type <type>", "Board type filter").choices(BOARD_TYPES)).option("--name <name>", "Filter boards by name").addHelpText(
473
+ parent.command("list").description("List agile boards").option("--limit <number>", "Max results (1-1000)", intInRange(1, 1e3), 25).option("--project <key>", "Filter boards by project key or ID").addOption(new Option("--type <type>", "Board type filter").choices(BOARD_TYPES)).option("--name <name>", "Filter boards by name").addHelpText(
458
474
  "after",
459
- '\nExamples:\n jiradc board list\n jiradc board list --max 10\n jiradc board list --project PROJ\n jiradc board list --type scrum --name "Team Board"'
475
+ '\nExamples:\n jiradc board list\n jiradc board list --limit 10\n jiradc board list --project PROJ\n jiradc board list --type scrum --name "Team Board"'
460
476
  ).action(async (opts) => {
461
477
  const client = getClient();
462
478
  const result = await client.agile.getBoards({
463
- maxResults: opts.max,
479
+ maxResults: opts.limit,
464
480
  projectKeyOrId: opts.project,
465
481
  type: opts.type,
466
482
  name: opts.name
@@ -477,7 +493,7 @@ function registerBoardCommands(program2) {
477
493
  Examples:
478
494
  $ jiradc board list
479
495
  $ jiradc board list --type scrum --project PROJ
480
- $ jiradc board issues 42 --max 20
496
+ $ jiradc board issues 42 --limit 20
481
497
  `
482
498
  );
483
499
  list(board);
@@ -550,9 +566,9 @@ function issueCount(parent) {
550
566
 
551
567
  // src/commands/component/list.ts
552
568
  function list2(parent) {
553
- parent.command("list <projectKey>").description("List all components for a project").addHelpText("after", "\nExamples:\n jiradc component list AI").action(async (projectKey) => {
569
+ parent.command("list").description("List all components for a project").requiredOption("--project <key>", "Project key (e.g., AI)").addHelpText("after", "\nExamples:\n jiradc component list --project AI").action(async (opts) => {
554
570
  const client = getClient();
555
- const result = await client.components.list({ projectKeyOrId: projectKey });
571
+ const result = await client.components.list({ projectKeyOrId: opts.project });
556
572
  output(result.map(transformComponent));
557
573
  });
558
574
  }
@@ -597,7 +613,7 @@ function registerComponentCommands(program2) {
597
613
  "after",
598
614
  `
599
615
  Examples:
600
- $ jiradc component list AI
616
+ $ jiradc component list --project AI
601
617
  $ jiradc component get 11289
602
618
  $ jiradc component create --project AI --name Backend
603
619
  $ jiradc component update 11289 --name Backend
@@ -615,15 +631,15 @@ Examples:
615
631
 
616
632
  // src/commands/field/options.ts
617
633
  function options(parent) {
618
- parent.command("options <id>").description("Get available options for a custom field").option("--query <text>", "Filter options by text").option("--max <number>", "Max results to return (1-1000)", intInRange(1, 1e3), 25).option("--page <number>", "Page number (1-indexed)", positiveInt).addHelpText(
634
+ parent.command("options <id>").description("Get available options for a custom field").option("--query <text>", "Filter options by text").option("--limit <number>", "Max results to return (1-1000)", intInRange(1, 1e3), 25).option("--page <number>", "Page number (1-indexed)", positiveInt).addHelpText(
619
635
  "after",
620
- '\nExamples:\n jiradc field options 10001\n jiradc field options 10001 --query "High"\n jiradc field options 10001 --max 20 --page 2'
636
+ '\nExamples:\n jiradc field options 10001\n jiradc field options 10001 --query "High"\n jiradc field options 10001 --limit 20 --page 2'
621
637
  ).action(async (id, opts) => {
622
638
  const client = getClient();
623
639
  const result = await client.fields.getFieldOptions({
624
640
  fieldId: id,
625
641
  query: opts.query,
626
- maxResults: opts.max,
642
+ maxResults: opts.limit,
627
643
  page: opts.page
628
644
  });
629
645
  output(transformPaged({ ...result, startAt: result.startAt ?? 0 }, transformCustomFieldOption));
@@ -657,6 +673,42 @@ Examples:
657
673
  options(field);
658
674
  }
659
675
 
676
+ // src/utils/resolve-user.ts
677
+ var cachedMe;
678
+ async function resolveUserToken(token) {
679
+ if (token === "none") return null;
680
+ if (token === "me") {
681
+ if (cachedMe !== void 0) return cachedMe;
682
+ const me2 = await getClient().users.getMyself();
683
+ if (!me2.name) {
684
+ throw new Error('Could not resolve "me": authenticated user has no name field');
685
+ }
686
+ cachedMe = me2.name;
687
+ return cachedMe;
688
+ }
689
+ return token;
690
+ }
691
+
692
+ // src/commands/issue/assign.ts
693
+ function assign(parent) {
694
+ parent.command("assign <key> <user>").description('Assign an issue. <user> is a username, "me", or "none" to unassign.').addHelpText(
695
+ "after",
696
+ `
697
+ Examples:
698
+ jiradc issue assign PROJ-123 jsmith
699
+ jiradc issue assign PROJ-123 me
700
+ jiradc issue assign PROJ-123 none`
701
+ ).action(async (key, user) => {
702
+ const resolved = await resolveUserToken(user);
703
+ const client = getClient();
704
+ await client.issues.update({
705
+ issueKeyOrId: key,
706
+ fields: { assignee: resolved === null ? null : { name: resolved } }
707
+ });
708
+ output({ assigned: true, issue: transformIssueRef(key), assignee: resolved });
709
+ });
710
+ }
711
+
660
712
  // src/commands/issue/attachment/delete.ts
661
713
  function deleteAttachment(parent) {
662
714
  parent.command("delete").description("Delete an attachment by ID").requiredOption("--id <attachmentId>", "Attachment ID to delete").addHelpText("after", "\nExamples:\n jiradc issue attachment delete --id 12345").action(async (opts) => {
@@ -842,16 +894,16 @@ function attachments(parent) {
842
894
 
843
895
  // src/commands/issue/batch-changelog.ts
844
896
  function batchChangelog(parent) {
845
- parent.command("batch-changelog <keys>").description("Get changelogs for multiple issues at once").option("--max <number>", "Max changelog entries per issue (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).addHelpText(
897
+ parent.command("batch-changelog <keys>").description("Get changelogs for multiple issues at once").option("--limit <number>", "Max changelog entries per issue (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).addHelpText(
846
898
  "after",
847
- "\nExamples:\n jiradc issue batch-changelog PROJ-1,PROJ-2,PROJ-3\n jiradc issue batch-changelog PROJ-123,PROJ-124 --max 10"
899
+ "\nExamples:\n jiradc issue batch-changelog PROJ-1,PROJ-2,PROJ-3\n jiradc issue batch-changelog PROJ-123,PROJ-124 --limit 10"
848
900
  ).action(async (keys, opts) => {
849
901
  const client = getClient();
850
902
  const keyList = keys.split(",").map((k) => k.trim());
851
903
  const entries = await Promise.all(
852
904
  keyList.map(async (key) => {
853
905
  try {
854
- const data = await client.issues.getChangelog({ issueKeyOrId: key, maxResults: opts.max });
906
+ const data = await client.issues.getChangelog({ issueKeyOrId: key, maxResults: opts.limit });
855
907
  return [key, data];
856
908
  } catch (error) {
857
909
  return [key, { error: error instanceof Error ? error.message : "Unknown error" }];
@@ -887,9 +939,12 @@ Examples:
887
939
 
888
940
  // src/commands/issue/changelog.ts
889
941
  function changelog(parent) {
890
- parent.command("changelog <key>").description("Get changelog for an issue").option("--max <number>", "Max changelog entries (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).addHelpText("after", "\nExamples:\n jiradc issue changelog PROJ-123\n jiradc issue changelog PROJ-123 --max 10").action(async (key, opts) => {
942
+ parent.command("changelog <key>").description("Get changelog for an issue").option("--limit <number>", "Max changelog entries (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).addHelpText(
943
+ "after",
944
+ "\nExamples:\n jiradc issue changelog PROJ-123\n jiradc issue changelog PROJ-123 --limit 10"
945
+ ).action(async (key, opts) => {
891
946
  const client = getClient();
892
- const result = await client.issues.getChangelog({ issueKeyOrId: key, maxResults: opts.max });
947
+ const result = await client.issues.getChangelog({ issueKeyOrId: key, maxResults: opts.limit });
893
948
  output(result);
894
949
  });
895
950
  }
@@ -1133,15 +1188,15 @@ function devStatus(parent) {
1133
1188
 
1134
1189
  // src/commands/issue/get-worklog.ts
1135
1190
  function getWorklog(parent) {
1136
- parent.command("get-worklog <key>").description("Get worklogs for an issue").option("--start-at <number>", "Starting index for pagination", nonNegativeInt).option("--max <number>", "Max results per page (1-1000)", intInRange(1, 1e3), 25).addHelpText(
1191
+ parent.command("get-worklog <key>").description("Get worklogs for an issue").option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--limit <number>", "Max results per page (1-1000)", intInRange(1, 1e3), 25).addHelpText(
1137
1192
  "after",
1138
- "\nExamples:\n jiradc issue get-worklog PROJ-123\n jiradc issue get-worklog PROJ-123 --max 10\n jiradc issue get-worklog PROJ-123 --start-at 10 --max 5"
1193
+ "\nExamples:\n jiradc issue get-worklog PROJ-123\n jiradc issue get-worklog PROJ-123 --limit 10\n jiradc issue get-worklog PROJ-123 --start 10 --limit 5"
1139
1194
  ).action(async (key, opts) => {
1140
1195
  const client = getClient();
1141
1196
  const result = await client.issues.getWorklogs({
1142
1197
  issueKeyOrId: key,
1143
- startAt: opts.startAt,
1144
- maxResults: opts.max
1198
+ startAt: opts.start,
1199
+ maxResults: opts.limit
1145
1200
  });
1146
1201
  output(transformPaged(result, transformWorklog));
1147
1202
  });
@@ -1182,17 +1237,37 @@ function get2(parent) {
1182
1237
 
1183
1238
  // src/commands/issue/link-epic.ts
1184
1239
  function linkEpic(parent) {
1185
- parent.command("link-epic <key>").description("Link an issue to an epic").requiredOption("--epic <epicKey>", "Epic issue key").addHelpText("after", "\nExamples:\n jiradc issue link-epic PROJ-456 --epic PROJ-123").action(async (key, opts) => {
1240
+ parent.command("link-epic <keys...>").description("Link one or more issues to an epic").requiredOption("--epic <epicKey>", "Epic issue key").addHelpText(
1241
+ "after",
1242
+ `
1243
+ Examples:
1244
+ jiradc issue link-epic PROJ-456 --epic PROJ-123
1245
+ jiradc issue link-epic PROJ-456 PROJ-457 PROJ-458 --epic PROJ-123`
1246
+ ).action(async (keys, opts) => {
1186
1247
  const client = getClient();
1187
- await client.issues.update({
1188
- issueKeyOrId: key,
1189
- fields: { customfield_10100: opts.epic }
1248
+ const results = await Promise.allSettled(
1249
+ keys.map(
1250
+ (key) => client.issues.update({ issueKeyOrId: key, fields: { customfield_10100: opts.epic } }).then(() => key)
1251
+ )
1252
+ );
1253
+ const linked = [];
1254
+ const failed = [];
1255
+ results.forEach((r, i) => {
1256
+ if (r.status === "fulfilled") {
1257
+ linked.push({ key: r.value });
1258
+ } else {
1259
+ const message = r.reason instanceof Error ? r.reason.message : String(r.reason);
1260
+ failed.push({ key: keys[i], error: message });
1261
+ }
1190
1262
  });
1191
1263
  output({
1192
- linked: true,
1193
- issue: transformIssueRef(key),
1194
- epic: transformIssueRef(opts.epic)
1264
+ epic: transformIssueRef(opts.epic),
1265
+ linked,
1266
+ ...failed.length > 0 && { failed }
1195
1267
  });
1268
+ if (failed.length > 0) {
1269
+ process.exitCode = 1;
1270
+ }
1196
1271
  });
1197
1272
  }
1198
1273
 
@@ -1229,16 +1304,16 @@ function link(parent) {
1229
1304
 
1230
1305
  // src/commands/issue/search.ts
1231
1306
  function search2(parent) {
1232
- parent.command("search <jql>").description("Search issues using JQL").option("--max <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start-at <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults").addHelpText(
1307
+ parent.command("search <jql>").description("Search issues using JQL").option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults").addHelpText(
1233
1308
  "after",
1234
- '\nExamples:\n jiradc issue search "project = PROJ AND status = Open"\n jiradc issue search "assignee = currentUser()" --max 10 --fields summary,status\n jiradc issue search "project = PROJ" --start-at 50 --max 50'
1309
+ '\nExamples:\n jiradc issue search "project = PROJ AND status = Open"\n jiradc issue search "assignee = currentUser()" --limit 10 --fields summary,status\n jiradc issue search "project = PROJ" --start 50 --limit 50'
1235
1310
  ).action(async (jql, opts) => {
1236
1311
  const client = getClient();
1237
1312
  const fields = opts.allFields ? void 0 : opts.fields?.split(",").map((f) => f.trim()) ?? DEFAULT_FIELDS;
1238
1313
  const result = await client.issues.search({
1239
1314
  jql,
1240
- startAt: opts.startAt,
1241
- maxResults: opts.max,
1315
+ startAt: opts.start,
1316
+ maxResults: opts.limit,
1242
1317
  fields
1243
1318
  });
1244
1319
  output(transformPaged(result, transformIssue));
@@ -1247,12 +1322,37 @@ function search2(parent) {
1247
1322
 
1248
1323
  // src/commands/issue/transition.ts
1249
1324
  function transition(parent) {
1250
- parent.command("transition <key>").description("Transition issue to a new status").requiredOption("--to <id>", 'Transition ID (get from "jiradc issue transitions")').option("--comment <text>", "Comment to add during transition").addHelpText(
1325
+ parent.command("transition <key>").description("Transition issue to a new status").requiredOption("--to <idOrName>", "Transition ID, or status name (case-insensitive)").option("--comment <text>", "Comment to add during transition").addHelpText(
1251
1326
  "after",
1252
- '\nExamples:\n jiradc issue transition PROJ-123 --to 31\n jiradc issue transition PROJ-123 --to 31 --comment "Moving to review"'
1327
+ `
1328
+ Examples:
1329
+ jiradc issue transition PROJ-123 --to 31
1330
+ jiradc issue transition PROJ-123 --to "In Review"
1331
+ jiradc issue transition PROJ-123 --to Done --comment "Verified in staging"`
1253
1332
  ).action(async (key, opts) => {
1254
1333
  const client = getClient();
1255
- await client.issues.transition({ issueKeyOrId: key, transitionId: opts.to, comment: opts.comment });
1334
+ let transitionId;
1335
+ if (/^\d+$/.test(opts.to)) {
1336
+ transitionId = opts.to;
1337
+ } else {
1338
+ const raw = await client.issues.getTransitions({
1339
+ issueKeyOrId: key,
1340
+ expand: "transitions.fields"
1341
+ });
1342
+ const transitions2 = transformTransitions(raw);
1343
+ const target = opts.to.toLowerCase();
1344
+ const matches = transitions2.filter((t) => t.name.toLowerCase() === target);
1345
+ if (matches.length === 0) {
1346
+ const names = transitions2.map((t) => `"${t.name}"`).join(", ");
1347
+ throw new Error(`Transition "${opts.to}" not available from current status. Available: ${names || "(none)"}`);
1348
+ }
1349
+ if (matches.length > 1) {
1350
+ const ids = matches.map((t) => t.id).join(", ");
1351
+ throw new Error(`Multiple transitions named "${opts.to}". Use --to <id> with one of: ${ids}`);
1352
+ }
1353
+ transitionId = matches[0].id;
1354
+ }
1355
+ await client.issues.transition({ issueKeyOrId: key, transitionId, comment: opts.comment });
1256
1356
  output({ transitioned: true, issue: transformIssueRef(key) });
1257
1357
  });
1258
1358
  }
@@ -1261,8 +1361,11 @@ function transition(parent) {
1261
1361
  function transitions(parent) {
1262
1362
  parent.command("transitions <key>").description("Get available transitions for an issue").addHelpText("after", "\nExamples:\n jiradc issue transitions PROJ-123").action(async (key) => {
1263
1363
  const client = getClient();
1264
- const result = await client.issues.getTransitions({ issueKeyOrId: key });
1265
- output(result);
1364
+ const result = await client.issues.getTransitions({
1365
+ issueKeyOrId: key,
1366
+ expand: "transitions.fields"
1367
+ });
1368
+ output(transformTransitions(result));
1266
1369
  });
1267
1370
  }
1268
1371
 
@@ -1275,24 +1378,98 @@ function unlink2(parent) {
1275
1378
  });
1276
1379
  }
1277
1380
 
1381
+ // src/utils/multi-value.ts
1382
+ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
1383
+ function parseMultiValue(flagName, raw) {
1384
+ const items = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
1385
+ if (items.length === 0) {
1386
+ throw new InvalidArgumentError2(`${flagName} cannot be empty`);
1387
+ }
1388
+ const prefixed = items.filter((s) => s.startsWith("+") || s.startsWith("-"));
1389
+ const bare = items.filter((s) => !s.startsWith("+") && !s.startsWith("-"));
1390
+ if (prefixed.length > 0 && bare.length > 0) {
1391
+ throw new InvalidArgumentError2(
1392
+ `${flagName} mixes set and mutate syntax. Either all values have +/- prefix, or none do.`
1393
+ );
1394
+ }
1395
+ if (bare.length > 0) {
1396
+ return { mode: "set", values: bare };
1397
+ }
1398
+ const adds = prefixed.filter((s) => s.startsWith("+")).map((s) => s.slice(1));
1399
+ const removes = prefixed.filter((s) => s.startsWith("-")).map((s) => s.slice(1));
1400
+ return { mode: "mutate", adds, removes };
1401
+ }
1402
+
1278
1403
  // src/commands/issue/update.ts
1404
+ function buildUpdateOps(parsed, wrap) {
1405
+ if (parsed.mode !== "mutate") return void 0;
1406
+ return [...parsed.adds.map((v) => ({ add: wrap(v) })), ...parsed.removes.map((v) => ({ remove: wrap(v) }))];
1407
+ }
1408
+ function buildSetValue(parsed, wrap) {
1409
+ if (parsed.mode !== "set") return void 0;
1410
+ return parsed.values.map(wrap);
1411
+ }
1279
1412
  function update2(parent) {
1280
- parent.command("update <key>").description("Update issue fields").option("--fields <json>", "JSON string of fields to update").option("--notify-users", "Notify users about the update (default: true)").option("--attachments <paths>", "Comma-separated local file paths to attach").addHelpText(
1413
+ parent.command("update <key>").description("Update issue fields").option("--fields <json>", "JSON string of fields to update (advanced; merges with shortcuts, wins on conflict)").option("--notify-users", "Notify users about the update (default: true)").option("--attachments <paths>", "Comma-separated local file paths to attach").option("--summary <text>", "Set the issue summary").option("--description <text>", "Set the issue description (wiki markup)").option("--priority <name>", "Set the priority by name (e.g. High)").option("--assignee <user>", 'Set the assignee. Username, "me", or "none" to unassign.').option("--labels <list>", 'Set labels ("a,b,c") or mutate ("+add,-remove")').option("--components <list>", 'Set components ("a,b") or mutate ("+add,-remove")').option("--fix-versions <list>", 'Set fix versions ("1.0,2.0") or mutate ("+1.0,-0.9")').addHelpText(
1281
1414
  "after",
1282
1415
  `
1283
1416
  Examples:
1284
- jiradc issue update PROJ-123 --fields '{"summary": "New title"}'
1285
- jiradc issue update PROJ-123 --fields '{"assignee": {"name": "jsmith"}, "priority": {"name": "High"}}'
1286
- jiradc issue update PROJ-123 --attachments /path/to/file.pdf,/path/to/image.png
1287
- jiradc issue update PROJ-123 --fields '{"summary": "With attachment"}' --attachments ./report.pdf`
1417
+ jiradc issue update PROJ-123 --summary "New title"
1418
+ jiradc issue update PROJ-123 --priority High --assignee me
1419
+ jiradc issue update PROJ-123 --labels backend,urgent
1420
+ jiradc issue update PROJ-123 --labels +urgent,-backend
1421
+ jiradc issue update PROJ-123 --components +Frontend
1422
+ jiradc issue update PROJ-123 --fields '{"customfield_10100": "EPIC-1"}' --priority High
1423
+ jiradc issue update PROJ-123 --attachments /path/to/file.pdf,/path/to/image.png`
1288
1424
  ).action(async (key, opts) => {
1289
- if (!opts.fields && !opts.attachments) {
1290
- throw new Error("Provide --fields and/or --attachments");
1425
+ const hasShortcut = opts.summary !== void 0 || opts.description !== void 0 || opts.priority !== void 0 || opts.assignee !== void 0 || opts.labels !== void 0 || opts.components !== void 0 || opts.fixVersions !== void 0;
1426
+ if (!opts.fields && !opts.attachments && !hasShortcut) {
1427
+ throw new Error(
1428
+ "Provide at least one of --fields, --attachments, or a shortcut flag (--summary, --priority, --labels, ...)"
1429
+ );
1430
+ }
1431
+ const fields = {};
1432
+ const updateOps = {};
1433
+ if (opts.summary !== void 0) fields.summary = opts.summary;
1434
+ if (opts.description !== void 0) fields.description = opts.description;
1435
+ if (opts.priority !== void 0) fields.priority = { name: opts.priority };
1436
+ if (opts.assignee !== void 0) {
1437
+ const resolved = await resolveUserToken(opts.assignee);
1438
+ fields.assignee = resolved === null ? null : { name: resolved };
1439
+ }
1440
+ if (opts.labels !== void 0) {
1441
+ const parsed = parseMultiValue("--labels", opts.labels);
1442
+ const setVal = buildSetValue(parsed, (v) => v);
1443
+ const ops = buildUpdateOps(parsed, (v) => v);
1444
+ if (setVal) fields.labels = setVal;
1445
+ if (ops) updateOps.labels = ops;
1446
+ }
1447
+ if (opts.components !== void 0) {
1448
+ const parsed = parseMultiValue("--components", opts.components);
1449
+ const setVal = buildSetValue(parsed, (v) => ({ name: v }));
1450
+ const ops = buildUpdateOps(parsed, (v) => ({ name: v }));
1451
+ if (setVal) fields.components = setVal;
1452
+ if (ops) updateOps.components = ops;
1453
+ }
1454
+ if (opts.fixVersions !== void 0) {
1455
+ const parsed = parseMultiValue("--fix-versions", opts.fixVersions);
1456
+ const setVal = buildSetValue(parsed, (v) => ({ name: v }));
1457
+ const ops = buildUpdateOps(parsed, (v) => ({ name: v }));
1458
+ if (setVal) fields.fixVersions = setVal;
1459
+ if (ops) updateOps.fixVersions = ops;
1291
1460
  }
1292
- const client = getClient();
1293
1461
  if (opts.fields) {
1294
- const parsed = JSON.parse(opts.fields);
1295
- await client.issues.update({ issueKeyOrId: key, fields: parsed, notifyUsers: opts.notifyUsers });
1462
+ const parsedFields = JSON.parse(opts.fields);
1463
+ Object.assign(fields, parsedFields);
1464
+ }
1465
+ const client = getClient();
1466
+ if (Object.keys(fields).length > 0 || Object.keys(updateOps).length > 0) {
1467
+ await client.issues.update({
1468
+ issueKeyOrId: key,
1469
+ fields: Object.keys(fields).length > 0 ? fields : void 0,
1470
+ update: Object.keys(updateOps).length > 0 ? updateOps : void 0,
1471
+ notifyUsers: opts.notifyUsers
1472
+ });
1296
1473
  }
1297
1474
  const uploaded = [];
1298
1475
  if (opts.attachments) {
@@ -1336,7 +1513,7 @@ function registerIssueCommands(program2) {
1336
1513
  `
1337
1514
  Examples:
1338
1515
  $ jiradc issue get PROJ-123
1339
- $ jiradc issue search "project = PROJ AND status = Open" --max 10
1516
+ $ jiradc issue search "project = PROJ AND status = Open" --limit 10
1340
1517
  $ jiradc issue create --project PROJ --type Task --summary "Fix the bug" --priority High
1341
1518
  $ jiradc issue update PROJ-123 --fields '{"summary": "New title"}'
1342
1519
  $ jiradc issue transition PROJ-123 --to 11
@@ -1350,6 +1527,7 @@ Examples:
1350
1527
  deleteIssue(issue);
1351
1528
  transition(issue);
1352
1529
  transitions(issue);
1530
+ assign(issue);
1353
1531
  comment(issue);
1354
1532
  commentEdit(issue);
1355
1533
  worklog(issue);
@@ -1426,15 +1604,15 @@ function create3(parent) {
1426
1604
  // src/commands/sprint/issues.ts
1427
1605
  import { Argument as Argument2 } from "commander";
1428
1606
  function issues2(parent) {
1429
- parent.command("issues").description("Get issues in a sprint").addArgument(new Argument2("<id>", "Sprint ID").argParser(positiveInt)).option("--max <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start-at <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the sprint").addHelpText(
1607
+ parent.command("issues").description("Get issues in a sprint").addArgument(new Argument2("<id>", "Sprint ID").argParser(positiveInt)).option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the sprint").addHelpText(
1430
1608
  "after",
1431
- '\nExamples:\n jiradc sprint issues 100\n jiradc sprint issues 100 --max 20\n jiradc sprint issues 100 --jql "status = Done" --fields summary,status\n jiradc sprint issues 100 --start-at 50 --max 25'
1609
+ '\nExamples:\n jiradc sprint issues 100\n jiradc sprint issues 100 --limit 20\n jiradc sprint issues 100 --jql "status = Done" --fields summary,status\n jiradc sprint issues 100 --start 50 --limit 25'
1432
1610
  ).action(async (id, opts) => {
1433
1611
  const client = getClient();
1434
1612
  const result = await client.agile.getSprintIssues({
1435
1613
  sprintId: id,
1436
- startAt: opts.startAt,
1437
- maxResults: opts.max,
1614
+ startAt: opts.start,
1615
+ maxResults: opts.limit,
1438
1616
  fields: opts.fields?.split(",").map((f) => f.trim()),
1439
1617
  jql: opts.jql
1440
1618
  });
@@ -1490,7 +1668,7 @@ function registerSprintCommands(program2) {
1490
1668
  Examples:
1491
1669
  $ jiradc sprint list --board 42
1492
1670
  $ jiradc sprint list --board 42 --state active
1493
- $ jiradc sprint issues 123 --max 20
1671
+ $ jiradc sprint issues 123 --limit 20
1494
1672
  $ jiradc sprint create --board 42 --name "Sprint 10" --start-date 2026-04-01 --end-date 2026-04-14
1495
1673
  `
1496
1674
  );
@@ -1625,15 +1803,15 @@ function me(parent) {
1625
1803
 
1626
1804
  // src/commands/user/search.ts
1627
1805
  function search3(parent) {
1628
- parent.command("search <query>").description("Search users by partial username, display name or email").option("--max <n>", "Maximum results (1-50)", intInRange(1, 50), 25).option("--start <n>", "Starting index (pagination offset)", nonNegativeInt, 0).option("--include-inactive", "Include inactive users in results", false).addHelpText(
1806
+ parent.command("search <query>").description("Search users by partial username, display name or email").option("--limit <n>", "Maximum results (1-50)", intInRange(1, 50), 25).option("--start <n>", "Starting index (pagination offset)", nonNegativeInt, 0).option("--include-inactive", "Include inactive users in results", false).addHelpText(
1629
1807
  "after",
1630
- '\nExamples:\n jiradc user search Drago\n jiradc user search "John Smith"\n jiradc user search dragomir@first.bet --max 5'
1808
+ '\nExamples:\n jiradc user search Drago\n jiradc user search "John Smith"\n jiradc user search dragomir@first.bet --limit 5'
1631
1809
  ).action(async (query, opts) => {
1632
1810
  const client = getClient();
1633
1811
  const result = await client.users.searchUsers({
1634
1812
  username: query,
1635
1813
  startAt: opts.start,
1636
- maxResults: opts.max,
1814
+ maxResults: opts.limit,
1637
1815
  includeActive: true,
1638
1816
  includeInactive: opts.includeInactive
1639
1817
  });
@@ -1688,7 +1866,7 @@ ${styleText("bold", "Environment:")}
1688
1866
 
1689
1867
  ${styleText("bold", "Examples:")}
1690
1868
  ${DIM}$${RESET} jiradc user me
1691
- ${DIM}$${RESET} jiradc issue search "project = PROJ AND status = Open" --max 10
1869
+ ${DIM}$${RESET} jiradc issue search "project = PROJ AND status = Open" --limit 10
1692
1870
  ${DIM}$${RESET} jiradc issue get PROJ-123 --fields summary,status
1693
1871
  ${DIM}$${RESET} jiradc issue create --project PROJ --type Task --summary "Fix the bug"
1694
1872
  ${DIM}$${RESET} jiradc issue transition PROJ-123 --to 11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jiradc-cli",
3
- "version": "1.0.17",
3
+ "version": "1.0.18-g2f6cdb4.1",
4
4
  "publish": true,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,7 +12,7 @@
12
12
  ],
13
13
  "dependencies": {
14
14
  "commander": "^13.1.0",
15
- "jira-data-center-client": "1.0.35"
15
+ "jira-data-center-client": "1.0.35-g2f6cdb4.1"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "24.10.4",
@@ -22,8 +22,8 @@
22
22
  "tsx": "^4.19.2",
23
23
  "typescript": "^5.7.2",
24
24
  "vitest": "^4.0.16",
25
- "config-typescript": "0.0.0",
26
- "config-eslint": "0.0.0"
25
+ "config-eslint": "0.0.0",
26
+ "config-typescript": "0.0.0"
27
27
  },
28
28
  "engines": {
29
29
  "node": ">=22.0.0"