jiradc-cli 1.0.32 → 1.0.34

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 +65 -59
  2. package/dist/index.js +491 -134
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -17,6 +17,11 @@ export JIRA_TOKEN="your-personal-access-token" # HTTP Access Token from Jira
17
17
 
18
18
  ## Breaking changes in this release
19
19
 
20
+ - **`field options` is removed.** It called a Jira 9 endpoint and returned 404 for every field on Jira 8.x. Jira scopes a custom field's options to a context, so there is no context-free replacement — use `issue createmeta --project <key> --type <name>` (create screen), `issue editmeta <key>` (edit screen), or `issue transitions <key>` (transition screen).
21
+ - **`issue transitions` no longer reports `required`** on screen fields. Jira reports field-configuration requiredness there, which cannot see the workflow validator that actually gates the transition, so it returned `false` for fields that then rejected the transition. Supply everything in a transition's `fields` block. (`required` from `createmeta`/`editmeta` reflects field configuration and is reliable.)
22
+ - **`issue get` / `issue search` return issue-picker custom fields as a bare key string** (or an array of keys) instead of the whole nested issue. `fields.customfield_NNNNN.key` becomes `fields.customfield_NNNNN`.
23
+ - **`issue create --custom-fields` is now `--fields`**, matching `issue update`. The old name still works as a deprecated alias.
24
+ - **Argument mistakes now exit 2/3/6** (`usage`/`not_found`/`auth`) instead of 1.
20
25
  - `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
26
  - `component list` now requires `--project <key>` instead of a positional `<projectKey>` argument.
22
27
  - `issue link-epic` is now variadic on the issue argument: `link-epic <issueKey...> --epic <epicKey>`. Single-issue calls keep working.
@@ -27,88 +32,89 @@ All commands output JSON. Add `--pretty` to pretty-print.
27
32
 
28
33
  ### issue
29
34
 
30
- | Command | Description |
31
- |---------|-------------|
32
- | `jiradc issue get <key>` | Get issue details (`--fields` to select, `--expand` for changelog/transitions) |
33
- | `jiradc issue search <jql>` | Search issues with JQL |
34
- | `jiradc issue create` | Create an issue (`--project`, `--type`, `--summary`, `--description`, `--custom-fields`) |
35
- | `jiradc issue update <key>` | Update an issue (`--summary`, `--description`, `--priority`, `--assignee`, `--labels`, `--components`, `--fix-versions`, or `--fields` JSON) |
36
- | `jiradc issue delete <key>` | Delete an issue |
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) |
39
- | `jiradc issue transitions <key>` | List available transitions |
40
- | `jiradc issue comment add <key>` | Add a comment (`--body`) |
41
- | `jiradc issue comment edit <key>` | Edit a comment (`--id`, `--body`) |
42
- | `jiradc issue comment delete <key>` | Delete a comment (`--id`) |
43
- | `jiradc issue link <key> <targetKey>` | Link two issues (`--type` link type name) |
44
- | `jiradc issue unlink <linkId>` | Remove a link |
45
- | `jiradc issue link-types` | List available link types |
46
- | `jiradc issue link-epic <keys...>` | Link one or more issues to an epic (`--epic <epicKey>`) |
47
- | `jiradc issue worklog add <key>` | Add a work log entry (`--time`, `--comment`, `--started`) |
48
- | `jiradc issue worklog list <key>` | Get work log entries |
49
- | `jiradc issue worklog edit <key>` | Update a work log entry (`--id`, `--time`, `--comment`, `--started`, `--adjust-estimate`, `--new-estimate`) |
50
- | `jiradc issue worklog delete <key>` | Delete a work log entry (`--id`, `--adjust-estimate`, `--new-estimate`, `--increase-by`) |
51
- | `jiradc issue changelog <key>` | Get issue changelog |
52
- | `jiradc issue batch-changelog` | Get changelog for multiple issues (`--keys`) |
53
- | `jiradc issue clone <key>` | Clone an issue with subtasks |
54
- | `jiradc issue batch-create` | Create multiple issues from JSON |
55
- | `jiradc issue dev-status <key>` | Get development status (branches, PRs, commits) |
35
+ | Command | Description |
36
+ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
37
+ | `jiradc issue get <key>` | Get issue details (`--fields` to select, `--expand` for changelog/transitions) |
38
+ | `jiradc issue search <jql>` | Search issues with JQL |
39
+ | `jiradc issue create` | Create an issue (`--project`, `--type`, `--summary`, `--description`, `--custom-fields`) |
40
+ | `jiradc issue update <key>` | Update an issue (`--summary`, `--description`, `--priority`, `--assignee`, `--labels`, `--components`, `--fix-versions`, or `--fields` JSON) |
41
+ | `jiradc issue delete <key>` | Delete an issue |
42
+ | `jiradc issue assign <key> <user>` | Assign issue (user can be a username, `me`, or `none` to unassign) |
43
+ | `jiradc issue transition <key>` | Transition issue to a new status (`--to` accepts ID or status name, `--comment` to add a note) |
44
+ | `jiradc issue transitions <key>` | List available transitions (with any fields their screens require) |
45
+ | `jiradc issue createmeta` | Fields a new issue accepts, with allowed values (`--project`, `--type`) |
46
+ | `jiradc issue editmeta <key>` | Fields an existing issue's edit screen accepts, with allowed values |
47
+ | `jiradc issue comment add <key>` | Add a comment (`--body`) |
48
+ | `jiradc issue comment edit <key>` | Edit a comment (`--id`, `--body`) |
49
+ | `jiradc issue comment delete <key>` | Delete a comment (`--id`) |
50
+ | `jiradc issue link <key> <targetKey>` | Link two issues (`--type` link type name) |
51
+ | `jiradc issue unlink <linkId>` | Remove a link |
52
+ | `jiradc issue link-types` | List available link types |
53
+ | `jiradc issue link-epic <keys...>` | Link one or more issues to an epic (`--epic <epicKey>`) |
54
+ | `jiradc issue worklog add <key>` | Add a work log entry (`--time`, `--comment`, `--started`) |
55
+ | `jiradc issue worklog list <key>` | Get work log entries |
56
+ | `jiradc issue worklog edit <key>` | Update a work log entry (`--id`, `--time`, `--comment`, `--started`, `--adjust-estimate`, `--new-estimate`) |
57
+ | `jiradc issue worklog delete <key>` | Delete a work log entry (`--id`, `--adjust-estimate`, `--new-estimate`, `--increase-by`) |
58
+ | `jiradc issue changelog <key>` | Get issue changelog |
59
+ | `jiradc issue batch-changelog` | Get changelog for multiple issues (`--keys`) |
60
+ | `jiradc issue clone <key>` | Clone an issue with subtasks |
61
+ | `jiradc issue batch-create` | Create multiple issues from JSON |
62
+ | `jiradc issue dev-status <key>` | Get development status (branches, PRs, commits) |
56
63
 
57
64
  #### issue attachment
58
65
 
59
- | Command | Description |
60
- |---------|-------------|
61
- | `jiradc issue attachment list <key>` | List attachments |
66
+ | Command | Description |
67
+ | ------------------------------------------------------- | ------------------------------ |
68
+ | `jiradc issue attachment list <key>` | List attachments |
62
69
  | `jiradc issue attachment download <key> <attachmentId>` | Download a specific attachment |
63
- | `jiradc issue attachment download-all <key>` | Download all attachments |
64
- | `jiradc issue attachment upload <key> <files...>` | Upload files |
65
- | `jiradc issue attachment delete <attachmentId>` | Delete an attachment |
70
+ | `jiradc issue attachment download-all <key>` | Download all attachments |
71
+ | `jiradc issue attachment upload <key> <files...>` | Upload files |
72
+ | `jiradc issue attachment delete <attachmentId>` | Delete an attachment |
66
73
 
67
74
  ### project
68
75
 
69
- | Command | Description |
70
- |---------|-------------|
71
- | `jiradc project list` | List projects |
76
+ | Command | Description |
77
+ | ------------------------------- | --------------------------- |
78
+ | `jiradc project list` | List projects |
72
79
  | `jiradc project versions <key>` | List versions for a project |
73
80
 
74
81
  ### component
75
82
 
76
- | Command | Description |
77
- |---------|-------------|
78
- | `jiradc component list` | List components for a project (`--project <key>`) |
79
- | `jiradc component get <id>` | Get a component by ID |
80
- | `jiradc component create` | Create a component (`--project`, `--name`, `--description`, `--lead`) |
81
- | `jiradc component update <id>` | Update a component |
82
- | `jiradc component issue-count <id>` | Get the number of issues using a component |
83
+ | Command | Description |
84
+ | ----------------------------------- | --------------------------------------------------------------------- |
85
+ | `jiradc component list` | List components for a project (`--project <key>`) |
86
+ | `jiradc component get <id>` | Get a component by ID |
87
+ | `jiradc component create` | Create a component (`--project`, `--name`, `--description`, `--lead`) |
88
+ | `jiradc component update <id>` | Update a component |
89
+ | `jiradc component issue-count <id>` | Get the number of issues using a component |
83
90
 
84
91
  ### board
85
92
 
86
- | Command | Description |
87
- |---------|-------------|
88
- | `jiradc board list` | List boards (`--project`, `--type`: scrum/kanban/simple, `--name`) |
89
- | `jiradc board issues <boardId>` | Get issues on a board |
93
+ | Command | Description |
94
+ | ------------------------------- | ------------------------------------------------------------------ |
95
+ | `jiradc board list` | List boards (`--project`, `--type`: scrum/kanban/simple, `--name`) |
96
+ | `jiradc board issues <boardId>` | Get issues on a board |
90
97
 
91
98
  ### sprint
92
99
 
93
- | Command | Description |
94
- |---------|-------------|
95
- | `jiradc sprint list <boardId>` | List sprints (`--state`: future/active/closed) |
96
- | `jiradc sprint issues <boardId> <sprintId>` | Get issues in a sprint |
97
- | `jiradc sprint create <boardId>` | Create a sprint |
98
- | `jiradc sprint update <sprintId>` | Update a sprint |
99
- | `jiradc sprint delete <sprintId>` | Delete a sprint (returns its issues to the backlog) |
100
+ | Command | Description |
101
+ | ------------------------------------------- | --------------------------------------------------- |
102
+ | `jiradc sprint list <boardId>` | List sprints (`--state`: future/active/closed) |
103
+ | `jiradc sprint issues <boardId> <sprintId>` | Get issues in a sprint |
104
+ | `jiradc sprint create <boardId>` | Create a sprint |
105
+ | `jiradc sprint update <sprintId>` | Update a sprint |
106
+ | `jiradc sprint delete <sprintId>` | Delete a sprint (returns its issues to the backlog) |
100
107
 
101
108
  ### field
102
109
 
103
- | Command | Description |
104
- |---------|-------------|
105
- | `jiradc field search` | Search for fields (`--query`, `--type`: custom/system) |
106
- | `jiradc field options <fieldKey>` | Get allowed values for a field (`--project`, `--issue-type`) |
110
+ | Command | Description |
111
+ | ------------------------------- | ----------------------------------------------------------------------------- |
112
+ | `jiradc field search <keyword>` | Search for fields by name or ID (returns name + type, **not** allowed values) |
107
113
 
108
114
  ### user
109
115
 
110
- | Command | Description |
111
- |---------|-------------|
116
+ | Command | Description |
117
+ | ---------------- | --------------------- |
112
118
  | `jiradc user me` | Get current user info |
113
119
 
114
120
  ## Pagination
package/dist/index.js CHANGED
@@ -4,6 +4,45 @@
4
4
  import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
5
5
  import { homedir } from "os";
6
6
  import { join } from "path";
7
+ var DEFAULT_TTL = 36e5;
8
+ function getCacheDir(name) {
9
+ return join(homedir(), ".cache", name);
10
+ }
11
+ function getCachePath(name, key) {
12
+ return join(getCacheDir(name), `${key}.json`);
13
+ }
14
+ function cacheGet(options, key) {
15
+ const ttl = options.ttl ?? DEFAULT_TTL;
16
+ const path = getCachePath(options.name, key);
17
+ try {
18
+ const stat = statSync(path);
19
+ if (Date.now() - stat.mtimeMs > ttl)
20
+ return null;
21
+ const raw = readFileSync(path, "utf-8");
22
+ const entry = JSON.parse(raw);
23
+ return entry.data;
24
+ } catch {
25
+ return null;
26
+ }
27
+ }
28
+ function cacheSet(options, key, data) {
29
+ const dir = getCacheDir(options.name);
30
+ const path = getCachePath(options.name, key);
31
+ const entry = { data, timestamp: Date.now() };
32
+ try {
33
+ mkdirSync(dir, { recursive: true });
34
+ writeFileSync(path, JSON.stringify(entry));
35
+ } catch {
36
+ }
37
+ }
38
+ async function cacheGetOrFetch(options, key, fetcher) {
39
+ const cached = cacheGet(options, key);
40
+ if (cached !== null)
41
+ return cached;
42
+ const data = await fetcher();
43
+ cacheSet(options, key, data);
44
+ return data;
45
+ }
7
46
 
8
47
  // ../../cli-utils/dist/bootstrap.js
9
48
  import { readFileSync as readFileSync2 } from "fs";
@@ -239,6 +278,7 @@ var CliAuthError = class extends Error {
239
278
  };
240
279
  var NOT_FOUND_RECOVERY = "Verify the id/key, then check your access.";
241
280
  var NOT_FOUND_MESSAGE = "Not found: it may not exist, or you may not have permission to see it";
281
+ var USAGE_RECOVERY = "Check the command syntax and flags; run the command with --help.";
242
282
  var CliNotFoundError = class extends Error {
243
283
  recovery;
244
284
  constructor(message, recovery = NOT_FOUND_RECOVERY) {
@@ -247,6 +287,16 @@ var CliNotFoundError = class extends Error {
247
287
  this.recovery = recovery;
248
288
  }
249
289
  };
290
+ var CliUsageError = class extends Error {
291
+ recovery;
292
+ detail;
293
+ constructor(message, recovery = USAGE_RECOVERY, detail) {
294
+ super(message);
295
+ this.name = "CliUsageError";
296
+ this.recovery = recovery;
297
+ this.detail = detail;
298
+ }
299
+ };
250
300
  var SubcommandRequiredError = class extends Error {
251
301
  subcommands;
252
302
  constructor(commandPath3, subcommands) {
@@ -255,6 +305,16 @@ var SubcommandRequiredError = class extends Error {
255
305
  this.subcommands = subcommands;
256
306
  }
257
307
  };
308
+ var UnknownSubcommandError = class extends Error {
309
+ subcommands;
310
+ suggestion;
311
+ constructor(commandPath3, token, subcommands, suggestion) {
312
+ super(`'${token}' is not a subcommand of '${commandPath3}'`);
313
+ this.name = "UnknownSubcommandError";
314
+ this.subcommands = subcommands;
315
+ this.suggestion = suggestion;
316
+ }
317
+ };
258
318
  var TYPE_EXIT = {
259
319
  usage: EXIT.USAGE,
260
320
  not_found: EXIT.NOT_FOUND,
@@ -357,12 +417,31 @@ function normalize(err, opts) {
357
417
  detail: { subcommands: err.subcommands }
358
418
  };
359
419
  }
420
+ if (err instanceof UnknownSubcommandError) {
421
+ const didYouMean = err.suggestion === void 0 ? "" : `Did you mean '${err.suggestion}'? `;
422
+ return {
423
+ type: "usage",
424
+ message,
425
+ recovery: `${didYouMean}Valid subcommands: ${err.subcommands.join(", ")}.`,
426
+ retryable: false,
427
+ detail: { subcommands: err.subcommands }
428
+ };
429
+ }
360
430
  if (err instanceof CliAuthError) {
361
431
  return { type: "auth", message: message || "Missing credentials", recovery: opts.authRecovery, retryable: false };
362
432
  }
363
433
  if (err instanceof CliNotFoundError) {
364
434
  return { type: "not_found", message, recovery: err.recovery, retryable: false };
365
435
  }
436
+ if (err instanceof CliUsageError) {
437
+ return {
438
+ type: "usage",
439
+ message,
440
+ recovery: err.recovery,
441
+ retryable: false,
442
+ ...err.detail !== void 0 ? { detail: err.detail } : {}
443
+ };
444
+ }
366
445
  const status = httpStatus(err);
367
446
  const detail = responseDetail(err);
368
447
  if (status !== void 0) {
@@ -548,11 +627,39 @@ function attachSubcommandGuards(cmd) {
548
627
  const hasAction = Boolean(cmd._actionHandler);
549
628
  if (hasAction)
550
629
  return;
551
- cmd.action(() => {
630
+ cmd.allowExcessArguments(true);
631
+ cmd.action((...params) => {
632
+ const invoked = params[params.length - 1];
552
633
  const names = cmd.commands.map((c) => c.name()).filter((name) => name !== "help");
553
- throw new SubcommandRequiredError(commandPath2(cmd), names);
634
+ const [token] = invoked.args;
635
+ if (token === void 0)
636
+ throw new SubcommandRequiredError(commandPath2(cmd), names);
637
+ throw new UnknownSubcommandError(commandPath2(cmd), token, names, suggestSubcommand(token, names));
554
638
  });
555
639
  }
640
+ function editDistance(a, b) {
641
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
642
+ for (let i = 1; i <= a.length; i++) {
643
+ const row = [i];
644
+ for (let j = 1; j <= b.length; j++) {
645
+ const substitution = prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
646
+ row[j] = Math.min(row[j - 1] + 1, prev[j] + 1, substitution);
647
+ }
648
+ prev = row;
649
+ }
650
+ return prev[b.length];
651
+ }
652
+ function suggestSubcommand(token, names) {
653
+ const MAX_EDITS = 2;
654
+ let best;
655
+ for (const name of names) {
656
+ const distance = editDistance(token.toLowerCase(), name.toLowerCase());
657
+ if (distance <= MAX_EDITS && distance < token.length && (!best || distance < best.distance)) {
658
+ best = { name, distance };
659
+ }
660
+ }
661
+ return best?.name;
662
+ }
556
663
  async function runCli(program, opts) {
557
664
  routeErrors(program);
558
665
  attachSubcommandGuards(program);
@@ -568,7 +675,7 @@ async function runCli(program, opts) {
568
675
 
569
676
  // src/program.ts
570
677
  import { styleText } from "util";
571
- import { Command as Command16 } from "commander";
678
+ import { Command as Command17 } from "commander";
572
679
 
573
680
  // src/commands/board/issues.ts
574
681
  import { Argument } from "commander";
@@ -623,9 +730,28 @@ function transformUser(user) {
623
730
  function transformField(field) {
624
731
  return field;
625
732
  }
626
- function transformCustomFieldOption(option) {
627
- const { self: _self, ...rest } = option;
628
- return rest;
733
+
734
+ // src/utils/transformers/field-meta.ts
735
+ function slimAllowedValue(raw) {
736
+ const out = {};
737
+ if (typeof raw.id === "string") out.id = raw.id;
738
+ if (typeof raw.name === "string") out.name = raw.name;
739
+ if (typeof raw.value === "string") out.value = raw.value;
740
+ if (Array.isArray(raw.children) && raw.children.length > 0) out.children = raw.children.map(slimAllowedValue);
741
+ return out;
742
+ }
743
+ function transformFieldMetaEntry(id, meta) {
744
+ const out = { id, required: meta.required === true };
745
+ if (typeof meta.name === "string") out.name = meta.name;
746
+ if (typeof meta.schema?.type === "string") out.type = meta.schema.type;
747
+ if (Array.isArray(meta.allowedValues) && meta.allowedValues.length > 0) {
748
+ out.allowedValues = meta.allowedValues.map(slimAllowedValue);
749
+ }
750
+ return out;
751
+ }
752
+ function transformFieldMeta(fields) {
753
+ const entries = Object.entries(fields).map(([id, meta]) => transformFieldMetaEntry(id, meta));
754
+ return [...entries.filter((f) => f.required), ...entries.filter((f) => !f.required)];
629
755
  }
630
756
 
631
757
  // src/utils/transformers/project.ts
@@ -766,12 +892,22 @@ function transformIssueLink(l) {
766
892
  ...outwardIssue ? { outwardIssue: transformIssueBasic(outwardIssue) } : {}
767
893
  };
768
894
  }
895
+ function customFieldNames(names, fields) {
896
+ if (!names) return void 0;
897
+ const kept = Object.entries(names).filter(
898
+ ([id]) => id.startsWith("customfield_") && fields[id] !== void 0
899
+ );
900
+ return kept.length > 0 ? Object.fromEntries(kept) : void 0;
901
+ }
769
902
  function transformIssue(issue) {
770
- const { self: _self, expand: _expand, names: _names, schema: _schema, fields, ...rest } = issue;
903
+ const { self: _self, expand: _expand, names, schema: _schema, fields, ...rest } = issue;
904
+ const transformed = transformIssueFields(fields, issue.key);
905
+ const fieldNames = customFieldNames(names, transformed);
771
906
  return {
772
907
  ...rest,
773
908
  url: issueBrowseUrl(issue.key),
774
- fields: transformIssueFields(fields)
909
+ fields: transformed,
910
+ ...fieldNames ? { names: fieldNames } : {}
775
911
  };
776
912
  }
777
913
  function isEmpty(v) {
@@ -807,7 +943,45 @@ function pruneSentinels(fields) {
807
943
  if (watches && watches.watchCount === 0) delete out.watches;
808
944
  return out;
809
945
  }
810
- function transformIssueFields(fields) {
946
+ var DEV_STATUS_DUMP = /com\.atlassian\.jira\.plugin\.devstatus/;
947
+ function collapseDevStatus(fields, key) {
948
+ const out = { ...fields };
949
+ for (const [field, value] of Object.entries(out)) {
950
+ if (typeof value === "string" && DEV_STATUS_DUMP.test(value)) {
951
+ out[field] = `(use: jiradc issue dev-status ${key})`;
952
+ }
953
+ }
954
+ return out;
955
+ }
956
+ function transformInlineComponent(c) {
957
+ const { self: _self, description: _description, ...rest } = c;
958
+ return rest;
959
+ }
960
+ var ISSUE_KEY_PATTERN = /^[A-Z][A-Z0-9_]*-\d+$/;
961
+ function asIssueRefKey(value) {
962
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
963
+ const o = value;
964
+ if (typeof o.key !== "string" || !ISSUE_KEY_PATTERN.test(o.key)) return void 0;
965
+ if (typeof o.self !== "string" || !o.self.includes("/issue/")) return void 0;
966
+ return o.key;
967
+ }
968
+ function collapseIssueRefs(fields) {
969
+ const out = { ...fields };
970
+ for (const [key, value] of Object.entries(out)) {
971
+ const single = asIssueRefKey(value);
972
+ if (single !== void 0) {
973
+ out[key] = single;
974
+ continue;
975
+ }
976
+ if (Array.isArray(value) && value.length > 0) {
977
+ const keys = value.map(asIssueRefKey);
978
+ if (keys.every((k) => k !== void 0)) out[key] = keys;
979
+ }
980
+ }
981
+ return out;
982
+ }
983
+ function transformIssueFields(fields, key) {
984
+ if (!fields) return {};
811
985
  const {
812
986
  // shaped sub-entities (recursed individually)
813
987
  issuetype,
@@ -833,9 +1007,11 @@ function transformIssueFields(fields) {
833
1007
  // everything else: optional scalars, customfield_*, etc. — gets compacted
834
1008
  ...rest
835
1009
  } = fields;
836
- const compacted = pruneSentinels(compactRecord(rest));
1010
+ const compacted = collapseDevStatus(collapseIssueRefs(pruneSentinels(compactRecord(rest))), key);
1011
+ const components = compacted.components;
837
1012
  return {
838
1013
  ...compacted,
1014
+ ...Array.isArray(components) ? { components: components.map(transformInlineComponent) } : {},
839
1015
  summary,
840
1016
  project,
841
1017
  created,
@@ -876,23 +1052,23 @@ function transformIssueFields(fields) {
876
1052
  }
877
1053
 
878
1054
  // src/utils/transformers/transition.ts
879
- function slimAllowedValue(raw) {
1055
+ function slimAllowedValue2(raw) {
880
1056
  if (!raw || typeof raw !== "object") return {};
881
1057
  const av = raw;
882
1058
  const slim = {};
883
1059
  if (typeof av.id === "string") slim.id = av.id;
884
1060
  if (typeof av.name === "string") slim.name = av.name;
885
1061
  if (typeof av.value === "string") slim.value = av.value;
1062
+ if (Array.isArray(av.children) && av.children.length > 0) slim.children = av.children.map(slimAllowedValue2);
886
1063
  return slim;
887
1064
  }
888
1065
  function slimField(raw) {
889
1066
  if (!raw || typeof raw !== "object") return {};
890
1067
  const field = raw;
891
1068
  const out = {};
892
- if (typeof field.required === "boolean") out.required = field.required;
893
1069
  if (typeof field.name === "string") out.name = field.name;
894
1070
  if (Array.isArray(field.allowedValues)) {
895
- out.allowedValues = field.allowedValues.map(slimAllowedValue);
1071
+ out.allowedValues = field.allowedValues.map(slimAllowedValue2);
896
1072
  }
897
1073
  return out;
898
1074
  }
@@ -1190,7 +1366,7 @@ function update(parent) {
1190
1366
  async (id, opts) => {
1191
1367
  const description = resolveTextOrFile(opts, "description", { required: false });
1192
1368
  if (opts.name === void 0 && description === void 0 && opts.lead === void 0 && opts.assigneeType === void 0) {
1193
- throw new Error("Provide at least one of --name, --description, --lead, --assignee-type");
1369
+ throw new CliUsageError("Provide at least one of --name, --description, --lead, --assignee-type");
1194
1370
  }
1195
1371
  const client = getClient();
1196
1372
  const result = await client.components.update({
@@ -1224,22 +1400,6 @@ function registerComponentCommands(program) {
1224
1400
  issueCount(component);
1225
1401
  }
1226
1402
 
1227
- // src/commands/field/options.ts
1228
- function options(parent) {
1229
- const cmd = parent.command("options").description("Get available options for a custom field").argument("<id>", "Field ID", text).option("--query <text>", "Filter options by text", text).option("--limit <number>", "Max results to return (1-1000)", intInRange(1, 1e3), 25).option("--start <number>", "Page number (1-indexed)", positiveInt);
1230
- examples(cmd, ["10001", '10001 --query "High"', "10001 --limit 20 --start 2"]);
1231
- cmd.action(async (id, opts) => {
1232
- const client = getClient();
1233
- const result = await client.fields.getFieldOptions({
1234
- fieldId: id,
1235
- query: opts.query,
1236
- maxResults: opts.limit,
1237
- page: opts.start
1238
- });
1239
- output(transformPaged({ ...result, startAt: result.startAt ?? 0 }, transformCustomFieldOption));
1240
- });
1241
- }
1242
-
1243
1403
  // src/commands/field/search.ts
1244
1404
  function search(parent) {
1245
1405
  const cmd = parent.command("search").description("Search for fields by name or ID").argument("<keyword>", "Search keyword", text).option("--limit <number>", "Maximum number of results (1-1000)", intInRange(1, 1e3), 25);
@@ -1254,9 +1414,8 @@ function search(parent) {
1254
1414
  // src/commands/field/index.ts
1255
1415
  function registerFieldCommands(program) {
1256
1416
  const field = program.command("field").description("Field operations");
1257
- examples(field, ['search "epic"', 'search "priority"', "options 10120"]);
1417
+ examples(field, ['search "epic"', 'search "priority"']);
1258
1418
  search(field);
1259
- options(field);
1260
1419
  }
1261
1420
 
1262
1421
  // src/utils/resolve-user.ts
@@ -1667,13 +1826,16 @@ function registerCommentCommands(parent) {
1667
1826
  }
1668
1827
 
1669
1828
  // src/commands/issue/create.ts
1829
+ import { Option as Option5 } from "commander";
1670
1830
  import { z as z2 } from "zod";
1671
- var customFieldsSchema = z2.record(z2.unknown());
1831
+ var fieldsSchema = z2.record(z2.unknown());
1672
1832
  function create3(parent) {
1673
1833
  const cmd = parent.command("create").description("Create a new issue").requiredOption("--project <key>", "Project key or ID", text).requiredOption("--type <name>", "Issue type name (e.g., Task, Bug, Story)", text).requiredOption("--summary <text>", "Issue summary/title", text).option("--assignee <user>", 'Assignee. Username, "me", or "none" to leave unassigned.', text).option("--reporter <user>", 'Reporter. Username or "me".', text).option("--priority <name>", "Priority name (e.g., High, Medium, Low)", text).option("--labels <labels>", "Comma-separated labels", text).option("--components <names>", "Comma-separated component names", text).option("--fix-versions <versions>", "Comma-separated fix version names", text).option("--due-date <date>", "Due date in YYYY-MM-DD format", date).option("--parent <key>", "Parent issue key (for subtasks)", issueKey).option(
1674
- "--custom-fields <json>",
1675
- `Additional custom fields as JSON (e.g., '{"customfield_10100": "EPIC-1"}')`,
1676
- jsonShape(customFieldsSchema)
1834
+ "--fields <json>",
1835
+ `Additional fields as JSON (e.g., '{"customfield_10100": "EPIC-1"}')`,
1836
+ jsonShape(fieldsSchema)
1837
+ ).addOption(
1838
+ new Option5("--custom-fields <json>", "Deprecated alias for --fields").argParser(jsonShape(fieldsSchema)).hideHelp()
1677
1839
  );
1678
1840
  textOrFileOption(cmd, "description", { description: "Issue description in wiki markup" });
1679
1841
  examples(cmd, [
@@ -1684,6 +1846,13 @@ function create3(parent) {
1684
1846
  cmd.action(
1685
1847
  async (opts) => {
1686
1848
  const description = resolveTextOrFile(opts, "description", { required: false });
1849
+ if (opts.fields && opts.customFields) {
1850
+ throw new CliUsageError(
1851
+ "Pass either --fields or --custom-fields, not both",
1852
+ "--custom-fields is a deprecated alias for --fields. Merge the two JSON objects into a single --fields value."
1853
+ );
1854
+ }
1855
+ const fields = opts.fields ?? opts.customFields;
1687
1856
  const assignee = opts.assignee !== void 0 ? await resolveUserToken(opts.assignee) : void 0;
1688
1857
  const reporter = opts.reporter !== void 0 ? await resolveUserToken(opts.reporter) : void 0;
1689
1858
  const client = getClient();
@@ -1700,13 +1869,31 @@ function create3(parent) {
1700
1869
  fixVersions: opts.fixVersions?.split(",").map((v) => v.trim()),
1701
1870
  dueDate: opts.dueDate,
1702
1871
  parent: opts.parent,
1703
- customFields: opts.customFields
1872
+ customFields: fields
1704
1873
  });
1705
1874
  output(transformCreatedIssue(result));
1706
1875
  }
1707
1876
  );
1708
1877
  }
1709
1878
 
1879
+ // src/commands/issue/createmeta.ts
1880
+ function createmeta(parent) {
1881
+ const cmd = parent.command("createmeta").description("Show which fields a new issue of a given type accepts, and the values they allow").requiredOption("--project <key>", "Project key", text).requiredOption("--type <name>", "Issue type name (e.g., Task, Bug, Story)", text);
1882
+ examples(cmd, ["--project PROJ --type Task", "--project PROJ --type Bug"]);
1883
+ cmd.action(async (opts) => {
1884
+ const client = getClient();
1885
+ const meta = await client.issues.getCreateMeta({ projectKey: opts.project, issueTypeName: opts.type });
1886
+ const issueType = meta.projects?.[0]?.issuetypes?.[0];
1887
+ if (!issueType?.fields) {
1888
+ throw new CliNotFoundError(
1889
+ `No create metadata for issue type "${opts.type}" in project "${opts.project}"`,
1890
+ "Verify the project key and issue type name, then check you may create that type there."
1891
+ );
1892
+ }
1893
+ output(transformFieldMeta(issueType.fields));
1894
+ });
1895
+ }
1896
+
1710
1897
  // src/commands/issue/delete.ts
1711
1898
  function deleteIssue(parent) {
1712
1899
  const cmd = parent.command("delete").description("Delete an issue").argument("<key>", "Issue key", issueKey).option("--delete-subtasks", "Also delete subtasks (default: false)");
@@ -1796,10 +1983,20 @@ function devStatus(parent) {
1796
1983
  });
1797
1984
  }
1798
1985
 
1986
+ // src/commands/issue/editmeta.ts
1987
+ function editmeta(parent) {
1988
+ const cmd = parent.command("editmeta").description("Show which fields this issue's edit screen accepts, and the values they allow").argument("<key>", "Issue key", issueKey);
1989
+ examples(cmd, ["PROJ-123"]);
1990
+ cmd.action(async (key) => {
1991
+ const client = getClient();
1992
+ const meta = await client.issues.getEditMeta({ issueKeyOrId: key });
1993
+ output(transformFieldMeta(meta.fields ?? {}));
1994
+ });
1995
+ }
1996
+
1799
1997
  // src/utils/constants.ts
1800
- var DEFAULT_FIELDS = [
1998
+ var SEARCH_DEFAULT_FIELDS = [
1801
1999
  "summary",
1802
- "description",
1803
2000
  "status",
1804
2001
  "assignee",
1805
2002
  "reporter",
@@ -1808,13 +2005,78 @@ var DEFAULT_FIELDS = [
1808
2005
  "created",
1809
2006
  "updated",
1810
2007
  "issuetype",
1811
- "components",
1812
- "comment"
2008
+ "components"
1813
2009
  ];
2010
+ var DEFAULT_FIELDS = [...SEARCH_DEFAULT_FIELDS, "description", "comment"];
2011
+
2012
+ // src/utils/field-selectors.ts
2013
+ var WILDCARD = "*";
2014
+ var NEGATION = "-";
2015
+ function buildFieldNameIndex(fields) {
2016
+ const ids = /* @__PURE__ */ new Set();
2017
+ const byName = /* @__PURE__ */ new Map();
2018
+ for (const field of fields) {
2019
+ ids.add(field.id);
2020
+ byName.set(field.name.toLowerCase(), field.id);
2021
+ for (const clause of field.clauseNames ?? []) byName.set(clause.toLowerCase(), field.id);
2022
+ }
2023
+ for (const field of fields) byName.set(field.id.toLowerCase(), field.id);
2024
+ return { ids, byName };
2025
+ }
2026
+ function resolveOne(token, index) {
2027
+ if (index.ids.has(token)) return token;
2028
+ return index.byName.get(token.toLowerCase());
2029
+ }
2030
+ function resolveFieldSelectors(tokens, index) {
2031
+ const resolved = [];
2032
+ const unresolved = [];
2033
+ for (const token of tokens) {
2034
+ if (token.startsWith(WILDCARD)) {
2035
+ resolved.push(token);
2036
+ continue;
2037
+ }
2038
+ const negated = token.startsWith(NEGATION);
2039
+ const bare = negated ? token.slice(1) : token;
2040
+ const id = resolveOne(bare, index);
2041
+ if (id === void 0) {
2042
+ unresolved.push(token);
2043
+ continue;
2044
+ }
2045
+ resolved.push(negated ? `${NEGATION}${id}` : id);
2046
+ }
2047
+ if (unresolved.length > 0) {
2048
+ const plural = unresolved.length > 1 ? "s" : "";
2049
+ throw new CliUsageError(
2050
+ `Unknown field name${plural}: ${unresolved.join(", ")}`,
2051
+ `Jira selects fields by id, and these matched no field id, name or JQL clause name on this instance. Run jiradc field search <keyword> to find the right name or id. Left unresolved these would be dropped silently, returning an answer missing the field${plural} you asked for.`,
2052
+ { unresolved }
2053
+ );
2054
+ }
2055
+ return [...new Set(resolved)];
2056
+ }
2057
+ function parseFieldSelectors(raw) {
2058
+ return raw.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
2059
+ }
2060
+ var FIELD_CACHE = { name: "jiradc", ttl: 36e5 };
2061
+ async function loadFieldNameIndex(client) {
2062
+ const fields = await cacheGetOrFetch(
2063
+ FIELD_CACHE,
2064
+ "field-names",
2065
+ async () => (await client.fields.getAll()).map(({ id, name, clauseNames }) => ({ id, name, clauseNames }))
2066
+ );
2067
+ return buildFieldNameIndex(fields);
2068
+ }
2069
+ async function selectFields(client, opts, defaults) {
2070
+ if (opts.allFields === true) return void 0;
2071
+ if (opts.fields === void 0) return defaults;
2072
+ const tokens = parseFieldSelectors(opts.fields);
2073
+ if (tokens.length === 0) return defaults;
2074
+ return resolveFieldSelectors(tokens, await loadFieldNameIndex(client));
2075
+ }
1814
2076
 
1815
2077
  // src/commands/issue/get.ts
1816
2078
  function get2(parent) {
1817
- const cmd = parent.command("get").description("Get issue details").argument("<key>", "Issue key", issueKey).option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)", text).option("--all-fields", "Return all fields instead of defaults").option("--expand <expand>", 'Expand options (e.g., "transitions", "changelog")', text);
2079
+ const cmd = parent.command("get").description("Get issue details").argument("<key>", "Issue key", issueKey).option("--fields <fields>", "Comma-separated fields to return, by name or id", text).option("--all-fields", "Return all fields instead of defaults").option("--expand <expand>", 'Expand options (e.g., "transitions", "changelog")', text);
1818
2080
  examples(cmd, [
1819
2081
  "PROJ-123",
1820
2082
  "PROJ-123 --fields summary,status,assignee",
@@ -1823,7 +2085,7 @@ function get2(parent) {
1823
2085
  ]);
1824
2086
  cmd.action(async (key, opts) => {
1825
2087
  const client = getClient();
1826
- const fields = opts.allFields ? void 0 : opts.fields?.split(",").map((f) => f.trim()) ?? DEFAULT_FIELDS;
2088
+ const fields = await selectFields(client, opts, DEFAULT_FIELDS);
1827
2089
  const result = await client.issues.get({
1828
2090
  issueKeyOrId: key,
1829
2091
  fields,
@@ -1902,63 +2164,126 @@ function link(parent) {
1902
2164
  });
1903
2165
  }
1904
2166
 
2167
+ // src/utils/paging.ts
2168
+ var ALL_RESULTS_CAP = 1e3;
2169
+ function planSearch(opts, maxPageSize) {
2170
+ const start = opts.start ?? 0;
2171
+ if (opts.all !== true) return { all: false, pageSize: opts.limit, start };
2172
+ if (opts.limitGiven) {
2173
+ throw new CliUsageError(
2174
+ "Pass either --all or --limit, not both",
2175
+ "--all collects every match; --limit caps how many come back. Drop --limit to collect everything, or drop --all and page with --limit and --start."
2176
+ );
2177
+ }
2178
+ return { all: true, pageSize: maxPageSize, start };
2179
+ }
2180
+ async function fetchEveryPage(fetchPage, opts) {
2181
+ const first = await fetchPage(opts.start, opts.pageSize);
2182
+ const outstanding = first.total - opts.start;
2183
+ if (outstanding > ALL_RESULTS_CAP) {
2184
+ throw new CliUsageError(
2185
+ `--all would collect ${outstanding} issues, above the ${ALL_RESULTS_CAP} limit`,
2186
+ `Narrow the query, or page it yourself with --start and --limit. Returning the first ${ALL_RESULTS_CAP} instead would look like a complete answer and would not be one.`,
2187
+ { total: first.total, cap: ALL_RESULTS_CAP }
2188
+ );
2189
+ }
2190
+ const offsets = [];
2191
+ for (let at = opts.start + first.issues.length; at < first.total; at += opts.pageSize) offsets.push(at);
2192
+ const rest = await Promise.all(offsets.map(async (at) => fetchPage(at, opts.pageSize)));
2193
+ const issues3 = [first, ...rest].flatMap((page) => page.issues);
2194
+ return { startAt: opts.start, maxResults: issues3.length, total: first.total, isLast: true, issues: issues3 };
2195
+ }
2196
+
1905
2197
  // src/commands/issue/search.ts
2198
+ var MAX_PAGE_SIZE = 50;
1906
2199
  function search2(parent) {
1907
- const cmd = parent.command("search").description("Search issues using JQL").argument("<jql>", "JQL query string", text).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)", text).option("--all-fields", "Return all fields instead of defaults");
2200
+ const cmd = parent.command("search").description("Search issues using JQL").argument("<jql>", "JQL query string", text).option(
2201
+ "--limit <number>",
2202
+ `Max results (1-${MAX_PAGE_SIZE}, Jira DC caps at ${MAX_PAGE_SIZE})`,
2203
+ intInRange(1, MAX_PAGE_SIZE),
2204
+ 25
2205
+ ).option("--start <number>", "Starting index for pagination", nonNegativeInt).option(
2206
+ "--fields <fields>",
2207
+ "Comma-separated fields to return, by name or id (defaults to a lean set without description/comment)",
2208
+ text
2209
+ ).option("--all-fields", "Return all fields instead of defaults").option("--all", `Collect every match, not just one page (up to ${ALL_RESULTS_CAP}; fails above it)`);
1908
2210
  examples(cmd, [
1909
2211
  '"project = PROJ AND status = Open"',
1910
2212
  '"assignee = currentUser()" --limit 10 --fields summary,status',
2213
+ '"project = PROJ AND status = Open" --all',
1911
2214
  '"project = PROJ" --start 50 --limit 50'
1912
2215
  ]);
1913
- cmd.action(async (jql, opts) => {
1914
- const client = getClient();
1915
- const fields = opts.allFields ? void 0 : opts.fields?.split(",").map((f) => f.trim()) ?? DEFAULT_FIELDS;
1916
- const result = await client.issues.search({
1917
- jql,
1918
- startAt: opts.start,
1919
- maxResults: opts.limit,
1920
- fields
1921
- });
1922
- output(transformPaged(result, transformIssue));
1923
- });
2216
+ cmd.action(
2217
+ async (jql, opts) => {
2218
+ const client = getClient();
2219
+ const fields = await selectFields(client, opts, SEARCH_DEFAULT_FIELDS);
2220
+ const plan = planSearch({ ...opts, limitGiven: cmd.getOptionValueSource("limit") === "cli" }, MAX_PAGE_SIZE);
2221
+ const page = async (startAt, maxResults) => client.issues.search({ jql, startAt, maxResults, fields });
2222
+ if (!plan.all) {
2223
+ output(transformPaged(await page(plan.start, plan.pageSize), transformIssue));
2224
+ return;
2225
+ }
2226
+ const every = await fetchEveryPage(page, { pageSize: plan.pageSize, start: plan.start });
2227
+ output({ ...every, issues: every.issues.map(transformIssue) });
2228
+ }
2229
+ );
1924
2230
  }
1925
2231
 
1926
2232
  // src/commands/issue/transition.ts
2233
+ import { z as z3 } from "zod";
2234
+ var fieldsSchema2 = z3.record(z3.unknown());
2235
+ function resolveTransitionId(to, transitions2) {
2236
+ const target = to.toLowerCase();
2237
+ const byName = transitions2.filter((t) => t.name.toLowerCase() === target);
2238
+ const matches = byName.length > 0 ? byName : transitions2.filter((t) => t.to.toLowerCase() === target);
2239
+ if (matches.length === 1) return matches[0].id;
2240
+ const available = transitions2.map((t) => ({ id: t.id, name: t.name, to: t.to }));
2241
+ if (matches.length === 0) {
2242
+ throw new CliUsageError(
2243
+ `Transition "${to}" not available from current status`,
2244
+ "Re-run --to with one of the listed transitions (by name, id, or target status).",
2245
+ { available }
2246
+ );
2247
+ }
2248
+ const ids = matches.map((t) => t.id).join(", ");
2249
+ throw new CliUsageError(
2250
+ `"${to}" matches more than one available transition`,
2251
+ `Re-run --to with one of these ids: ${ids}.`,
2252
+ { available }
2253
+ );
2254
+ }
1927
2255
  function transition(parent) {
1928
- const cmd = parent.command("transition").description("Transition issue to a new status").argument("<key>", "Issue key", issueKey).requiredOption("--to <idOrName>", "Transition ID, or status name (case-insensitive)", text);
2256
+ const cmd = parent.command("transition").description("Transition issue to a new status").argument("<key>", "Issue key", issueKey).requiredOption("--to <idOrName>", "Transition ID, transition name, or target status name (case-insensitive)", text).option(
2257
+ "--fields <json>",
2258
+ `Fields required by the transition screen, as JSON (e.g. '{"customfield_10100": {"value": "Approved"}}')`,
2259
+ jsonShape(fieldsSchema2)
2260
+ );
1929
2261
  commentOption(cmd, { description: "Comment to add during transition" });
1930
2262
  examples(cmd, [
1931
2263
  "PROJ-123 --to 31",
2264
+ 'PROJ-123 --to "Start Review"',
1932
2265
  'PROJ-123 --to "In Review"',
1933
- 'PROJ-123 --to Done --comment "Verified in staging"'
2266
+ 'PROJ-123 --to Done --comment "Verified in staging"',
2267
+ `PROJ-123 --to Approve --fields '{"customfield_10100": {"value": "Approved"}}'`
1934
2268
  ]);
1935
- cmd.action(async (key, opts) => {
1936
- const comment = resolveTextOrFile(opts, "comment", { required: false });
1937
- const client = getClient();
1938
- let transitionId;
1939
- if (/^\d+$/.test(opts.to)) {
1940
- transitionId = opts.to;
1941
- } else {
1942
- const raw = await client.issues.getTransitions({
1943
- issueKeyOrId: key,
1944
- expand: "transitions.fields"
1945
- });
1946
- const transitions2 = transformTransitions(raw);
1947
- const target = opts.to.toLowerCase();
1948
- const matches = transitions2.filter((t) => t.name.toLowerCase() === target);
1949
- if (matches.length === 0) {
1950
- const names = transitions2.map((t) => `"${t.name}"`).join(", ");
1951
- throw new Error(`Transition "${opts.to}" not available from current status. Available: ${names || "(none)"}`);
1952
- }
1953
- if (matches.length > 1) {
1954
- const ids = matches.map((t) => t.id).join(", ");
1955
- throw new Error(`Multiple transitions named "${opts.to}". Use --to <id> with one of: ${ids}`);
2269
+ cmd.action(
2270
+ async (key, opts) => {
2271
+ const comment = resolveTextOrFile(opts, "comment", { required: false });
2272
+ const client = getClient();
2273
+ let transitionId;
2274
+ if (/^\d+$/.test(opts.to)) {
2275
+ transitionId = opts.to;
2276
+ } else {
2277
+ const raw = await client.issues.getTransitions({
2278
+ issueKeyOrId: key,
2279
+ expand: "transitions.fields"
2280
+ });
2281
+ transitionId = resolveTransitionId(opts.to, transformTransitions(raw));
1956
2282
  }
1957
- transitionId = matches[0].id;
2283
+ await client.issues.transition({ issueKeyOrId: key, transitionId, fields: opts.fields, comment });
2284
+ output({ transitioned: true, issue: transformIssueRef(key) });
1958
2285
  }
1959
- await client.issues.transition({ issueKeyOrId: key, transitionId, comment });
1960
- output({ transitioned: true, issue: transformIssueRef(key) });
1961
- });
2286
+ );
1962
2287
  }
1963
2288
 
1964
2289
  // src/commands/issue/transitions.ts
@@ -1987,7 +2312,7 @@ function unlink2(parent) {
1987
2312
  }
1988
2313
 
1989
2314
  // src/commands/issue/update.ts
1990
- import { z as z3 } from "zod";
2315
+ import { z as z4 } from "zod";
1991
2316
 
1992
2317
  // src/utils/multi-value.ts
1993
2318
  import { InvalidArgumentError as InvalidArgumentError5 } from "commander";
@@ -2016,7 +2341,7 @@ function parseMultiValue(flagName, raw) {
2016
2341
  }
2017
2342
 
2018
2343
  // src/commands/issue/update.ts
2019
- var fieldsSchema = z3.record(z3.unknown());
2344
+ var fieldsSchema3 = z4.record(z4.unknown());
2020
2345
  function buildUpdateOps(parsed, wrap) {
2021
2346
  if (parsed.mode !== "mutate") return void 0;
2022
2347
  return [...parsed.adds.map((v) => ({ add: wrap(v) })), ...parsed.removes.map((v) => ({ remove: wrap(v) }))];
@@ -2029,7 +2354,7 @@ function update3(parent) {
2029
2354
  const cmd = parent.command("update").description("Update issue fields").argument("<key>", "Issue key", issueKey).option(
2030
2355
  "--fields <json>",
2031
2356
  "JSON string of fields to update (advanced; merges with shortcuts, wins on conflict)",
2032
- jsonShape(fieldsSchema)
2357
+ jsonShape(fieldsSchema3)
2033
2358
  ).option("--no-notify-users", "Suppress notification emails (default: notify)").option("--attachments <paths>", "Comma-separated local file paths to attach", text).option("--summary <text>", "Set the issue summary", text).option("--priority <name>", "Set the priority by name (e.g. High)", text).option("--assignee <user>", 'Set the assignee. Username, "me", or "none" to unassign.', text).option("--labels <list>", 'Set labels ("a,b,c") or mutate ("+add,-remove")', text).option("--components <list>", 'Set components ("a,b") or mutate ("+add,-remove")', text).option("--fix-versions <list>", 'Set fix versions ("1.0,2.0") or mutate ("+1.0,-0.9")', text);
2034
2359
  textOrFileOption(cmd, "description", { description: "Set the issue description (wiki markup)" });
2035
2360
  examples(cmd, [
@@ -2044,7 +2369,7 @@ function update3(parent) {
2044
2369
  const description = resolveTextOrFile(opts, "description", { required: false });
2045
2370
  const hasShortcut = opts.summary !== void 0 || description !== void 0 || opts.priority !== void 0 || opts.assignee !== void 0 || opts.labels !== void 0 || opts.components !== void 0 || opts.fixVersions !== void 0;
2046
2371
  if (!opts.fields && !opts.attachments && !hasShortcut) {
2047
- throw new Error(
2372
+ throw new CliUsageError(
2048
2373
  "Provide at least one of --fields, --attachments, or a shortcut flag (--summary, --priority, --labels, ...)"
2049
2374
  );
2050
2375
  }
@@ -2132,12 +2457,12 @@ function create4(parent) {
2132
2457
  }
2133
2458
 
2134
2459
  // src/commands/issue/worklog/delete.ts
2135
- import { Option as Option5 } from "commander";
2460
+ import { Option as Option6 } from "commander";
2136
2461
  var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
2137
2462
  function deleteWorklog(parent) {
2138
2463
  const cmd = parent.command("delete").description("Delete a worklog entry").argument("<key>", "Issue key", issueKey);
2139
2464
  subEntityOption(cmd, "worklog", { mandatory: true });
2140
- cmd.addOption(new Option5("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text).option(
2465
+ cmd.addOption(new Option6("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text).option(
2141
2466
  "--increase-by <amount>",
2142
2467
  'Amount to increase the estimate by; required when --adjust-estimate is "manual"',
2143
2468
  text
@@ -2178,12 +2503,12 @@ function list4(parent) {
2178
2503
  }
2179
2504
 
2180
2505
  // src/commands/issue/worklog/update.ts
2181
- import { Option as Option6 } from "commander";
2506
+ import { Option as Option7 } from "commander";
2182
2507
  var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
2183
2508
  function update4(parent) {
2184
2509
  const cmd = parent.command("update").description("Update an existing worklog entry").argument("<key>", "Issue key", issueKey);
2185
2510
  subEntityOption(cmd, "worklog", { mandatory: true });
2186
- cmd.option("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')", text).option("--started <datetime>", "Start time in ISO 8601 format", dateTime).addOption(new Option6("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE2)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text);
2511
+ cmd.option("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')", text).option("--started <datetime>", "Start time in ISO 8601 format", dateTime).addOption(new Option7("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE2)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text);
2187
2512
  commentOption(cmd, { description: "Worklog comment" });
2188
2513
  examples(cmd, [
2189
2514
  'PROJ-123 --worklog-id 12345 --time "1h 30m"',
@@ -2241,6 +2566,8 @@ function registerIssueCommands(program) {
2241
2566
  deleteIssue(issue);
2242
2567
  transition(issue);
2243
2568
  transitions(issue);
2569
+ createmeta(issue);
2570
+ editmeta(issue);
2244
2571
  assign(issue);
2245
2572
  registerCommentCommands(issue);
2246
2573
  registerWorklogCommands(issue);
@@ -2342,10 +2669,10 @@ function issues2(parent) {
2342
2669
  }
2343
2670
 
2344
2671
  // src/commands/sprint/list.ts
2345
- import { Option as Option7 } from "commander";
2672
+ import { Option as Option8 } from "commander";
2346
2673
  var SPRINT_STATES = ["future", "active", "closed"];
2347
2674
  function list6(parent) {
2348
- const cmd = parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new Option7("--state <state>", "Filter by sprint state").choices(SPRINT_STATES));
2675
+ const cmd = parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new Option8("--state <state>", "Filter by sprint state").choices(SPRINT_STATES));
2349
2676
  examples(cmd, ["--board 42", "--board 42 --state active"]);
2350
2677
  cmd.action(async (opts) => {
2351
2678
  const client = getClient();
@@ -2358,10 +2685,10 @@ function list6(parent) {
2358
2685
  }
2359
2686
 
2360
2687
  // src/commands/sprint/update.ts
2361
- import { Argument as Argument4, Option as Option8 } from "commander";
2688
+ import { Argument as Argument4, Option as Option9 } from "commander";
2362
2689
  var SPRINT_STATES2 = ["future", "active", "closed"];
2363
2690
  function update5(parent) {
2364
- const cmd = parent.command("update").description("Update an existing sprint").addArgument(new Argument4("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name", text).addOption(new Option8("--state <state>", "New sprint state").choices(SPRINT_STATES2)).option("--start-date <date>", "New start date in ISO 8601 format", text).option("--end-date <date>", "New end date in ISO 8601 format", text).option("--goal <goal>", "New sprint goal", text);
2691
+ const cmd = parent.command("update").description("Update an existing sprint").addArgument(new Argument4("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name", text).addOption(new Option9("--state <state>", "New sprint state").choices(SPRINT_STATES2)).option("--start-date <date>", "New start date in ISO 8601 format", text).option("--end-date <date>", "New end date in ISO 8601 format", text).option("--goal <goal>", "New sprint goal", text);
2365
2692
  examples(cmd, [
2366
2693
  '100 --name "Sprint 10 - Extended"',
2367
2694
  "100 --state active",
@@ -2401,10 +2728,10 @@ function registerSprintCommands(program) {
2401
2728
 
2402
2729
  // src/commands/token/client.ts
2403
2730
  import { JiraClient as JiraClient2 } from "jira-data-center-client";
2404
- function getTokenClient(options2 = {}) {
2731
+ function getTokenClient(options = {}) {
2405
2732
  const baseUrl = process.env.JIRA_URL;
2406
- const username = options2.basicUsername ?? process.env.JIRA_BASIC_USERNAME;
2407
- const password = options2.basicPassword ?? process.env.JIRA_BASIC_PASSWORD;
2733
+ const username = options.basicUsername ?? process.env.JIRA_BASIC_USERNAME;
2734
+ const password = options.basicPassword ?? process.env.JIRA_BASIC_PASSWORD;
2408
2735
  if (!baseUrl || !username || !password) {
2409
2736
  const missing = [
2410
2737
  ...!baseUrl ? ["JIRA_URL"] : [],
@@ -2545,12 +2872,12 @@ function registerUserCommands(program) {
2545
2872
  function assertExactlyOne(opts, keys, label) {
2546
2873
  const present = keys.filter((k) => opts[k] !== void 0);
2547
2874
  if (present.length !== 1)
2548
- throw new Error(`Provide exactly one of ${keys.map((k) => `--${k}`).join(", ")} (${label}).`);
2875
+ throw new CliUsageError(`Provide exactly one of ${keys.map((k) => `--${k}`).join(", ")} (${label}).`);
2549
2876
  return present[0];
2550
2877
  }
2551
2878
  function assertAtLeastOne(opts, keys) {
2552
2879
  if (!keys.some((k) => opts[k] !== void 0))
2553
- throw new Error(`Provide at least one of ${keys.map((k) => `--${k}`).join(", ")}.`);
2880
+ throw new CliUsageError(`Provide at least one of ${keys.map((k) => `--${k}`).join(", ")}.`);
2554
2881
  }
2555
2882
 
2556
2883
  // src/commands/xray/execution/add.ts
@@ -2581,7 +2908,9 @@ function add(parent) {
2581
2908
  }
2582
2909
  const allKeys = [...keys];
2583
2910
  if (allKeys.length === 0) {
2584
- throw new Error("No tests to add \u2014 the given --set(s) contain no tests and no --test keys were provided.");
2911
+ throw new CliUsageError(
2912
+ "No tests to add \u2014 the given --set(s) contain no tests and no --test keys were provided."
2913
+ );
2585
2914
  }
2586
2915
  const result = await client.testExecutions.addTests({ execKey, keys: allKeys });
2587
2916
  output(result ?? { added: allKeys });
@@ -2825,7 +3154,7 @@ function update7(parent) {
2825
3154
  ]);
2826
3155
  cmd.action(async (folderId, opts) => {
2827
3156
  if (opts.name === void 0 && opts.rank === void 0) {
2828
- throw new Error("Provide at least one of --name or --rank.");
3157
+ throw new CliUsageError("Provide at least one of --name or --rank.");
2829
3158
  }
2830
3159
  const client = getClient();
2831
3160
  await client.testRepository.updateFolder({
@@ -2855,12 +3184,12 @@ function registerFolderCommands(xray) {
2855
3184
  // src/commands/xray/import/execution.ts
2856
3185
  import { readFileSync as readFileSync4 } from "fs";
2857
3186
  import { basename as basename2 } from "path";
2858
- import { Option as Option9 } from "commander";
3187
+ import { Option as Option10 } from "commander";
2859
3188
  var FORMATS = ["xray", "junit", "testng", "nunit", "xunit", "robot", "cucumber", "behave"];
2860
3189
  var NEEDS_SCOPE = /* @__PURE__ */ new Set(["junit", "testng", "nunit", "xunit", "robot"]);
2861
3190
  function execution(parent) {
2862
3191
  const cmd = parent.command("execution").description("Import test execution results into Xray").requiredOption("--file <path>", "Path to the result file", filePath).addOption(
2863
- new Option9(
3192
+ new Option10(
2864
3193
  "--format <format>",
2865
3194
  "Result format. xray/cucumber/behave: JSON body, no --project/--execution required. junit/testng/nunit/xunit/robot: XML body, requires --project OR --execution."
2866
3195
  ).choices(FORMATS).makeOptionMandatory()
@@ -2881,7 +3210,7 @@ function execution(parent) {
2881
3210
  cmd.action(
2882
3211
  async (opts) => {
2883
3212
  if (NEEDS_SCOPE.has(opts.format) && !opts.project && !opts.execution) {
2884
- throw new Error(
3213
+ throw new CliUsageError(
2885
3214
  `--project or --execution is required for ${opts.format} format. Provide --project <key> to create a new execution, or --execution <key> to import into an existing one.`
2886
3215
  );
2887
3216
  }
@@ -3058,7 +3387,7 @@ function add4(parent) {
3058
3387
  }
3059
3388
 
3060
3389
  // src/commands/xray/precondition/create.ts
3061
- import { Option as Option10 } from "commander";
3390
+ import { Option as Option11 } from "commander";
3062
3391
 
3063
3392
  // src/utils/xray/fields.ts
3064
3393
  var CONCEPT_SUFFIX = {
@@ -3089,18 +3418,22 @@ function resolveTestTypeValue(allowedValues, type) {
3089
3418
  if (type === "manual") {
3090
3419
  const match2 = allowedValues.find((v) => /manual/i.test(v));
3091
3420
  if (!match2)
3092
- throw new Error(`No 'manual' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`);
3421
+ throw new CliNotFoundError(
3422
+ `No 'manual' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
3423
+ );
3093
3424
  return match2;
3094
3425
  }
3095
3426
  if (type === "generic") {
3096
3427
  const match2 = allowedValues.find((v) => /generic/i.test(v));
3097
3428
  if (!match2)
3098
- throw new Error(`No 'generic' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`);
3429
+ throw new CliNotFoundError(
3430
+ `No 'generic' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
3431
+ );
3099
3432
  return match2;
3100
3433
  }
3101
3434
  const match = allowedValues.find((v) => !/manual/i.test(v) && !/generic/i.test(v));
3102
3435
  if (!match)
3103
- throw new Error(
3436
+ throw new CliNotFoundError(
3104
3437
  `No 'cucumber' (gherkin/automation) Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
3105
3438
  );
3106
3439
  return match;
@@ -3121,7 +3454,7 @@ async function resolveTypeValue(client, params) {
3121
3454
  }
3122
3455
  function requireField(map, concept, label) {
3123
3456
  const id = map[concept];
3124
- if (!id) throw new Error(`Xray field for '${label}' not found on this instance.`);
3457
+ if (!id) throw new CliNotFoundError(`Xray field for '${label}' not found on this instance.`);
3125
3458
  return id;
3126
3459
  }
3127
3460
  function buildTestCustomFields(map, opts) {
@@ -3159,7 +3492,7 @@ function create10(parent) {
3159
3492
  const cmd = parent.command("create").description(
3160
3493
  "Create a new Xray Pre-Condition issue. Note: the Pre-Condition issue type is only available in projects that have it configured (e.g. BD); it is not on your project scheme."
3161
3494
  ).requiredOption("--project <key>", "Project key (e.g. BD)", text).requiredOption("--summary <text>", "Pre-Condition summary (title)", text).addOption(
3162
- new Option10("--type <type>", "Pre-Condition type").choices(["manual", "generic", "cucumber"]).default("manual")
3495
+ new Option11("--type <type>", "Pre-Condition type").choices(["manual", "generic", "cucumber"]).default("manual")
3163
3496
  );
3164
3497
  textOrFileOption(cmd, "condition", { description: "Pre-condition body / definition text" });
3165
3498
  textOrFileOption(cmd, "description", { description: "Pre-Condition issue description" });
@@ -3230,9 +3563,9 @@ function remove4(parent) {
3230
3563
  }
3231
3564
 
3232
3565
  // src/commands/xray/precondition/update.ts
3233
- import { Option as Option11 } from "commander";
3566
+ import { Option as Option12 } from "commander";
3234
3567
  function update9(parent) {
3235
- const cmd = parent.command("update").description("Update an Xray Pre-Condition issue").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option11("--type <type>", "New Pre-Condition type").choices(["manual", "generic", "cucumber"]));
3568
+ const cmd = parent.command("update").description("Update an Xray Pre-Condition issue").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option12("--type <type>", "New Pre-Condition type").choices(["manual", "generic", "cucumber"]));
3236
3569
  textOrFileOption(cmd, "condition", { description: "New pre-condition body / definition text" });
3237
3570
  textOrFileOption(cmd, "description", { description: "New Pre-Condition issue description" });
3238
3571
  examples(cmd, ['PROJ-50 --summary "Updated precondition"', 'PROJ-50 --type generic --condition "apiKey != null"']);
@@ -3255,12 +3588,12 @@ function update9(parent) {
3255
3588
  fieldMap: map
3256
3589
  });
3257
3590
  const typeId = map["precondition-type"];
3258
- if (!typeId) throw new Error("Xray field for 'Pre-Condition Type' not found on this instance.");
3591
+ if (!typeId) throw new CliNotFoundError("Xray field for 'Pre-Condition Type' not found on this instance.");
3259
3592
  customFields[typeId] = { value: typeValue };
3260
3593
  }
3261
3594
  if (condition !== void 0) {
3262
3595
  const bodyId = map["precondition-body"];
3263
- if (!bodyId) throw new Error("Xray field for 'Conditions' not found on this instance.");
3596
+ if (!bodyId) throw new CliNotFoundError("Xray field for 'Conditions' not found on this instance.");
3264
3597
  customFields[bodyId] = condition;
3265
3598
  }
3266
3599
  }
@@ -3291,9 +3624,10 @@ function registerPreconditionCommands(xray) {
3291
3624
  // src/utils/xray/run-id.ts
3292
3625
  async function resolveRunId(client, o) {
3293
3626
  if (o.runId !== void 0) return o.runId;
3294
- if (!o.execution || !o.test) throw new Error("Provide a run id, or both --execution and --test to resolve the run.");
3627
+ if (!o.execution || !o.test)
3628
+ throw new CliUsageError("Provide a run id, or both --execution and --test to resolve the run.");
3295
3629
  const run = await client.testRuns.resolve({ execKey: o.execution, testKey: o.test });
3296
- if (!run?.id) throw new Error(`No test run found for ${o.test} in ${o.execution}.`);
3630
+ if (!run?.id) throw new CliNotFoundError(`No test run found for ${o.test} in ${o.execution}.`);
3297
3631
  return Number(run.id);
3298
3632
  }
3299
3633
 
@@ -3665,7 +3999,7 @@ function update12(parent) {
3665
3999
  const data = resolveTextOrFile(opts, "data", { required: false });
3666
4000
  const result = resolveTextOrFile(opts, "result", { required: false });
3667
4001
  if (action === void 0 && data === void 0 && result === void 0) {
3668
- throw new Error(
4002
+ throw new CliUsageError(
3669
4003
  "Provide at least one of --action (or --action-file), --data (or --data-file), --result (or --result-file)."
3670
4004
  );
3671
4005
  }
@@ -3689,16 +4023,16 @@ function registerStepCommands(xray) {
3689
4023
  }
3690
4024
 
3691
4025
  // src/commands/xray/test/create.ts
3692
- import { Option as Option12 } from "commander";
3693
- import { z as z4 } from "zod";
3694
- var stepsSchema = z4.array(
3695
- z4.object({ action: z4.string(), data: z4.string().optional(), result: z4.string().optional() })
4026
+ import { Option as Option13 } from "commander";
4027
+ import { z as z5 } from "zod";
4028
+ var stepsSchema = z5.array(
4029
+ z5.object({ action: z5.string(), data: z5.string().optional(), result: z5.string().optional() })
3696
4030
  );
3697
4031
  function create11(parent) {
3698
4032
  const cmd = parent.command("create").description("Create a new Xray Test issue").requiredOption("--project <key>", "Project key (e.g. AI)", text).requiredOption("--summary <text>", "Test summary (title)", text).addOption(
3699
- new Option12("--type <type>", "Test type (when cucumber: also pass --gherkin; when generic: --definition)").choices(["manual", "cucumber", "generic"]).default("manual")
4033
+ new Option13("--type <type>", "Test type (when cucumber: also pass --gherkin; when generic: --definition)").choices(["manual", "cucumber", "generic"]).default("manual")
3700
4034
  ).addOption(
3701
- new Option12("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
4035
+ new Option13("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
3702
4036
  "scenario",
3703
4037
  "scenario-outline"
3704
4038
  ])
@@ -3726,10 +4060,10 @@ function create11(parent) {
3726
4060
  const definition = resolveTextOrFile(opts, "definition", { required: false });
3727
4061
  const description = resolveTextOrFile(opts, "description", { required: false });
3728
4062
  if (opts.type === "cucumber" && !gherkin) {
3729
- throw new Error("--gherkin (or --gherkin-file) is required when --type is cucumber.");
4063
+ throw new CliUsageError("--gherkin (or --gherkin-file) is required when --type is cucumber.");
3730
4064
  }
3731
4065
  if (opts.type === "generic" && !definition) {
3732
- throw new Error("--definition (or --definition-file) is required when --type is generic.");
4066
+ throw new CliUsageError("--definition (or --definition-file) is required when --type is generic.");
3733
4067
  }
3734
4068
  const cucumberType = opts.cucumberType === "scenario" ? "Scenario" : opts.cucumberType === "scenario-outline" ? "Scenario Outline" : void 0;
3735
4069
  const client = getClient();
@@ -3804,13 +4138,13 @@ function list18(parent) {
3804
4138
  const client = getClient();
3805
4139
  if (opts.folder !== void 0) {
3806
4140
  if (!opts.project) {
3807
- throw new Error("--project is required when --folder is specified.");
4141
+ throw new CliUsageError("--project is required when --folder is specified.");
3808
4142
  }
3809
4143
  const otherScopes = ["set", "plan", "execution", "precondition"].filter(
3810
4144
  (k) => opts[k] !== void 0
3811
4145
  );
3812
4146
  if (otherScopes.length > 0) {
3813
- throw new Error(
4147
+ throw new CliUsageError(
3814
4148
  `--folder cannot be combined with ${otherScopes.map((k) => `--${k}`).join(", ")}. Use --folder with --project only.`
3815
4149
  );
3816
4150
  }
@@ -3868,10 +4202,10 @@ function list18(parent) {
3868
4202
  }
3869
4203
 
3870
4204
  // src/commands/xray/test/update.ts
3871
- import { Option as Option13 } from "commander";
4205
+ import { Option as Option14 } from "commander";
3872
4206
  function update13(parent) {
3873
- const cmd = parent.command("update").description("Update an Xray Test issue").argument("<testKey>", "Test issue key (e.g. PROJ-584)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option13("--type <type>", "Test type").choices(["manual", "cucumber", "generic"])).addOption(
3874
- new Option13("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
4207
+ const cmd = parent.command("update").description("Update an Xray Test issue").argument("<testKey>", "Test issue key (e.g. PROJ-584)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option14("--type <type>", "Test type").choices(["manual", "cucumber", "generic"])).addOption(
4208
+ new Option14("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
3875
4209
  "scenario",
3876
4210
  "scenario-outline"
3877
4211
  ])
@@ -3902,10 +4236,10 @@ function update13(parent) {
3902
4236
  if (opts.reporter !== void 0) fields.reporter = { name: opts.reporter };
3903
4237
  if (opts.type !== void 0) {
3904
4238
  if (opts.type === "cucumber" && !gherkin) {
3905
- throw new Error("--gherkin (or --gherkin-file) is required when --type is cucumber.");
4239
+ throw new CliUsageError("--gherkin (or --gherkin-file) is required when --type is cucumber.");
3906
4240
  }
3907
4241
  if (opts.type === "generic" && !definition) {
3908
- throw new Error("--definition (or --definition-file) is required when --type is generic.");
4242
+ throw new CliUsageError("--definition (or --definition-file) is required when --type is generic.");
3909
4243
  }
3910
4244
  const cucumberType = opts.cucumberType === "scenario" ? "Scenario" : opts.cucumberType === "scenario-outline" ? "Scenario Outline" : void 0;
3911
4245
  const projectKey = testKey.split("-")[0];
@@ -4059,7 +4393,7 @@ function registerXrayCommands(program) {
4059
4393
  var DIM = "\x1B[2m";
4060
4394
  var RESET = "\x1B[0m";
4061
4395
  function buildProgram() {
4062
- const program = new Command16();
4396
+ const program = new Command17();
4063
4397
  program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion(import.meta.url)).configureHelp({
4064
4398
  styleTitle: (str) => styleText("bold", str),
4065
4399
  styleUsage: (str) => styleText("dim", str),
@@ -4120,10 +4454,33 @@ function getCredentialInfo() {
4120
4454
  };
4121
4455
  }
4122
4456
 
4457
+ // src/utils/field-error-recovery.ts
4458
+ var CUSTOM_FIELD_KEY = /^customfield_(\d+)$/;
4459
+ function customFieldIds(detail) {
4460
+ if (!detail || typeof detail !== "object") return [];
4461
+ const errors = detail.errors;
4462
+ if (!errors || typeof errors !== "object") return [];
4463
+ return Object.keys(errors).filter((k) => CUSTOM_FIELD_KEY.test(k));
4464
+ }
4465
+ function adaptFieldErrors(normalized) {
4466
+ const ids = customFieldIds(normalized.detail);
4467
+ if (ids.length === 0) return normalized;
4468
+ const numbers = ids.map((id) => CUSTOM_FIELD_KEY.exec(id)?.[1] ?? id);
4469
+ const lookup = numbers.map((n) => `jiradc field search ${n}`).join(", ");
4470
+ const plural = ids.length > 1 ? "s" : "";
4471
+ return {
4472
+ ...normalized,
4473
+ recovery: `Jira rejected field${plural} ${ids.join(", ")}. Run ${lookup} to see the field's name and type before retrying. For the values a field accepts, use issue transitions (transition screens), issue createmeta (create) or issue editmeta (edit).`
4474
+ };
4475
+ }
4476
+
4123
4477
  // src/index.ts
4124
4478
  await runCli(buildProgram(), {
4125
4479
  credentialInfo: getCredentialInfo,
4126
4480
  service: "Jira",
4127
4481
  authRecovery: "Set the JIRA_URL and JIRA_TOKEN environment variables.",
4128
- urlEnvVar: "JIRA_URL"
4482
+ urlEnvVar: "JIRA_URL",
4483
+ // Jira names rejected fields by custom-field id. The generic recovery leaves
4484
+ // an agent holding an opaque id, so point it at the command that explains one.
4485
+ adapt: adaptFieldErrors
4129
4486
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jiradc-cli",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
4
4
  "publish": true,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -13,7 +13,7 @@
13
13
  "dependencies": {
14
14
  "commander": "^13.1.0",
15
15
  "zod": "^3.25.67",
16
- "jira-data-center-client": "1.0.42"
16
+ "jira-data-center-client": "1.0.44"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "24.10.4",