jiradc-cli 1.0.32 → 1.0.33

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 +246 -111
  3. package/package.json +4 -4
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
@@ -239,6 +239,7 @@ var CliAuthError = class extends Error {
239
239
  };
240
240
  var NOT_FOUND_RECOVERY = "Verify the id/key, then check your access.";
241
241
  var NOT_FOUND_MESSAGE = "Not found: it may not exist, or you may not have permission to see it";
242
+ var USAGE_RECOVERY = "Check the command syntax and flags; run the command with --help.";
242
243
  var CliNotFoundError = class extends Error {
243
244
  recovery;
244
245
  constructor(message, recovery = NOT_FOUND_RECOVERY) {
@@ -247,6 +248,16 @@ var CliNotFoundError = class extends Error {
247
248
  this.recovery = recovery;
248
249
  }
249
250
  };
251
+ var CliUsageError = class extends Error {
252
+ recovery;
253
+ detail;
254
+ constructor(message, recovery = USAGE_RECOVERY, detail) {
255
+ super(message);
256
+ this.name = "CliUsageError";
257
+ this.recovery = recovery;
258
+ this.detail = detail;
259
+ }
260
+ };
250
261
  var SubcommandRequiredError = class extends Error {
251
262
  subcommands;
252
263
  constructor(commandPath3, subcommands) {
@@ -363,6 +374,15 @@ function normalize(err, opts) {
363
374
  if (err instanceof CliNotFoundError) {
364
375
  return { type: "not_found", message, recovery: err.recovery, retryable: false };
365
376
  }
377
+ if (err instanceof CliUsageError) {
378
+ return {
379
+ type: "usage",
380
+ message,
381
+ recovery: err.recovery,
382
+ retryable: false,
383
+ ...err.detail !== void 0 ? { detail: err.detail } : {}
384
+ };
385
+ }
366
386
  const status = httpStatus(err);
367
387
  const detail = responseDetail(err);
368
388
  if (status !== void 0) {
@@ -568,7 +588,7 @@ async function runCli(program, opts) {
568
588
 
569
589
  // src/program.ts
570
590
  import { styleText } from "util";
571
- import { Command as Command16 } from "commander";
591
+ import { Command as Command17 } from "commander";
572
592
 
573
593
  // src/commands/board/issues.ts
574
594
  import { Argument } from "commander";
@@ -623,9 +643,28 @@ function transformUser(user) {
623
643
  function transformField(field) {
624
644
  return field;
625
645
  }
626
- function transformCustomFieldOption(option) {
627
- const { self: _self, ...rest } = option;
628
- return rest;
646
+
647
+ // src/utils/transformers/field-meta.ts
648
+ function slimAllowedValue(raw) {
649
+ const out = {};
650
+ if (typeof raw.id === "string") out.id = raw.id;
651
+ if (typeof raw.name === "string") out.name = raw.name;
652
+ if (typeof raw.value === "string") out.value = raw.value;
653
+ if (Array.isArray(raw.children) && raw.children.length > 0) out.children = raw.children.map(slimAllowedValue);
654
+ return out;
655
+ }
656
+ function transformFieldMetaEntry(id, meta) {
657
+ const out = { id, required: meta.required === true };
658
+ if (typeof meta.name === "string") out.name = meta.name;
659
+ if (typeof meta.schema?.type === "string") out.type = meta.schema.type;
660
+ if (Array.isArray(meta.allowedValues) && meta.allowedValues.length > 0) {
661
+ out.allowedValues = meta.allowedValues.map(slimAllowedValue);
662
+ }
663
+ return out;
664
+ }
665
+ function transformFieldMeta(fields) {
666
+ const entries = Object.entries(fields).map(([id, meta]) => transformFieldMetaEntry(id, meta));
667
+ return [...entries.filter((f) => f.required), ...entries.filter((f) => !f.required)];
629
668
  }
630
669
 
631
670
  // src/utils/transformers/project.ts
@@ -807,6 +846,29 @@ function pruneSentinels(fields) {
807
846
  if (watches && watches.watchCount === 0) delete out.watches;
808
847
  return out;
809
848
  }
849
+ var ISSUE_KEY_PATTERN = /^[A-Z][A-Z0-9_]*-\d+$/;
850
+ function asIssueRefKey(value) {
851
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
852
+ const o = value;
853
+ if (typeof o.key !== "string" || !ISSUE_KEY_PATTERN.test(o.key)) return void 0;
854
+ if (typeof o.self !== "string" || !o.self.includes("/issue/")) return void 0;
855
+ return o.key;
856
+ }
857
+ function collapseIssueRefs(fields) {
858
+ const out = { ...fields };
859
+ for (const [key, value] of Object.entries(out)) {
860
+ const single = asIssueRefKey(value);
861
+ if (single !== void 0) {
862
+ out[key] = single;
863
+ continue;
864
+ }
865
+ if (Array.isArray(value) && value.length > 0) {
866
+ const keys = value.map(asIssueRefKey);
867
+ if (keys.every((k) => k !== void 0)) out[key] = keys;
868
+ }
869
+ }
870
+ return out;
871
+ }
810
872
  function transformIssueFields(fields) {
811
873
  const {
812
874
  // shaped sub-entities (recursed individually)
@@ -833,7 +895,7 @@ function transformIssueFields(fields) {
833
895
  // everything else: optional scalars, customfield_*, etc. — gets compacted
834
896
  ...rest
835
897
  } = fields;
836
- const compacted = pruneSentinels(compactRecord(rest));
898
+ const compacted = collapseIssueRefs(pruneSentinels(compactRecord(rest)));
837
899
  return {
838
900
  ...compacted,
839
901
  summary,
@@ -876,23 +938,23 @@ function transformIssueFields(fields) {
876
938
  }
877
939
 
878
940
  // src/utils/transformers/transition.ts
879
- function slimAllowedValue(raw) {
941
+ function slimAllowedValue2(raw) {
880
942
  if (!raw || typeof raw !== "object") return {};
881
943
  const av = raw;
882
944
  const slim = {};
883
945
  if (typeof av.id === "string") slim.id = av.id;
884
946
  if (typeof av.name === "string") slim.name = av.name;
885
947
  if (typeof av.value === "string") slim.value = av.value;
948
+ if (Array.isArray(av.children) && av.children.length > 0) slim.children = av.children.map(slimAllowedValue2);
886
949
  return slim;
887
950
  }
888
951
  function slimField(raw) {
889
952
  if (!raw || typeof raw !== "object") return {};
890
953
  const field = raw;
891
954
  const out = {};
892
- if (typeof field.required === "boolean") out.required = field.required;
893
955
  if (typeof field.name === "string") out.name = field.name;
894
956
  if (Array.isArray(field.allowedValues)) {
895
- out.allowedValues = field.allowedValues.map(slimAllowedValue);
957
+ out.allowedValues = field.allowedValues.map(slimAllowedValue2);
896
958
  }
897
959
  return out;
898
960
  }
@@ -1190,7 +1252,7 @@ function update(parent) {
1190
1252
  async (id, opts) => {
1191
1253
  const description = resolveTextOrFile(opts, "description", { required: false });
1192
1254
  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");
1255
+ throw new CliUsageError("Provide at least one of --name, --description, --lead, --assignee-type");
1194
1256
  }
1195
1257
  const client = getClient();
1196
1258
  const result = await client.components.update({
@@ -1224,22 +1286,6 @@ function registerComponentCommands(program) {
1224
1286
  issueCount(component);
1225
1287
  }
1226
1288
 
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
1289
  // src/commands/field/search.ts
1244
1290
  function search(parent) {
1245
1291
  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 +1300,8 @@ function search(parent) {
1254
1300
  // src/commands/field/index.ts
1255
1301
  function registerFieldCommands(program) {
1256
1302
  const field = program.command("field").description("Field operations");
1257
- examples(field, ['search "epic"', 'search "priority"', "options 10120"]);
1303
+ examples(field, ['search "epic"', 'search "priority"']);
1258
1304
  search(field);
1259
- options(field);
1260
1305
  }
1261
1306
 
1262
1307
  // src/utils/resolve-user.ts
@@ -1667,13 +1712,16 @@ function registerCommentCommands(parent) {
1667
1712
  }
1668
1713
 
1669
1714
  // src/commands/issue/create.ts
1715
+ import { Option as Option5 } from "commander";
1670
1716
  import { z as z2 } from "zod";
1671
- var customFieldsSchema = z2.record(z2.unknown());
1717
+ var fieldsSchema = z2.record(z2.unknown());
1672
1718
  function create3(parent) {
1673
1719
  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)
1720
+ "--fields <json>",
1721
+ `Additional fields as JSON (e.g., '{"customfield_10100": "EPIC-1"}')`,
1722
+ jsonShape(fieldsSchema)
1723
+ ).addOption(
1724
+ new Option5("--custom-fields <json>", "Deprecated alias for --fields").argParser(jsonShape(fieldsSchema)).hideHelp()
1677
1725
  );
1678
1726
  textOrFileOption(cmd, "description", { description: "Issue description in wiki markup" });
1679
1727
  examples(cmd, [
@@ -1684,6 +1732,13 @@ function create3(parent) {
1684
1732
  cmd.action(
1685
1733
  async (opts) => {
1686
1734
  const description = resolveTextOrFile(opts, "description", { required: false });
1735
+ if (opts.fields && opts.customFields) {
1736
+ throw new CliUsageError(
1737
+ "Pass either --fields or --custom-fields, not both",
1738
+ "--custom-fields is a deprecated alias for --fields. Merge the two JSON objects into a single --fields value."
1739
+ );
1740
+ }
1741
+ const fields = opts.fields ?? opts.customFields;
1687
1742
  const assignee = opts.assignee !== void 0 ? await resolveUserToken(opts.assignee) : void 0;
1688
1743
  const reporter = opts.reporter !== void 0 ? await resolveUserToken(opts.reporter) : void 0;
1689
1744
  const client = getClient();
@@ -1700,13 +1755,31 @@ function create3(parent) {
1700
1755
  fixVersions: opts.fixVersions?.split(",").map((v) => v.trim()),
1701
1756
  dueDate: opts.dueDate,
1702
1757
  parent: opts.parent,
1703
- customFields: opts.customFields
1758
+ customFields: fields
1704
1759
  });
1705
1760
  output(transformCreatedIssue(result));
1706
1761
  }
1707
1762
  );
1708
1763
  }
1709
1764
 
1765
+ // src/commands/issue/createmeta.ts
1766
+ function createmeta(parent) {
1767
+ 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);
1768
+ examples(cmd, ["--project PROJ --type Task", "--project PROJ --type Bug"]);
1769
+ cmd.action(async (opts) => {
1770
+ const client = getClient();
1771
+ const meta = await client.issues.getCreateMeta({ projectKey: opts.project, issueTypeName: opts.type });
1772
+ const issueType = meta.projects?.[0]?.issuetypes?.[0];
1773
+ if (!issueType?.fields) {
1774
+ throw new CliNotFoundError(
1775
+ `No create metadata for issue type "${opts.type}" in project "${opts.project}"`,
1776
+ "Verify the project key and issue type name, then check you may create that type there."
1777
+ );
1778
+ }
1779
+ output(transformFieldMeta(issueType.fields));
1780
+ });
1781
+ }
1782
+
1710
1783
  // src/commands/issue/delete.ts
1711
1784
  function deleteIssue(parent) {
1712
1785
  const cmd = parent.command("delete").description("Delete an issue").argument("<key>", "Issue key", issueKey).option("--delete-subtasks", "Also delete subtasks (default: false)");
@@ -1796,6 +1869,17 @@ function devStatus(parent) {
1796
1869
  });
1797
1870
  }
1798
1871
 
1872
+ // src/commands/issue/editmeta.ts
1873
+ function editmeta(parent) {
1874
+ 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);
1875
+ examples(cmd, ["PROJ-123"]);
1876
+ cmd.action(async (key) => {
1877
+ const client = getClient();
1878
+ const meta = await client.issues.getEditMeta({ issueKeyOrId: key });
1879
+ output(transformFieldMeta(meta.fields ?? {}));
1880
+ });
1881
+ }
1882
+
1799
1883
  // src/utils/constants.ts
1800
1884
  var DEFAULT_FIELDS = [
1801
1885
  "summary",
@@ -1924,41 +2008,60 @@ function search2(parent) {
1924
2008
  }
1925
2009
 
1926
2010
  // src/commands/issue/transition.ts
2011
+ import { z as z3 } from "zod";
2012
+ var fieldsSchema2 = z3.record(z3.unknown());
2013
+ function resolveTransitionId(to, transitions2) {
2014
+ const target = to.toLowerCase();
2015
+ const byName = transitions2.filter((t) => t.name.toLowerCase() === target);
2016
+ const matches = byName.length > 0 ? byName : transitions2.filter((t) => t.to.toLowerCase() === target);
2017
+ if (matches.length === 1) return matches[0].id;
2018
+ const available = transitions2.map((t) => ({ id: t.id, name: t.name, to: t.to }));
2019
+ if (matches.length === 0) {
2020
+ throw new CliUsageError(
2021
+ `Transition "${to}" not available from current status`,
2022
+ "Re-run --to with one of the listed transitions (by name, id, or target status).",
2023
+ { available }
2024
+ );
2025
+ }
2026
+ const ids = matches.map((t) => t.id).join(", ");
2027
+ throw new CliUsageError(
2028
+ `"${to}" matches more than one available transition`,
2029
+ `Re-run --to with one of these ids: ${ids}.`,
2030
+ { available }
2031
+ );
2032
+ }
1927
2033
  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);
2034
+ 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(
2035
+ "--fields <json>",
2036
+ `Fields required by the transition screen, as JSON (e.g. '{"customfield_10100": {"value": "Approved"}}')`,
2037
+ jsonShape(fieldsSchema2)
2038
+ );
1929
2039
  commentOption(cmd, { description: "Comment to add during transition" });
1930
2040
  examples(cmd, [
1931
2041
  "PROJ-123 --to 31",
2042
+ 'PROJ-123 --to "Start Review"',
1932
2043
  'PROJ-123 --to "In Review"',
1933
- 'PROJ-123 --to Done --comment "Verified in staging"'
2044
+ 'PROJ-123 --to Done --comment "Verified in staging"',
2045
+ `PROJ-123 --to Approve --fields '{"customfield_10100": {"value": "Approved"}}'`
1934
2046
  ]);
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}`);
2047
+ cmd.action(
2048
+ async (key, opts) => {
2049
+ const comment = resolveTextOrFile(opts, "comment", { required: false });
2050
+ const client = getClient();
2051
+ let transitionId;
2052
+ if (/^\d+$/.test(opts.to)) {
2053
+ transitionId = opts.to;
2054
+ } else {
2055
+ const raw = await client.issues.getTransitions({
2056
+ issueKeyOrId: key,
2057
+ expand: "transitions.fields"
2058
+ });
2059
+ transitionId = resolveTransitionId(opts.to, transformTransitions(raw));
1956
2060
  }
1957
- transitionId = matches[0].id;
2061
+ await client.issues.transition({ issueKeyOrId: key, transitionId, fields: opts.fields, comment });
2062
+ output({ transitioned: true, issue: transformIssueRef(key) });
1958
2063
  }
1959
- await client.issues.transition({ issueKeyOrId: key, transitionId, comment });
1960
- output({ transitioned: true, issue: transformIssueRef(key) });
1961
- });
2064
+ );
1962
2065
  }
1963
2066
 
1964
2067
  // src/commands/issue/transitions.ts
@@ -1987,7 +2090,7 @@ function unlink2(parent) {
1987
2090
  }
1988
2091
 
1989
2092
  // src/commands/issue/update.ts
1990
- import { z as z3 } from "zod";
2093
+ import { z as z4 } from "zod";
1991
2094
 
1992
2095
  // src/utils/multi-value.ts
1993
2096
  import { InvalidArgumentError as InvalidArgumentError5 } from "commander";
@@ -2016,7 +2119,7 @@ function parseMultiValue(flagName, raw) {
2016
2119
  }
2017
2120
 
2018
2121
  // src/commands/issue/update.ts
2019
- var fieldsSchema = z3.record(z3.unknown());
2122
+ var fieldsSchema3 = z4.record(z4.unknown());
2020
2123
  function buildUpdateOps(parsed, wrap) {
2021
2124
  if (parsed.mode !== "mutate") return void 0;
2022
2125
  return [...parsed.adds.map((v) => ({ add: wrap(v) })), ...parsed.removes.map((v) => ({ remove: wrap(v) }))];
@@ -2029,7 +2132,7 @@ function update3(parent) {
2029
2132
  const cmd = parent.command("update").description("Update issue fields").argument("<key>", "Issue key", issueKey).option(
2030
2133
  "--fields <json>",
2031
2134
  "JSON string of fields to update (advanced; merges with shortcuts, wins on conflict)",
2032
- jsonShape(fieldsSchema)
2135
+ jsonShape(fieldsSchema3)
2033
2136
  ).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
2137
  textOrFileOption(cmd, "description", { description: "Set the issue description (wiki markup)" });
2035
2138
  examples(cmd, [
@@ -2044,7 +2147,7 @@ function update3(parent) {
2044
2147
  const description = resolveTextOrFile(opts, "description", { required: false });
2045
2148
  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
2149
  if (!opts.fields && !opts.attachments && !hasShortcut) {
2047
- throw new Error(
2150
+ throw new CliUsageError(
2048
2151
  "Provide at least one of --fields, --attachments, or a shortcut flag (--summary, --priority, --labels, ...)"
2049
2152
  );
2050
2153
  }
@@ -2132,12 +2235,12 @@ function create4(parent) {
2132
2235
  }
2133
2236
 
2134
2237
  // src/commands/issue/worklog/delete.ts
2135
- import { Option as Option5 } from "commander";
2238
+ import { Option as Option6 } from "commander";
2136
2239
  var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
2137
2240
  function deleteWorklog(parent) {
2138
2241
  const cmd = parent.command("delete").description("Delete a worklog entry").argument("<key>", "Issue key", issueKey);
2139
2242
  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(
2243
+ 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
2244
  "--increase-by <amount>",
2142
2245
  'Amount to increase the estimate by; required when --adjust-estimate is "manual"',
2143
2246
  text
@@ -2178,12 +2281,12 @@ function list4(parent) {
2178
2281
  }
2179
2282
 
2180
2283
  // src/commands/issue/worklog/update.ts
2181
- import { Option as Option6 } from "commander";
2284
+ import { Option as Option7 } from "commander";
2182
2285
  var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
2183
2286
  function update4(parent) {
2184
2287
  const cmd = parent.command("update").description("Update an existing worklog entry").argument("<key>", "Issue key", issueKey);
2185
2288
  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);
2289
+ 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
2290
  commentOption(cmd, { description: "Worklog comment" });
2188
2291
  examples(cmd, [
2189
2292
  'PROJ-123 --worklog-id 12345 --time "1h 30m"',
@@ -2241,6 +2344,8 @@ function registerIssueCommands(program) {
2241
2344
  deleteIssue(issue);
2242
2345
  transition(issue);
2243
2346
  transitions(issue);
2347
+ createmeta(issue);
2348
+ editmeta(issue);
2244
2349
  assign(issue);
2245
2350
  registerCommentCommands(issue);
2246
2351
  registerWorklogCommands(issue);
@@ -2342,10 +2447,10 @@ function issues2(parent) {
2342
2447
  }
2343
2448
 
2344
2449
  // src/commands/sprint/list.ts
2345
- import { Option as Option7 } from "commander";
2450
+ import { Option as Option8 } from "commander";
2346
2451
  var SPRINT_STATES = ["future", "active", "closed"];
2347
2452
  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));
2453
+ 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
2454
  examples(cmd, ["--board 42", "--board 42 --state active"]);
2350
2455
  cmd.action(async (opts) => {
2351
2456
  const client = getClient();
@@ -2358,10 +2463,10 @@ function list6(parent) {
2358
2463
  }
2359
2464
 
2360
2465
  // src/commands/sprint/update.ts
2361
- import { Argument as Argument4, Option as Option8 } from "commander";
2466
+ import { Argument as Argument4, Option as Option9 } from "commander";
2362
2467
  var SPRINT_STATES2 = ["future", "active", "closed"];
2363
2468
  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);
2469
+ 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
2470
  examples(cmd, [
2366
2471
  '100 --name "Sprint 10 - Extended"',
2367
2472
  "100 --state active",
@@ -2401,10 +2506,10 @@ function registerSprintCommands(program) {
2401
2506
 
2402
2507
  // src/commands/token/client.ts
2403
2508
  import { JiraClient as JiraClient2 } from "jira-data-center-client";
2404
- function getTokenClient(options2 = {}) {
2509
+ function getTokenClient(options = {}) {
2405
2510
  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;
2511
+ const username = options.basicUsername ?? process.env.JIRA_BASIC_USERNAME;
2512
+ const password = options.basicPassword ?? process.env.JIRA_BASIC_PASSWORD;
2408
2513
  if (!baseUrl || !username || !password) {
2409
2514
  const missing = [
2410
2515
  ...!baseUrl ? ["JIRA_URL"] : [],
@@ -2545,12 +2650,12 @@ function registerUserCommands(program) {
2545
2650
  function assertExactlyOne(opts, keys, label) {
2546
2651
  const present = keys.filter((k) => opts[k] !== void 0);
2547
2652
  if (present.length !== 1)
2548
- throw new Error(`Provide exactly one of ${keys.map((k) => `--${k}`).join(", ")} (${label}).`);
2653
+ throw new CliUsageError(`Provide exactly one of ${keys.map((k) => `--${k}`).join(", ")} (${label}).`);
2549
2654
  return present[0];
2550
2655
  }
2551
2656
  function assertAtLeastOne(opts, keys) {
2552
2657
  if (!keys.some((k) => opts[k] !== void 0))
2553
- throw new Error(`Provide at least one of ${keys.map((k) => `--${k}`).join(", ")}.`);
2658
+ throw new CliUsageError(`Provide at least one of ${keys.map((k) => `--${k}`).join(", ")}.`);
2554
2659
  }
2555
2660
 
2556
2661
  // src/commands/xray/execution/add.ts
@@ -2581,7 +2686,9 @@ function add(parent) {
2581
2686
  }
2582
2687
  const allKeys = [...keys];
2583
2688
  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.");
2689
+ throw new CliUsageError(
2690
+ "No tests to add \u2014 the given --set(s) contain no tests and no --test keys were provided."
2691
+ );
2585
2692
  }
2586
2693
  const result = await client.testExecutions.addTests({ execKey, keys: allKeys });
2587
2694
  output(result ?? { added: allKeys });
@@ -2825,7 +2932,7 @@ function update7(parent) {
2825
2932
  ]);
2826
2933
  cmd.action(async (folderId, opts) => {
2827
2934
  if (opts.name === void 0 && opts.rank === void 0) {
2828
- throw new Error("Provide at least one of --name or --rank.");
2935
+ throw new CliUsageError("Provide at least one of --name or --rank.");
2829
2936
  }
2830
2937
  const client = getClient();
2831
2938
  await client.testRepository.updateFolder({
@@ -2855,12 +2962,12 @@ function registerFolderCommands(xray) {
2855
2962
  // src/commands/xray/import/execution.ts
2856
2963
  import { readFileSync as readFileSync4 } from "fs";
2857
2964
  import { basename as basename2 } from "path";
2858
- import { Option as Option9 } from "commander";
2965
+ import { Option as Option10 } from "commander";
2859
2966
  var FORMATS = ["xray", "junit", "testng", "nunit", "xunit", "robot", "cucumber", "behave"];
2860
2967
  var NEEDS_SCOPE = /* @__PURE__ */ new Set(["junit", "testng", "nunit", "xunit", "robot"]);
2861
2968
  function execution(parent) {
2862
2969
  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(
2970
+ new Option10(
2864
2971
  "--format <format>",
2865
2972
  "Result format. xray/cucumber/behave: JSON body, no --project/--execution required. junit/testng/nunit/xunit/robot: XML body, requires --project OR --execution."
2866
2973
  ).choices(FORMATS).makeOptionMandatory()
@@ -2881,7 +2988,7 @@ function execution(parent) {
2881
2988
  cmd.action(
2882
2989
  async (opts) => {
2883
2990
  if (NEEDS_SCOPE.has(opts.format) && !opts.project && !opts.execution) {
2884
- throw new Error(
2991
+ throw new CliUsageError(
2885
2992
  `--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
2993
  );
2887
2994
  }
@@ -3058,7 +3165,7 @@ function add4(parent) {
3058
3165
  }
3059
3166
 
3060
3167
  // src/commands/xray/precondition/create.ts
3061
- import { Option as Option10 } from "commander";
3168
+ import { Option as Option11 } from "commander";
3062
3169
 
3063
3170
  // src/utils/xray/fields.ts
3064
3171
  var CONCEPT_SUFFIX = {
@@ -3089,18 +3196,22 @@ function resolveTestTypeValue(allowedValues, type) {
3089
3196
  if (type === "manual") {
3090
3197
  const match2 = allowedValues.find((v) => /manual/i.test(v));
3091
3198
  if (!match2)
3092
- throw new Error(`No 'manual' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`);
3199
+ throw new CliNotFoundError(
3200
+ `No 'manual' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
3201
+ );
3093
3202
  return match2;
3094
3203
  }
3095
3204
  if (type === "generic") {
3096
3205
  const match2 = allowedValues.find((v) => /generic/i.test(v));
3097
3206
  if (!match2)
3098
- throw new Error(`No 'generic' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`);
3207
+ throw new CliNotFoundError(
3208
+ `No 'generic' Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
3209
+ );
3099
3210
  return match2;
3100
3211
  }
3101
3212
  const match = allowedValues.find((v) => !/manual/i.test(v) && !/generic/i.test(v));
3102
3213
  if (!match)
3103
- throw new Error(
3214
+ throw new CliNotFoundError(
3104
3215
  `No 'cucumber' (gherkin/automation) Test Type value found on this instance. Available: ${allowedValues.join(", ")}`
3105
3216
  );
3106
3217
  return match;
@@ -3121,7 +3232,7 @@ async function resolveTypeValue(client, params) {
3121
3232
  }
3122
3233
  function requireField(map, concept, label) {
3123
3234
  const id = map[concept];
3124
- if (!id) throw new Error(`Xray field for '${label}' not found on this instance.`);
3235
+ if (!id) throw new CliNotFoundError(`Xray field for '${label}' not found on this instance.`);
3125
3236
  return id;
3126
3237
  }
3127
3238
  function buildTestCustomFields(map, opts) {
@@ -3159,7 +3270,7 @@ function create10(parent) {
3159
3270
  const cmd = parent.command("create").description(
3160
3271
  "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
3272
  ).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")
3273
+ new Option11("--type <type>", "Pre-Condition type").choices(["manual", "generic", "cucumber"]).default("manual")
3163
3274
  );
3164
3275
  textOrFileOption(cmd, "condition", { description: "Pre-condition body / definition text" });
3165
3276
  textOrFileOption(cmd, "description", { description: "Pre-Condition issue description" });
@@ -3230,9 +3341,9 @@ function remove4(parent) {
3230
3341
  }
3231
3342
 
3232
3343
  // src/commands/xray/precondition/update.ts
3233
- import { Option as Option11 } from "commander";
3344
+ import { Option as Option12 } from "commander";
3234
3345
  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"]));
3346
+ 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
3347
  textOrFileOption(cmd, "condition", { description: "New pre-condition body / definition text" });
3237
3348
  textOrFileOption(cmd, "description", { description: "New Pre-Condition issue description" });
3238
3349
  examples(cmd, ['PROJ-50 --summary "Updated precondition"', 'PROJ-50 --type generic --condition "apiKey != null"']);
@@ -3255,12 +3366,12 @@ function update9(parent) {
3255
3366
  fieldMap: map
3256
3367
  });
3257
3368
  const typeId = map["precondition-type"];
3258
- if (!typeId) throw new Error("Xray field for 'Pre-Condition Type' not found on this instance.");
3369
+ if (!typeId) throw new CliNotFoundError("Xray field for 'Pre-Condition Type' not found on this instance.");
3259
3370
  customFields[typeId] = { value: typeValue };
3260
3371
  }
3261
3372
  if (condition !== void 0) {
3262
3373
  const bodyId = map["precondition-body"];
3263
- if (!bodyId) throw new Error("Xray field for 'Conditions' not found on this instance.");
3374
+ if (!bodyId) throw new CliNotFoundError("Xray field for 'Conditions' not found on this instance.");
3264
3375
  customFields[bodyId] = condition;
3265
3376
  }
3266
3377
  }
@@ -3291,9 +3402,10 @@ function registerPreconditionCommands(xray) {
3291
3402
  // src/utils/xray/run-id.ts
3292
3403
  async function resolveRunId(client, o) {
3293
3404
  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.");
3405
+ if (!o.execution || !o.test)
3406
+ throw new CliUsageError("Provide a run id, or both --execution and --test to resolve the run.");
3295
3407
  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}.`);
3408
+ if (!run?.id) throw new CliNotFoundError(`No test run found for ${o.test} in ${o.execution}.`);
3297
3409
  return Number(run.id);
3298
3410
  }
3299
3411
 
@@ -3665,7 +3777,7 @@ function update12(parent) {
3665
3777
  const data = resolveTextOrFile(opts, "data", { required: false });
3666
3778
  const result = resolveTextOrFile(opts, "result", { required: false });
3667
3779
  if (action === void 0 && data === void 0 && result === void 0) {
3668
- throw new Error(
3780
+ throw new CliUsageError(
3669
3781
  "Provide at least one of --action (or --action-file), --data (or --data-file), --result (or --result-file)."
3670
3782
  );
3671
3783
  }
@@ -3689,16 +3801,16 @@ function registerStepCommands(xray) {
3689
3801
  }
3690
3802
 
3691
3803
  // 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() })
3804
+ import { Option as Option13 } from "commander";
3805
+ import { z as z5 } from "zod";
3806
+ var stepsSchema = z5.array(
3807
+ z5.object({ action: z5.string(), data: z5.string().optional(), result: z5.string().optional() })
3696
3808
  );
3697
3809
  function create11(parent) {
3698
3810
  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")
3811
+ new Option13("--type <type>", "Test type (when cucumber: also pass --gherkin; when generic: --definition)").choices(["manual", "cucumber", "generic"]).default("manual")
3700
3812
  ).addOption(
3701
- new Option12("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
3813
+ new Option13("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
3702
3814
  "scenario",
3703
3815
  "scenario-outline"
3704
3816
  ])
@@ -3726,10 +3838,10 @@ function create11(parent) {
3726
3838
  const definition = resolveTextOrFile(opts, "definition", { required: false });
3727
3839
  const description = resolveTextOrFile(opts, "description", { required: false });
3728
3840
  if (opts.type === "cucumber" && !gherkin) {
3729
- throw new Error("--gherkin (or --gherkin-file) is required when --type is cucumber.");
3841
+ throw new CliUsageError("--gherkin (or --gherkin-file) is required when --type is cucumber.");
3730
3842
  }
3731
3843
  if (opts.type === "generic" && !definition) {
3732
- throw new Error("--definition (or --definition-file) is required when --type is generic.");
3844
+ throw new CliUsageError("--definition (or --definition-file) is required when --type is generic.");
3733
3845
  }
3734
3846
  const cucumberType = opts.cucumberType === "scenario" ? "Scenario" : opts.cucumberType === "scenario-outline" ? "Scenario Outline" : void 0;
3735
3847
  const client = getClient();
@@ -3804,13 +3916,13 @@ function list18(parent) {
3804
3916
  const client = getClient();
3805
3917
  if (opts.folder !== void 0) {
3806
3918
  if (!opts.project) {
3807
- throw new Error("--project is required when --folder is specified.");
3919
+ throw new CliUsageError("--project is required when --folder is specified.");
3808
3920
  }
3809
3921
  const otherScopes = ["set", "plan", "execution", "precondition"].filter(
3810
3922
  (k) => opts[k] !== void 0
3811
3923
  );
3812
3924
  if (otherScopes.length > 0) {
3813
- throw new Error(
3925
+ throw new CliUsageError(
3814
3926
  `--folder cannot be combined with ${otherScopes.map((k) => `--${k}`).join(", ")}. Use --folder with --project only.`
3815
3927
  );
3816
3928
  }
@@ -3868,10 +3980,10 @@ function list18(parent) {
3868
3980
  }
3869
3981
 
3870
3982
  // src/commands/xray/test/update.ts
3871
- import { Option as Option13 } from "commander";
3983
+ import { Option as Option14 } from "commander";
3872
3984
  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([
3985
+ 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(
3986
+ new Option14("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
3875
3987
  "scenario",
3876
3988
  "scenario-outline"
3877
3989
  ])
@@ -3902,10 +4014,10 @@ function update13(parent) {
3902
4014
  if (opts.reporter !== void 0) fields.reporter = { name: opts.reporter };
3903
4015
  if (opts.type !== void 0) {
3904
4016
  if (opts.type === "cucumber" && !gherkin) {
3905
- throw new Error("--gherkin (or --gherkin-file) is required when --type is cucumber.");
4017
+ throw new CliUsageError("--gherkin (or --gherkin-file) is required when --type is cucumber.");
3906
4018
  }
3907
4019
  if (opts.type === "generic" && !definition) {
3908
- throw new Error("--definition (or --definition-file) is required when --type is generic.");
4020
+ throw new CliUsageError("--definition (or --definition-file) is required when --type is generic.");
3909
4021
  }
3910
4022
  const cucumberType = opts.cucumberType === "scenario" ? "Scenario" : opts.cucumberType === "scenario-outline" ? "Scenario Outline" : void 0;
3911
4023
  const projectKey = testKey.split("-")[0];
@@ -4059,7 +4171,7 @@ function registerXrayCommands(program) {
4059
4171
  var DIM = "\x1B[2m";
4060
4172
  var RESET = "\x1B[0m";
4061
4173
  function buildProgram() {
4062
- const program = new Command16();
4174
+ const program = new Command17();
4063
4175
  program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion(import.meta.url)).configureHelp({
4064
4176
  styleTitle: (str) => styleText("bold", str),
4065
4177
  styleUsage: (str) => styleText("dim", str),
@@ -4120,10 +4232,33 @@ function getCredentialInfo() {
4120
4232
  };
4121
4233
  }
4122
4234
 
4235
+ // src/utils/field-error-recovery.ts
4236
+ var CUSTOM_FIELD_KEY = /^customfield_(\d+)$/;
4237
+ function customFieldIds(detail) {
4238
+ if (!detail || typeof detail !== "object") return [];
4239
+ const errors = detail.errors;
4240
+ if (!errors || typeof errors !== "object") return [];
4241
+ return Object.keys(errors).filter((k) => CUSTOM_FIELD_KEY.test(k));
4242
+ }
4243
+ function adaptFieldErrors(normalized) {
4244
+ const ids = customFieldIds(normalized.detail);
4245
+ if (ids.length === 0) return normalized;
4246
+ const numbers = ids.map((id) => CUSTOM_FIELD_KEY.exec(id)?.[1] ?? id);
4247
+ const lookup = numbers.map((n) => `jiradc field search ${n}`).join(", ");
4248
+ const plural = ids.length > 1 ? "s" : "";
4249
+ return {
4250
+ ...normalized,
4251
+ 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).`
4252
+ };
4253
+ }
4254
+
4123
4255
  // src/index.ts
4124
4256
  await runCli(buildProgram(), {
4125
4257
  credentialInfo: getCredentialInfo,
4126
4258
  service: "Jira",
4127
4259
  authRecovery: "Set the JIRA_URL and JIRA_TOKEN environment variables.",
4128
- urlEnvVar: "JIRA_URL"
4260
+ urlEnvVar: "JIRA_URL",
4261
+ // Jira names rejected fields by custom-field id. The generic recovery leaves
4262
+ // an agent holding an opaque id, so point it at the command that explains one.
4263
+ adapt: adaptFieldErrors
4129
4264
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jiradc-cli",
3
- "version": "1.0.32",
3
+ "version": "1.0.33",
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",
@@ -23,9 +23,9 @@
23
23
  "tsx": "^4.19.2",
24
24
  "typescript": "^5.7.2",
25
25
  "vitest": "^4.0.16",
26
- "cli-utils": "1.0.0",
26
+ "config-typescript": "0.0.0",
27
27
  "config-eslint": "0.0.0",
28
- "config-typescript": "0.0.0"
28
+ "cli-utils": "1.0.0"
29
29
  },
30
30
  "engines": {
31
31
  "node": ">=22.0.0"