jiradc-cli 1.0.20 → 2.0.0-g7be3698.7

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 +13 -6
  2. package/dist/index.js +548 -288
  3. package/package.json +4 -3
package/README.md CHANGED
@@ -37,14 +37,17 @@ All commands output JSON. Add `--pretty` to pretty-print.
37
37
  | `jiradc issue assign <key> <user>` | Assign issue (user can be a username, `me`, or `none` to unassign) |
38
38
  | `jiradc issue transition <key>` | Transition issue to a new status (`--to` accepts ID or status name, `--comment` to add a note) |
39
39
  | `jiradc issue transitions <key>` | List available transitions |
40
- | `jiradc issue comment <key>` | Add a comment |
41
- | `jiradc issue comment-edit <key> <commentId>` | Edit a comment |
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`) |
42
43
  | `jiradc issue link <key> <targetKey>` | Link two issues (`--type` link type name) |
43
44
  | `jiradc issue unlink <linkId>` | Remove a link |
44
45
  | `jiradc issue link-types` | List available link types |
45
46
  | `jiradc issue link-epic <keys...>` | Link one or more issues to an epic (`--epic <epicKey>`) |
46
- | `jiradc issue worklog <key>` | Add a work log entry |
47
- | `jiradc issue get-worklog <key>` | Get work log entries |
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`) |
48
51
  | `jiradc issue changelog <key>` | Get issue changelog |
49
52
  | `jiradc issue batch-changelog` | Get changelog for multiple issues (`--keys`) |
50
53
  | `jiradc issue clone <key>` | Clone an issue with subtasks |
@@ -93,6 +96,7 @@ All commands output JSON. Add `--pretty` to pretty-print.
93
96
  | `jiradc sprint issues <boardId> <sprintId>` | Get issues in a sprint |
94
97
  | `jiradc sprint create <boardId>` | Create a sprint |
95
98
  | `jiradc sprint update <sprintId>` | Update a sprint |
99
+ | `jiradc sprint delete <sprintId>` | Delete a sprint (returns its issues to the backlog) |
96
100
 
97
101
  ### field
98
102
 
@@ -151,13 +155,16 @@ jiradc issue update AI-123 --fix-versions 1.0,2.0
151
155
  jiradc issue link-epic AI-456 AI-457 AI-458 --epic AI-100
152
156
 
153
157
  # Add a comment
154
- jiradc issue comment AI-123 --body "Fixed in commit abc123"
158
+ jiradc issue comment add AI-123 --body "Fixed in commit abc123"
159
+
160
+ # Delete a comment
161
+ jiradc issue comment delete AI-123 --id 12345
155
162
 
156
163
  # Link two issues
157
164
  jiradc issue link AI-123 AI-456 --type "blocks"
158
165
 
159
166
  # Log work
160
- jiradc issue worklog AI-123 --time "2h 30m" --comment "Code review"
167
+ jiradc issue worklog add AI-123 --time "2h 30m" --comment "Code review"
161
168
 
162
169
  # List active sprints
163
170
  jiradc sprint list 42 --state active
package/dist/index.js CHANGED
@@ -1,16 +1,25 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/index.ts
4
- import { readFileSync } from "fs";
5
- import { dirname, join as join4 } from "path";
6
- import { fileURLToPath } from "url";
7
- import { styleText } from "util";
8
- import { Command as Command8 } from "commander";
3
+ // ../../cli-utils/dist/cache.js
4
+ import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
5
+ import { homedir } from "os";
6
+ import { join } from "path";
9
7
 
10
- // src/commands/board/issues.ts
11
- import { Argument } from "commander";
8
+ // ../../cli-utils/dist/bootstrap.js
9
+ import { readFileSync as readFileSync2 } from "fs";
10
+ import { dirname, join as join2 } from "path";
11
+ import { fileURLToPath } from "url";
12
+ function readPackageVersion(importMetaUrl) {
13
+ try {
14
+ const here = dirname(fileURLToPath(importMetaUrl));
15
+ const pkg = JSON.parse(readFileSync2(join2(here, "..", "package.json"), "utf-8"));
16
+ return pkg.version ?? "0.0.0";
17
+ } catch {
18
+ return "0.0.0";
19
+ }
20
+ }
12
21
 
13
- // src/utils/cli.ts
22
+ // ../../cli-utils/dist/validators.js
14
23
  import { InvalidArgumentError } from "commander";
15
24
  function intInRange(min, max) {
16
25
  return (raw) => {
@@ -39,43 +48,256 @@ function positiveInt(raw) {
39
48
  return n;
40
49
  }
41
50
 
42
- // src/utils/client.ts
43
- import { JiraClient } from "jira-data-center-client";
44
-
45
- // src/utils/credentials.ts
46
- function getCredentialInfo() {
47
- const baseUrl = process.env.JIRA_URL;
48
- const token = process.env.JIRA_TOKEN;
51
+ // ../../cli-utils/dist/registry.js
52
+ var SUB_ENTITY_ID_NUMERIC = {
53
+ comment: true,
54
+ worklog: true,
55
+ attachment: true,
56
+ request: true,
57
+ type: true,
58
+ employee: true,
59
+ message: false,
60
+ membership: false
61
+ };
62
+
63
+ // ../../cli-utils/dist/builders.js
64
+ function defineOption(cmd, flags, description, parser, mandatory) {
65
+ if (mandatory) {
66
+ return parser ? cmd.requiredOption(flags, description, parser) : cmd.requiredOption(flags, description);
67
+ }
68
+ return parser ? cmd.option(flags, description, parser) : cmd.option(flags, description);
69
+ }
70
+ function subjectArg(cmd, name, opts = {}) {
71
+ const inner = opts.variadic ? `${name}...` : name;
72
+ const token = opts.optional ? `[${inner}]` : `<${inner}>`;
73
+ return opts.parser ? cmd.argument(token, opts.description ?? "", opts.parser) : cmd.argument(token, opts.description ?? "");
74
+ }
75
+ function subEntityOption(cmd, entity, opts = {}) {
76
+ const numeric = opts.numeric ?? SUB_ENTITY_ID_NUMERIC[entity] ?? true;
77
+ const description = `${entity.charAt(0).toUpperCase()}${entity.slice(1)} id`;
78
+ return defineOption(cmd, `--${entity}-id <id>`, description, numeric ? positiveInt : void 0, opts.mandatory);
79
+ }
80
+ function bodyOption(cmd, opts = {}) {
81
+ return defineOption(cmd, "--body <text>", "Prose body content", void 0, opts.mandatory);
82
+ }
83
+
84
+ // ../../cli-utils/dist/errors.js
85
+ import { CommanderError } from "commander";
86
+ var EXIT = {
87
+ SUCCESS: 0,
88
+ GENERIC: 1,
89
+ USAGE: 2,
90
+ NOT_FOUND: 3,
91
+ FORBIDDEN: 4,
92
+ CONFLICT: 5,
93
+ AUTH: 6
94
+ };
95
+ var CliAuthError = class extends Error {
96
+ constructor(message) {
97
+ super(message);
98
+ this.name = "CliAuthError";
99
+ }
100
+ };
101
+ var TYPE_EXIT = {
102
+ usage: EXIT.USAGE,
103
+ not_found: EXIT.NOT_FOUND,
104
+ forbidden: EXIT.FORBIDDEN,
105
+ conflict: EXIT.CONFLICT,
106
+ auth: EXIT.AUTH,
107
+ rate_limited: EXIT.GENERIC,
108
+ server: EXIT.GENERIC,
109
+ network: EXIT.GENERIC,
110
+ unknown: EXIT.GENERIC
111
+ };
112
+ var NETWORK_CODES = ["ENOTFOUND", "ECONNREFUSED", "ECONNRESET", "ETIMEDOUT"];
113
+ function httpStatus(err) {
114
+ const e = err;
115
+ return e?.response?.status ?? e?.statusCode;
116
+ }
117
+ function responseDetail(err) {
118
+ const e = err;
119
+ const data = e?.response?.data;
120
+ if (data && typeof data === "object")
121
+ return data;
122
+ const body = e?.body;
123
+ if (typeof body === "string") {
124
+ try {
125
+ const parsed = JSON.parse(body);
126
+ return parsed.error ?? parsed;
127
+ } catch {
128
+ return void 0;
129
+ }
130
+ }
131
+ if (body && typeof body === "object") {
132
+ return body.error ?? body;
133
+ }
134
+ return void 0;
135
+ }
136
+ function errorCode(err) {
137
+ return err?.code;
138
+ }
139
+ function normalize(err, opts) {
140
+ const message = err instanceof Error ? err.message : String(err);
141
+ if (err instanceof CommanderError) {
142
+ return {
143
+ type: "usage",
144
+ message: message || "Invalid command usage",
145
+ recovery: "Check the command syntax and flags; run the command with --help.",
146
+ retryable: false
147
+ };
148
+ }
149
+ if (err instanceof CliAuthError) {
150
+ return { type: "auth", message: message || "Missing credentials", recovery: opts.authRecovery, retryable: false };
151
+ }
152
+ const status = httpStatus(err);
153
+ const detail = responseDetail(err);
154
+ if (status !== void 0) {
155
+ switch (status) {
156
+ case 400:
157
+ return {
158
+ type: "usage",
159
+ status,
160
+ message: "Bad request (HTTP 400)",
161
+ recovery: "Check parameter values (ids, keys, query syntax) against the API.",
162
+ retryable: false,
163
+ detail
164
+ };
165
+ case 401:
166
+ return {
167
+ type: "auth",
168
+ status,
169
+ message: "Authentication failed (HTTP 401)",
170
+ recovery: opts.authRecovery,
171
+ retryable: false
172
+ };
173
+ case 403:
174
+ return {
175
+ type: "forbidden",
176
+ status,
177
+ message: "Forbidden (HTTP 403)",
178
+ recovery: `Your ${opts.service} account lacks permission for this operation; check token scope and resource permissions.`,
179
+ retryable: false,
180
+ detail
181
+ };
182
+ case 404:
183
+ return {
184
+ type: "not_found",
185
+ status,
186
+ message: "Not found (HTTP 404)",
187
+ recovery: "Verify the id/key exists and that you have access to it.",
188
+ retryable: false,
189
+ detail
190
+ };
191
+ case 409:
192
+ return {
193
+ type: "conflict",
194
+ status,
195
+ message: "Conflict (HTTP 409)",
196
+ recovery: "The resource changed or already exists; re-fetch current state and retry.",
197
+ retryable: false,
198
+ detail
199
+ };
200
+ case 429:
201
+ return {
202
+ type: "rate_limited",
203
+ status,
204
+ message: "Rate limited (HTTP 429)",
205
+ recovery: "Wait and retry the request.",
206
+ retryable: true
207
+ };
208
+ }
209
+ if (status >= 500) {
210
+ return {
211
+ type: "server",
212
+ status,
213
+ message: `Server error (HTTP ${status})`,
214
+ recovery: `${opts.service} returned an internal error; retry shortly.`,
215
+ retryable: true,
216
+ detail
217
+ };
218
+ }
219
+ return {
220
+ type: "unknown",
221
+ status,
222
+ message: `${opts.service} error (HTTP ${status}): ${message}`,
223
+ recovery: "Inspect the detail field for the API response.",
224
+ retryable: false,
225
+ detail
226
+ };
227
+ }
228
+ const code = errorCode(err);
229
+ if (code && NETWORK_CODES.includes(code) || NETWORK_CODES.some((c) => message.includes(c))) {
230
+ return {
231
+ type: "network",
232
+ message: `Cannot connect to ${opts.service}: ${message}`,
233
+ recovery: opts.networkRecovery ?? "Verify the *_URL is correct, the server is reachable, and you are on the VPN if required.",
234
+ retryable: true
235
+ };
236
+ }
237
+ return { type: "unknown", message, recovery: "Unexpected error; inspect the message.", retryable: false };
238
+ }
239
+ function classifyError(err, opts) {
240
+ const base = normalize(err, opts);
241
+ const n = opts.adapt ? opts.adapt(base, err) : base;
242
+ if (n.type === "auth" && n.detail === void 0 && opts.credentialInfo) {
243
+ n.detail = opts.credentialInfo();
244
+ }
49
245
  return {
50
- environment: {
51
- JIRA_URL: {
52
- value: baseUrl ?? null,
53
- description: "Your Jira Server base URL (e.g., https://jira.example.com)"
54
- },
55
- JIRA_TOKEN: {
56
- value: token ? "<set>" : null,
57
- description: "Personal Access Token"
246
+ envelope: {
247
+ error: {
248
+ type: n.type,
249
+ message: n.message,
250
+ recovery: n.recovery,
251
+ retryable: n.retryable,
252
+ ...n.detail !== void 0 ? { detail: n.detail } : {}
58
253
  }
59
254
  },
60
- tokenUrl: `${baseUrl ?? "https://jira.example.com"}/secure/ViewProfile.jspa`,
61
- hint: "Export environment variables in your shell profile (e.g., ~/.zshrc)."
255
+ exitCode: TYPE_EXIT[n.type]
256
+ };
257
+ }
258
+ function createErrorHandler(opts) {
259
+ return (err) => {
260
+ const { envelope, exitCode } = classifyError(err, opts);
261
+ process.stderr.write(`${JSON.stringify(envelope)}
262
+ `);
263
+ return process.exit(exitCode);
62
264
  };
63
265
  }
266
+ function isCleanCommanderExit(err) {
267
+ return err instanceof CommanderError && (err.exitCode === 0 || err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version");
268
+ }
269
+ function routeErrors(cmd) {
270
+ cmd.exitOverride();
271
+ cmd.configureOutput({ writeErr: () => void 0 });
272
+ cmd.commands.forEach(routeErrors);
273
+ }
274
+ async function runCli(program, opts) {
275
+ routeErrors(program);
276
+ try {
277
+ await program.parseAsync();
278
+ } catch (err) {
279
+ if (isCleanCommanderExit(err)) {
280
+ process.exit(err.exitCode ?? 0);
281
+ }
282
+ createErrorHandler(opts)(err);
283
+ }
284
+ }
285
+
286
+ // src/program.ts
287
+ import { styleText } from "util";
288
+ import { Command as Command11 } from "commander";
289
+
290
+ // src/commands/board/issues.ts
291
+ import { Argument } from "commander";
64
292
 
65
293
  // src/utils/client.ts
294
+ import { JiraClient } from "jira-data-center-client";
66
295
  function getClient() {
67
296
  const baseUrl = process.env.JIRA_URL;
68
297
  const token = process.env.JIRA_TOKEN;
69
298
  if (!baseUrl || !token) {
70
299
  const missing = [...!baseUrl ? ["JIRA_URL"] : [], ...!token ? ["JIRA_TOKEN"] : []];
71
- process.stderr.write(
72
- `${JSON.stringify({
73
- error: `Missing required environment variables: ${missing.join(", ")}`,
74
- ...getCredentialInfo()
75
- })}
76
- `
77
- );
78
- process.exit(1);
300
+ throw new CliAuthError(`Missing required environment variables: ${missing.join(", ")}`);
79
301
  }
80
302
  return new JiraClient({ baseUrl, token });
81
303
  }
@@ -89,68 +311,6 @@ function output(data) {
89
311
  process.stdout.write(`${JSON.stringify(data, null, prettyPrint ? 2 : void 0)}
90
312
  `);
91
313
  }
92
- function handleError(err) {
93
- const message = err instanceof Error ? err.message : String(err);
94
- const axiosStatus = err?.response?.status;
95
- if (axiosStatus === 400) {
96
- const responseData = err?.response?.data;
97
- const jiraErrors = responseData && typeof responseData === "object" ? responseData : void 0;
98
- process.stderr.write(
99
- `${JSON.stringify({
100
- error: `Bad request (HTTP 400)`,
101
- detail: jiraErrors ?? message,
102
- hint: "Check that all parameters are valid (JQL syntax, field names, project keys, etc.)."
103
- })}
104
- `
105
- );
106
- } else if (axiosStatus === 401) {
107
- process.stderr.write(
108
- `${JSON.stringify({
109
- error: "Authentication failed (HTTP 401)",
110
- ...getCredentialInfo()
111
- })}
112
- `
113
- );
114
- } else if (axiosStatus === 403) {
115
- const responseData = err?.response?.data;
116
- process.stderr.write(
117
- `${JSON.stringify({
118
- error: "Forbidden (HTTP 403)",
119
- detail: responseData && typeof responseData === "object" ? responseData : message,
120
- hint: "Your account does not have permission for this operation. Check project permissions and token scope."
121
- })}
122
- `
123
- );
124
- } else if (message.includes("ENOTFOUND") || message.includes("ECONNREFUSED") || message.includes("ECONNRESET")) {
125
- process.stderr.write(
126
- `${JSON.stringify({
127
- error: `Cannot connect to Jira server: ${message}`,
128
- hint: "Verify that JIRA_URL is correct and the server is reachable."
129
- })}
130
- `
131
- );
132
- } else if (axiosStatus === 500) {
133
- process.stderr.write(
134
- `${JSON.stringify({
135
- error: `Server error (HTTP 500): ${message}`,
136
- hint: "The server returned an internal error. Check that all parameters are valid (project keys, issue keys, JQL query syntax)."
137
- })}
138
- `
139
- );
140
- } else {
141
- const responseData = err?.response?.data;
142
- const detail = responseData && typeof responseData === "object" ? responseData : void 0;
143
- process.stderr.write(
144
- `${JSON.stringify({
145
- error: message,
146
- ...axiosStatus !== void 0 && { statusCode: axiosStatus },
147
- ...detail && { detail }
148
- })}
149
- `
150
- );
151
- }
152
- process.exit(1);
153
- }
154
314
 
155
315
  // src/utils/transformers/base.ts
156
316
  function jiraBaseUrl() {
@@ -379,8 +539,8 @@ function transformIssueFields(fields) {
379
539
  issuelinks,
380
540
  subtasks,
381
541
  parent,
382
- comment: comment2,
383
- worklog: worklog2,
542
+ comment,
543
+ worklog,
384
544
  attachment,
385
545
  // required scalars / structured fields we always keep verbatim
386
546
  summary,
@@ -412,20 +572,20 @@ function transformIssueFields(fields) {
412
572
  // Drop empty comment / worklog containers entirely. The default Jira
413
573
  // search response includes both wrappers on every issue regardless of
414
574
  // count; on a 25-issue page that's 25 × 2 empty objects of pure noise.
415
- ...comment2 && comment2.comments.length > 0 ? {
575
+ ...comment && comment.comments.length > 0 ? {
416
576
  comment: {
417
- comments: comment2.comments.map(transformComment),
418
- maxResults: comment2.maxResults,
419
- total: comment2.total,
420
- startAt: comment2.startAt
577
+ comments: comment.comments.map(transformComment),
578
+ maxResults: comment.maxResults,
579
+ total: comment.total,
580
+ startAt: comment.startAt
421
581
  }
422
582
  } : {},
423
- ...worklog2 && worklog2.worklogs.length > 0 ? {
583
+ ...worklog && worklog.worklogs.length > 0 ? {
424
584
  worklog: {
425
- worklogs: worklog2.worklogs.map(transformWorklog),
426
- maxResults: worklog2.maxResults,
427
- total: worklog2.total,
428
- startAt: worklog2.startAt
585
+ worklogs: worklog.worklogs.map(transformWorklog),
586
+ maxResults: worklog.maxResults,
587
+ total: worklog.total,
588
+ startAt: worklog.startAt
429
589
  }
430
590
  } : {},
431
591
  ...attachment && attachment.length > 0 ? { attachment: attachment.map(transformAttachment) } : {}
@@ -513,8 +673,8 @@ function list(parent) {
513
673
  }
514
674
 
515
675
  // src/commands/board/index.ts
516
- function registerBoardCommands(program2) {
517
- const board = program2.command("board").description("Board operations").addHelpText(
676
+ function registerBoardCommands(program) {
677
+ const board = program.command("board").description("Board operations").addHelpText(
518
678
  "after",
519
679
  `
520
680
  Examples:
@@ -635,8 +795,8 @@ Examples:
635
795
  }
636
796
 
637
797
  // src/commands/component/index.ts
638
- function registerComponentCommands(program2) {
639
- const component = program2.command("component").description("Project component operations").addHelpText(
798
+ function registerComponentCommands(program) {
799
+ const component = program.command("component").description("Project component operations").addHelpText(
640
800
  "after",
641
801
  `
642
802
  Examples:
@@ -658,16 +818,16 @@ Examples:
658
818
 
659
819
  // src/commands/field/options.ts
660
820
  function options(parent) {
661
- parent.command("options <id>").description("Get available options for a custom field").option("--query <text>", "Filter options by text").option("--limit <number>", "Max results to return (1-1000)", intInRange(1, 1e3), 25).option("--page <number>", "Page number (1-indexed)", positiveInt).addHelpText(
821
+ parent.command("options <id>").description("Get available options for a custom field").option("--query <text>", "Filter options by text").option("--limit <number>", "Max results to return (1-1000)", intInRange(1, 1e3), 25).option("--start <number>", "Page number (1-indexed)", positiveInt).addHelpText(
662
822
  "after",
663
- '\nExamples:\n jiradc field options 10001\n jiradc field options 10001 --query "High"\n jiradc field options 10001 --limit 20 --page 2'
823
+ '\nExamples:\n jiradc field options 10001\n jiradc field options 10001 --query "High"\n jiradc field options 10001 --limit 20 --start 2'
664
824
  ).action(async (id, opts) => {
665
825
  const client = getClient();
666
826
  const result = await client.fields.getFieldOptions({
667
827
  fieldId: id,
668
828
  query: opts.query,
669
829
  maxResults: opts.limit,
670
- page: opts.page
830
+ page: opts.start
671
831
  });
672
832
  output(transformPaged({ ...result, startAt: result.startAt ?? 0 }, transformCustomFieldOption));
673
833
  });
@@ -686,8 +846,8 @@ function search(parent) {
686
846
  }
687
847
 
688
848
  // src/commands/field/index.ts
689
- function registerFieldCommands(program2) {
690
- const field = program2.command("field").description("Field operations").addHelpText(
849
+ function registerFieldCommands(program) {
850
+ const field = program.command("field").description("Field operations").addHelpText(
691
851
  "after",
692
852
  `
693
853
  Examples:
@@ -718,15 +878,15 @@ async function resolveUserToken(token) {
718
878
 
719
879
  // src/commands/issue/assign.ts
720
880
  function assign(parent) {
721
- parent.command("assign <key> <user>").description('Assign an issue. <user> is a username, "me", or "none" to unassign.').addHelpText(
881
+ parent.command("assign <key>").description('Assign an issue. --assignee is a username, "me", or "none" to unassign.').requiredOption("--assignee <user>", 'Username to assign, "me" for the current user, or "none" to unassign').addHelpText(
722
882
  "after",
723
883
  `
724
884
  Examples:
725
- jiradc issue assign PROJ-123 jsmith
726
- jiradc issue assign PROJ-123 me
727
- jiradc issue assign PROJ-123 none`
728
- ).action(async (key, user) => {
729
- const resolved = await resolveUserToken(user);
885
+ jiradc issue assign PROJ-123 --assignee jsmith
886
+ jiradc issue assign PROJ-123 --assignee me
887
+ jiradc issue assign PROJ-123 --assignee none`
888
+ ).action(async (key, opts) => {
889
+ const resolved = await resolveUserToken(opts.assignee);
730
890
  const client = getClient();
731
891
  await client.issues.update({
732
892
  issueKeyOrId: key,
@@ -738,20 +898,22 @@ Examples:
738
898
 
739
899
  // src/commands/issue/attachment/delete.ts
740
900
  function deleteAttachment(parent) {
741
- parent.command("delete").description("Delete an attachment by ID").requiredOption("--id <attachmentId>", "Attachment ID to delete").addHelpText("after", "\nExamples:\n jiradc issue attachment delete --id 12345").action(async (opts) => {
901
+ const cmd = parent.command("delete <key>").description("Delete an attachment by ID");
902
+ subEntityOption(cmd, "attachment", { mandatory: true });
903
+ cmd.addHelpText("after", "\nExamples:\n jiradc issue attachment delete PROJ-123 --attachment-id 12345").action(async (key, opts) => {
742
904
  const client = getClient();
743
- await client.issues.deleteAttachment({ attachmentId: opts.id });
744
- output({ deleted: true, attachmentId: opts.id });
905
+ await client.issues.deleteAttachment({ attachmentId: String(opts.attachmentId) });
906
+ output({ deleted: true, issueKey: key, attachmentId: opts.attachmentId });
745
907
  });
746
908
  }
747
909
 
748
910
  // src/commands/issue/attachment/download-all.ts
749
- import { mkdirSync } from "fs";
750
- import { join } from "path";
911
+ import { mkdirSync as mkdirSync2 } from "fs";
912
+ import { join as join3 } from "path";
751
913
  function downloadAll(parent) {
752
914
  parent.command("download-all <key>").description("Download all attachments from an issue").requiredOption("--output <dir>", "Local directory to save attachments into").addHelpText("after", "\nExamples:\n jiradc issue attachment download-all PROJ-123 --output ./downloads").action(async (key, opts) => {
753
915
  const client = getClient();
754
- mkdirSync(opts.output, { recursive: true });
916
+ mkdirSync2(opts.output, { recursive: true });
755
917
  const issue = await client.issues.get({
756
918
  issueKeyOrId: key,
757
919
  fields: ["attachment"]
@@ -768,7 +930,7 @@ function downloadAll(parent) {
768
930
  failed.push({ filename: att.filename, error: "No content URL" });
769
931
  continue;
770
932
  }
771
- const destPath = join(opts.output, att.filename);
933
+ const destPath = join3(opts.output, att.filename);
772
934
  try {
773
935
  await client.issues.downloadAttachment({ url: att.content, destinationPath: destPath });
774
936
  results.push({ filename: att.filename, size: att.size, path: destPath });
@@ -788,11 +950,17 @@ function downloadAll(parent) {
788
950
 
789
951
  // src/commands/issue/attachment/download.ts
790
952
  function download(parent) {
791
- parent.command("download <key>").description("Download a single attachment by ID").requiredOption("--id <attachmentId>", "Attachment ID").requiredOption("--output <path>", "Local file path to save the attachment").addHelpText("after", "\nExamples:\n jiradc issue attachment download PROJ-123 --id 12345 --output ./report.pdf").action(async (key, opts) => {
953
+ const cmd = parent.command("download <key>").description("Download a single attachment by ID");
954
+ subEntityOption(cmd, "attachment", { mandatory: true });
955
+ cmd.requiredOption("--output <path>", "Local file path to save the attachment").addHelpText(
956
+ "after",
957
+ "\nExamples:\n jiradc issue attachment download PROJ-123 --attachment-id 12345 --output ./report.pdf"
958
+ ).action(async (key, opts) => {
792
959
  const client = getClient();
793
- const attachment = await client.issues.getAttachment({ attachmentId: opts.id });
960
+ const attachmentId = String(opts.attachmentId);
961
+ const attachment = await client.issues.getAttachment({ attachmentId });
794
962
  if (!attachment.content) {
795
- throw new Error(`Attachment ${opts.id} has no content URL`);
963
+ throw new Error(`Attachment ${attachmentId} has no content URL`);
796
964
  }
797
965
  await client.issues.downloadAttachment({
798
966
  url: attachment.content,
@@ -841,8 +1009,8 @@ function upload(parent) {
841
1009
  const filePaths = opts.files.split(",").map((f) => f.trim());
842
1010
  const results = [];
843
1011
  for (const filePath of filePaths) {
844
- const attachments2 = await client.issues.addAttachment({ issueKeyOrId: key, filePath });
845
- results.push(...attachments2);
1012
+ const attachments = await client.issues.addAttachment({ issueKeyOrId: key, filePath });
1013
+ results.push(...attachments);
846
1014
  }
847
1015
  output({
848
1016
  issueKey: key,
@@ -866,9 +1034,9 @@ function registerAttachmentCommands(parent) {
866
1034
  Examples:
867
1035
  $ jiradc issue attachment list PROJ-123
868
1036
  $ jiradc issue attachment upload PROJ-123 --files ./report.pdf
869
- $ jiradc issue attachment download PROJ-123 --id 12345 --output ./report.pdf
1037
+ $ jiradc issue attachment download PROJ-123 --attachment-id 12345 --output ./report.pdf
870
1038
  $ jiradc issue attachment download-all PROJ-123 --output ./downloads
871
- $ jiradc issue attachment delete --id 12345
1039
+ $ jiradc issue attachment delete PROJ-123 --attachment-id 12345
872
1040
  `
873
1041
  );
874
1042
  upload(attachment);
@@ -878,47 +1046,6 @@ Examples:
878
1046
  deleteAttachment(attachment);
879
1047
  }
880
1048
 
881
- // src/commands/issue/attachments.ts
882
- import { mkdirSync as mkdirSync2 } from "fs";
883
- import { join as join2 } from "path";
884
- function attachments(parent) {
885
- parent.command("attachments <key>").description("Download all attachments from an issue").requiredOption("--output <dir>", "Local directory to save attachments into").addHelpText("after", "\nExamples:\n jiradc issue attachments PROJ-123 --output ./downloads").action(async (key, opts) => {
886
- const client = getClient();
887
- mkdirSync2(opts.output, { recursive: true });
888
- const issue = await client.issues.get({
889
- issueKeyOrId: key,
890
- fields: ["attachment"]
891
- });
892
- const atts = issue.fields.attachment ?? [];
893
- if (atts.length === 0) {
894
- output({ issueKey: key, downloaded: 0, files: [] });
895
- return;
896
- }
897
- const results = [];
898
- const failed = [];
899
- for (const att of atts) {
900
- if (!att.content) {
901
- failed.push({ filename: att.filename, error: "No content URL" });
902
- continue;
903
- }
904
- const destPath = join2(opts.output, att.filename);
905
- try {
906
- await client.issues.downloadAttachment({ url: att.content, destinationPath: destPath });
907
- results.push({ filename: att.filename, size: att.size, path: destPath });
908
- } catch (err) {
909
- failed.push({ filename: att.filename, error: String(err) });
910
- }
911
- }
912
- output({
913
- issueKey: key,
914
- downloaded: results.length,
915
- total: atts.length,
916
- files: results,
917
- ...failed.length > 0 && { failed }
918
- });
919
- });
920
- }
921
-
922
1049
  // src/commands/issue/batch-changelog.ts
923
1050
  function batchChangelog(parent) {
924
1051
  parent.command("batch-changelog <keys>").description("Get changelogs for multiple issues at once").option("--limit <number>", "Max changelog entries per issue (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).addHelpText(
@@ -979,7 +1106,7 @@ function changelog(parent) {
979
1106
  // src/commands/issue/clone.ts
980
1107
  import { unlink } from "fs/promises";
981
1108
  import { tmpdir } from "os";
982
- import { join as join3 } from "path";
1109
+ import { join as join4 } from "path";
983
1110
  var CLONE_FIELDS = [
984
1111
  "summary",
985
1112
  "description",
@@ -1030,7 +1157,7 @@ Examples:
1030
1157
  const tmpFiles = [];
1031
1158
  const copied = await Promise.all(
1032
1159
  f.attachment.map(async (att) => {
1033
- const tmpPath = join3(tmpdir(), `jiradc-clone-${Date.now()}-${att.filename}`);
1160
+ const tmpPath = join4(tmpdir(), `jiradc-clone-${Date.now()}-${att.filename}`);
1034
1161
  tmpFiles.push(tmpPath);
1035
1162
  await client.issues.downloadAttachment({ url: att.content, destinationPath: tmpPath });
1036
1163
  await client.issues.addAttachment({ issueKeyOrId: newKey, filePath: tmpPath });
@@ -1071,26 +1198,65 @@ Examples:
1071
1198
  );
1072
1199
  }
1073
1200
 
1074
- // src/commands/issue/comment-edit.ts
1075
- function commentEdit(parent) {
1076
- parent.command("comment-edit <key>").description("Edit an existing comment").requiredOption("--id <commentId>", "Comment ID to edit").requiredOption("--body <text>", "Updated comment body in wiki markup").addHelpText("after", '\nExamples:\n jiradc issue comment-edit PROJ-123 --id 12345 --body "Updated comment text"').action(async (key, opts) => {
1201
+ // src/commands/issue/comment/create.ts
1202
+ function create2(parent) {
1203
+ const cmd = parent.command("create <key>").description("Add a comment to an issue").addHelpText("after", '\nExamples:\n jiradc issue comment create PROJ-123 --body "Fixed in latest build"');
1204
+ bodyOption(cmd, { mandatory: true });
1205
+ cmd.action(async (key, opts) => {
1077
1206
  const client = getClient();
1078
- const result = await client.issues.editComment({ issueKeyOrId: key, commentId: opts.id, body: opts.body });
1207
+ const result = await client.issues.addComment({ issueKeyOrId: key, body: opts.body });
1079
1208
  output(transformComment(result));
1080
1209
  });
1081
1210
  }
1082
1211
 
1083
- // src/commands/issue/comment.ts
1084
- function comment(parent) {
1085
- parent.command("comment <key>").description("Add a comment to an issue").requiredOption("--body <text>", "Comment body in wiki markup").addHelpText("after", '\nExamples:\n jiradc issue comment PROJ-123 --body "Fixed in latest build"').action(async (key, opts) => {
1212
+ // src/commands/issue/comment/delete.ts
1213
+ function deleteComment(parent) {
1214
+ const cmd = parent.command("delete <key>").description("Delete a comment from an issue").addHelpText("after", "\nExamples:\n jiradc issue comment delete PROJ-123 --comment-id 12345");
1215
+ subEntityOption(cmd, "comment", { mandatory: true });
1216
+ cmd.action(async (key, opts) => {
1086
1217
  const client = getClient();
1087
- const result = await client.issues.addComment({ issueKeyOrId: key, body: opts.body });
1218
+ await client.issues.deleteComment({ issueKeyOrId: key, commentId: String(opts.commentId) });
1219
+ output({ deleted: true, issueKey: key, commentId: opts.commentId });
1220
+ });
1221
+ }
1222
+
1223
+ // src/commands/issue/comment/update.ts
1224
+ function update2(parent) {
1225
+ const cmd = parent.command("update <key>").description("Update an existing comment").addHelpText(
1226
+ "after",
1227
+ '\nExamples:\n jiradc issue comment update PROJ-123 --comment-id 12345 --body "Updated comment text"'
1228
+ );
1229
+ subEntityOption(cmd, "comment", { mandatory: true });
1230
+ bodyOption(cmd, { mandatory: true });
1231
+ cmd.action(async (key, opts) => {
1232
+ const client = getClient();
1233
+ const result = await client.issues.editComment({
1234
+ issueKeyOrId: key,
1235
+ commentId: String(opts.commentId),
1236
+ body: opts.body
1237
+ });
1088
1238
  output(transformComment(result));
1089
1239
  });
1090
1240
  }
1091
1241
 
1242
+ // src/commands/issue/comment/index.ts
1243
+ function registerCommentCommands(parent) {
1244
+ const comment = parent.command("comment").description("Comment operations").addHelpText(
1245
+ "after",
1246
+ `
1247
+ Examples:
1248
+ $ jiradc issue comment create PROJ-123 --body "Fixed in latest build"
1249
+ $ jiradc issue comment update PROJ-123 --comment-id 12345 --body "Updated comment text"
1250
+ $ jiradc issue comment delete PROJ-123 --comment-id 12345
1251
+ `
1252
+ );
1253
+ create2(comment);
1254
+ update2(comment);
1255
+ deleteComment(comment);
1256
+ }
1257
+
1092
1258
  // src/commands/issue/create.ts
1093
- function create2(parent) {
1259
+ function create3(parent) {
1094
1260
  parent.command("create").description("Create a new issue").requiredOption("--project <key>", "Project key or ID").requiredOption("--type <name>", "Issue type name (e.g., Task, Bug, Story)").requiredOption("--summary <text>", "Issue summary/title").option("--description <text>", "Issue description in wiki markup").option("--assignee <username>", "Assignee username").option("--reporter <username>", "Reporter username").option("--priority <name>", "Priority name (e.g., High, Medium, Low)").option("--labels <labels>", "Comma-separated labels").option("--components <names>", "Comma-separated component names").option("--fix-versions <versions>", "Comma-separated fix version names").option("--due-date <date>", "Due date in YYYY-MM-DD format").option("--parent <key>", "Parent issue key (for subtasks)").option("--custom-fields <json>", `Additional custom fields as JSON (e.g., '{"customfield_10100": "EPIC-1"}')`).addHelpText(
1095
1261
  "after",
1096
1262
  `
@@ -1213,22 +1379,6 @@ function devStatus(parent) {
1213
1379
  });
1214
1380
  }
1215
1381
 
1216
- // src/commands/issue/get-worklog.ts
1217
- function getWorklog(parent) {
1218
- parent.command("get-worklog <key>").description("Get worklogs for an issue").option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--limit <number>", "Max results per page (1-1000)", intInRange(1, 1e3), 25).addHelpText(
1219
- "after",
1220
- "\nExamples:\n jiradc issue get-worklog PROJ-123\n jiradc issue get-worklog PROJ-123 --limit 10\n jiradc issue get-worklog PROJ-123 --start 10 --limit 5"
1221
- ).action(async (key, opts) => {
1222
- const client = getClient();
1223
- const result = await client.issues.getWorklogs({
1224
- issueKeyOrId: key,
1225
- startAt: opts.start,
1226
- maxResults: opts.limit
1227
- });
1228
- output(transformPaged(result, transformWorklog));
1229
- });
1230
- }
1231
-
1232
1382
  // src/utils/constants.ts
1233
1383
  var DEFAULT_FIELDS = [
1234
1384
  "summary",
@@ -1440,7 +1590,7 @@ function buildSetValue(parsed, wrap) {
1440
1590
  if (parsed.mode !== "set") return void 0;
1441
1591
  return parsed.values.map(wrap);
1442
1592
  }
1443
- function update2(parent) {
1593
+ function update3(parent) {
1444
1594
  parent.command("update <key>").description("Update issue fields").option("--fields <json>", "JSON string of fields to update (advanced; merges with shortcuts, wins on conflict)").option("--no-notify-users", "Suppress notification emails (default: notify)").option("--attachments <paths>", "Comma-separated local file paths to attach").option("--summary <text>", "Set the issue summary").option("--description <text>", "Set the issue description (wiki markup)").option("--priority <name>", "Set the priority by name (e.g. High)").option("--assignee <user>", 'Set the assignee. Username, "me", or "none" to unassign.').option("--labels <list>", 'Set labels ("a,b,c") or mutate ("+add,-remove")').option("--components <list>", 'Set components ("a,b") or mutate ("+add,-remove")').option("--fix-versions <list>", 'Set fix versions ("1.0,2.0") or mutate ("+1.0,-0.9")').addHelpText(
1445
1595
  "after",
1446
1596
  `
@@ -1521,11 +1671,11 @@ Examples:
1521
1671
  });
1522
1672
  }
1523
1673
 
1524
- // src/commands/issue/worklog.ts
1525
- function worklog(parent) {
1526
- parent.command("worklog <key>").description("Log time spent on an issue").requiredOption("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')").option("--comment <text>", "Worklog comment").option("--started <datetime>", "Start time in ISO 8601 format").addHelpText(
1674
+ // src/commands/issue/worklog/create.ts
1675
+ function create4(parent) {
1676
+ parent.command("create <key>").description("Log time spent on an issue").requiredOption("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')").option("--comment <text>", "Worklog comment").option("--started <datetime>", "Start time in ISO 8601 format").addHelpText(
1527
1677
  "after",
1528
- '\nExamples:\n jiradc issue worklog PROJ-123 --time 2h\n jiradc issue worklog PROJ-123 --time "1d 4h" --comment "Backend implementation"\n jiradc issue worklog PROJ-123 --time 3h --started "2026-03-19T09:00:00.000+0000"'
1678
+ '\nExamples:\n jiradc issue worklog create PROJ-123 --time 2h\n jiradc issue worklog create PROJ-123 --time "1d 4h" --comment "Backend implementation"\n jiradc issue worklog create PROJ-123 --time 3h --started "2026-03-19T09:00:00.000+0000"'
1529
1679
  ).action(async (key, opts) => {
1530
1680
  const client = getClient();
1531
1681
  const result = await client.issues.addWorklog({
@@ -1538,9 +1688,93 @@ function worklog(parent) {
1538
1688
  });
1539
1689
  }
1540
1690
 
1691
+ // src/commands/issue/worklog/delete.ts
1692
+ import { Option as Option4 } from "commander";
1693
+ var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
1694
+ function deleteWorklog(parent) {
1695
+ const cmd = parent.command("delete <key>").description("Delete a worklog entry");
1696
+ subEntityOption(cmd, "worklog", { mandatory: true });
1697
+ cmd.addOption(new Option4("--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"').option("--increase-by <amount>", 'Amount to increase the estimate by; required when --adjust-estimate is "manual"').addHelpText(
1698
+ "after",
1699
+ "\nExamples:\n jiradc issue worklog delete PROJ-123 --worklog-id 12345\n jiradc issue worklog delete PROJ-123 --worklog-id 12345 --adjust-estimate leave\n jiradc issue worklog delete PROJ-123 --worklog-id 12345 --adjust-estimate new --new-estimate 2h"
1700
+ ).action(
1701
+ async (key, opts) => {
1702
+ const client = getClient();
1703
+ await client.issues.deleteWorklog({
1704
+ issueKeyOrId: key,
1705
+ worklogId: String(opts.worklogId),
1706
+ adjustEstimate: opts.adjustEstimate,
1707
+ newEstimate: opts.newEstimate,
1708
+ increaseBy: opts.increaseBy
1709
+ });
1710
+ output({ deleted: true, issueKey: key, worklogId: opts.worklogId });
1711
+ }
1712
+ );
1713
+ }
1714
+
1715
+ // src/commands/issue/worklog/list.ts
1716
+ function list4(parent) {
1717
+ parent.command("list <key>").description("Get worklogs for an issue").option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--limit <number>", "Max results per page (1-1000)", intInRange(1, 1e3), 25).addHelpText(
1718
+ "after",
1719
+ "\nExamples:\n jiradc issue worklog list PROJ-123\n jiradc issue worklog list PROJ-123 --limit 10\n jiradc issue worklog list PROJ-123 --start 10 --limit 5"
1720
+ ).action(async (key, opts) => {
1721
+ const client = getClient();
1722
+ const result = await client.issues.getWorklogs({
1723
+ issueKeyOrId: key,
1724
+ startAt: opts.start,
1725
+ maxResults: opts.limit
1726
+ });
1727
+ output(transformPaged(result, transformWorklog));
1728
+ });
1729
+ }
1730
+
1731
+ // src/commands/issue/worklog/update.ts
1732
+ import { Option as Option5 } from "commander";
1733
+ var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
1734
+ function update4(parent) {
1735
+ const cmd = parent.command("update <key>").description("Update an existing worklog entry");
1736
+ subEntityOption(cmd, "worklog", { mandatory: true });
1737
+ cmd.option("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')").option("--comment <text>", "Worklog comment").option("--started <datetime>", "Start time in ISO 8601 format").addOption(new Option5("--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"').addHelpText(
1738
+ "after",
1739
+ '\nExamples:\n jiradc issue worklog update PROJ-123 --worklog-id 12345 --time "1h 30m"\n jiradc issue worklog update PROJ-123 --worklog-id 12345 --comment "Revised note"\n jiradc issue worklog update PROJ-123 --worklog-id 12345 --time 2h --adjust-estimate new --new-estimate 4h'
1740
+ ).action(
1741
+ async (key, opts) => {
1742
+ const client = getClient();
1743
+ const result = await client.issues.updateWorklog({
1744
+ issueKeyOrId: key,
1745
+ worklogId: String(opts.worklogId),
1746
+ timeSpent: opts.time,
1747
+ comment: opts.comment,
1748
+ started: opts.started,
1749
+ adjustEstimate: opts.adjustEstimate,
1750
+ newEstimate: opts.newEstimate
1751
+ });
1752
+ output(transformWorklog(result));
1753
+ }
1754
+ );
1755
+ }
1756
+
1757
+ // src/commands/issue/worklog/index.ts
1758
+ function registerWorklogCommands(parent) {
1759
+ const worklog = parent.command("worklog").description("Worklog operations").addHelpText(
1760
+ "after",
1761
+ `
1762
+ Examples:
1763
+ $ jiradc issue worklog create PROJ-123 --time 2h --comment "Backend work"
1764
+ $ jiradc issue worklog list PROJ-123 --limit 10
1765
+ $ jiradc issue worklog update PROJ-123 --worklog-id 12345 --time "1h 30m"
1766
+ $ jiradc issue worklog delete PROJ-123 --worklog-id 12345
1767
+ `
1768
+ );
1769
+ create4(worklog);
1770
+ list4(worklog);
1771
+ update4(worklog);
1772
+ deleteWorklog(worklog);
1773
+ }
1774
+
1541
1775
  // src/commands/issue/index.ts
1542
- function registerIssueCommands(program2) {
1543
- const issue = program2.command("issue").description("Issue operations").addHelpText(
1776
+ function registerIssueCommands(program) {
1777
+ const issue = program.command("issue").description("Issue operations").addHelpText(
1544
1778
  "after",
1545
1779
  `
1546
1780
  Examples:
@@ -1554,16 +1788,14 @@ Examples:
1554
1788
  );
1555
1789
  get2(issue);
1556
1790
  search2(issue);
1557
- create2(issue);
1558
- update2(issue);
1791
+ create3(issue);
1792
+ update3(issue);
1559
1793
  deleteIssue(issue);
1560
1794
  transition(issue);
1561
1795
  transitions(issue);
1562
1796
  assign(issue);
1563
- comment(issue);
1564
- commentEdit(issue);
1565
- worklog(issue);
1566
- getWorklog(issue);
1797
+ registerCommentCommands(issue);
1798
+ registerWorklogCommands(issue);
1567
1799
  changelog(issue);
1568
1800
  batchChangelog(issue);
1569
1801
  link(issue);
@@ -1571,14 +1803,13 @@ Examples:
1571
1803
  linkTypes(issue);
1572
1804
  linkEpic(issue);
1573
1805
  registerAttachmentCommands(issue);
1574
- attachments(issue);
1575
1806
  batchCreate(issue);
1576
1807
  clone(issue);
1577
1808
  devStatus(issue);
1578
1809
  }
1579
1810
 
1580
1811
  // src/commands/project/list.ts
1581
- function list4(parent) {
1812
+ function list5(parent) {
1582
1813
  parent.command("list").description("List all projects").option("--expand <expand>", 'Expand options (e.g., "description,lead")').option("--include-archived", "Include archived projects (default: false)").addHelpText(
1583
1814
  "after",
1584
1815
  "\nExamples:\n jiradc project list\n jiradc project list --expand description,lead\n jiradc project list --include-archived"
@@ -1602,8 +1833,8 @@ function versions(parent) {
1602
1833
  }
1603
1834
 
1604
1835
  // src/commands/project/index.ts
1605
- function registerProjectCommands(program2) {
1606
- const project = program2.command("project").description("Project operations").addHelpText(
1836
+ function registerProjectCommands(program) {
1837
+ const project = program.command("project").description("Project operations").addHelpText(
1607
1838
  "after",
1608
1839
  `
1609
1840
  Examples:
@@ -1611,12 +1842,12 @@ Examples:
1611
1842
  $ jiradc project versions PROJ
1612
1843
  `
1613
1844
  );
1614
- list4(project);
1845
+ list5(project);
1615
1846
  versions(project);
1616
1847
  }
1617
1848
 
1618
1849
  // src/commands/sprint/create.ts
1619
- function create3(parent) {
1850
+ function create5(parent) {
1620
1851
  parent.command("create").description("Create a new sprint").requiredOption("--board <id>", "Board ID to create sprint in", positiveInt).requiredOption("--name <name>", "Sprint name").option("--start-date <date>", "Start date in ISO 8601 format").option("--end-date <date>", "End date in ISO 8601 format").option("--goal <goal>", "Sprint goal").addHelpText(
1621
1852
  "after",
1622
1853
  '\nExamples:\n jiradc sprint create --board 42 --name "Sprint 10"\n jiradc sprint create --board 42 --name "Sprint 10" --start-date 2026-03-20 --end-date 2026-04-03 --goal "Complete auth module"'
@@ -1633,10 +1864,20 @@ function create3(parent) {
1633
1864
  });
1634
1865
  }
1635
1866
 
1636
- // src/commands/sprint/issues.ts
1867
+ // src/commands/sprint/delete.ts
1637
1868
  import { Argument as Argument2 } from "commander";
1869
+ function deleteSprint(parent) {
1870
+ parent.command("delete").description("Delete a sprint (returns its issues to the backlog)").addArgument(new Argument2("<id>", "Sprint ID").argParser(positiveInt)).addHelpText("after", "\nExamples:\n jiradc sprint delete 100").action(async (id) => {
1871
+ const client = getClient();
1872
+ await client.agile.deleteSprint({ sprintId: id });
1873
+ output({ deleted: true, sprintId: id });
1874
+ });
1875
+ }
1876
+
1877
+ // src/commands/sprint/issues.ts
1878
+ import { Argument as Argument3 } from "commander";
1638
1879
  function issues2(parent) {
1639
- parent.command("issues").description("Get issues in a sprint").addArgument(new Argument2("<id>", "Sprint ID").argParser(positiveInt)).option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the sprint").addHelpText(
1880
+ parent.command("issues").description("Get issues in a sprint").addArgument(new Argument3("<id>", "Sprint ID").argParser(positiveInt)).option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the sprint").addHelpText(
1640
1881
  "after",
1641
1882
  '\nExamples:\n jiradc sprint issues 100\n jiradc sprint issues 100 --limit 20\n jiradc sprint issues 100 --jql "status = Done" --fields summary,status\n jiradc sprint issues 100 --start 50 --limit 25'
1642
1883
  ).action(async (id, opts) => {
@@ -1653,10 +1894,10 @@ function issues2(parent) {
1653
1894
  }
1654
1895
 
1655
1896
  // src/commands/sprint/list.ts
1656
- import { Option as Option4 } from "commander";
1897
+ import { Option as Option6 } from "commander";
1657
1898
  var SPRINT_STATES = ["future", "active", "closed"];
1658
- function list5(parent) {
1659
- parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new Option4("--state <state>", "Filter by sprint state").choices(SPRINT_STATES)).addHelpText(
1899
+ function list6(parent) {
1900
+ parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new Option6("--state <state>", "Filter by sprint state").choices(SPRINT_STATES)).addHelpText(
1660
1901
  "after",
1661
1902
  "\nExamples:\n jiradc sprint list --board 42\n jiradc sprint list --board 42 --state active"
1662
1903
  ).action(async (opts) => {
@@ -1670,10 +1911,10 @@ function list5(parent) {
1670
1911
  }
1671
1912
 
1672
1913
  // src/commands/sprint/update.ts
1673
- import { Argument as Argument3, Option as Option5 } from "commander";
1914
+ import { Argument as Argument4, Option as Option7 } from "commander";
1674
1915
  var SPRINT_STATES2 = ["future", "active", "closed"];
1675
- function update3(parent) {
1676
- parent.command("update").description("Update an existing sprint").addArgument(new Argument3("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name").addOption(new Option5("--state <state>", "New sprint state").choices(SPRINT_STATES2)).option("--start-date <date>", "New start date in ISO 8601 format").option("--end-date <date>", "New end date in ISO 8601 format").option("--goal <goal>", "New sprint goal").addHelpText(
1916
+ function update5(parent) {
1917
+ parent.command("update").description("Update an existing sprint").addArgument(new Argument4("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name").addOption(new Option7("--state <state>", "New sprint state").choices(SPRINT_STATES2)).option("--start-date <date>", "New start date in ISO 8601 format").option("--end-date <date>", "New end date in ISO 8601 format").option("--goal <goal>", "New sprint goal").addHelpText(
1677
1918
  "after",
1678
1919
  '\nExamples:\n jiradc sprint update 100 --name "Sprint 10 - Extended"\n jiradc sprint update 100 --state active\n jiradc sprint update 100 --end-date 2026-04-10 --goal "Updated goal"'
1679
1920
  ).action(
@@ -1693,8 +1934,8 @@ function update3(parent) {
1693
1934
  }
1694
1935
 
1695
1936
  // src/commands/sprint/index.ts
1696
- function registerSprintCommands(program2) {
1697
- const sprint = program2.command("sprint").description("Sprint operations").addHelpText(
1937
+ function registerSprintCommands(program) {
1938
+ const sprint = program.command("sprint").description("Sprint operations").addHelpText(
1698
1939
  "after",
1699
1940
  `
1700
1941
  Examples:
@@ -1704,10 +1945,11 @@ Examples:
1704
1945
  $ jiradc sprint create --board 42 --name "Sprint 10" --start-date 2026-04-01 --end-date 2026-04-14
1705
1946
  `
1706
1947
  );
1707
- list5(sprint);
1948
+ list6(sprint);
1708
1949
  issues2(sprint);
1709
- create3(sprint);
1710
- update3(sprint);
1950
+ create5(sprint);
1951
+ update5(sprint);
1952
+ deleteSprint(sprint);
1711
1953
  }
1712
1954
 
1713
1955
  // src/commands/token/client.ts
@@ -1736,7 +1978,7 @@ function getTokenClient(options2 = {}) {
1736
1978
  }
1737
1979
 
1738
1980
  // src/commands/token/create.ts
1739
- function create4(parent) {
1981
+ function create6(parent) {
1740
1982
  parent.command("create").description("Create a Personal Access Token. The secret is returned exactly once.").requiredOption("--name <name>", "Token name").option(
1741
1983
  "--expiration-duration <days>",
1742
1984
  "Token lifetime in days. Omit for a non-expiring token (admin policy permitting).",
@@ -1767,7 +2009,7 @@ Examples:
1767
2009
  }
1768
2010
 
1769
2011
  // src/commands/token/list.ts
1770
- function list6(parent) {
2012
+ function list7(parent) {
1771
2013
  parent.command("list").description("List Personal Access Tokens owned by the authenticated user (secrets not included)").option("--basic-username <u>", "Override $JIRA_BASIC_USERNAME").option(
1772
2014
  "--basic-password <p>",
1773
2015
  "Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)"
@@ -1782,36 +2024,38 @@ function list6(parent) {
1782
2024
 
1783
2025
  // src/commands/token/revoke.ts
1784
2026
  function revoke(parent) {
1785
- parent.command("revoke").description("Revoke a Personal Access Token by id").requiredOption("--id <tokenId>", "Token id to revoke", positiveInt).option("--basic-username <u>", "Override $JIRA_BASIC_USERNAME").option(
2027
+ const cmd = parent.command("revoke").description("Revoke a Personal Access Token by id").option("--basic-username <u>", "Override $JIRA_BASIC_USERNAME").option(
1786
2028
  "--basic-password <p>",
1787
2029
  "Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)"
1788
- ).action(async (opts) => {
2030
+ );
2031
+ subjectArg(cmd, "tokenId", { parser: positiveInt, description: "Token id to revoke" });
2032
+ cmd.action(async (tokenId, opts) => {
1789
2033
  const { client, username, password } = getTokenClient({
1790
2034
  basicUsername: opts.basicUsername,
1791
2035
  basicPassword: opts.basicPassword
1792
2036
  });
1793
- await client.accessTokens.revoke({ username, password, tokenId: opts.id });
1794
- output({ revoked: opts.id });
2037
+ await client.accessTokens.revoke({ username, password, tokenId });
2038
+ output({ revoked: tokenId });
1795
2039
  });
1796
2040
  }
1797
2041
 
1798
2042
  // src/commands/token/index.ts
1799
- function registerTokenCommands(program2) {
1800
- const token = program2.command("token").description("Personal Access Token management").addHelpText(
2043
+ function registerTokenCommands(program) {
2044
+ const token = program.command("token").description("Personal Access Token management").addHelpText(
1801
2045
  "after",
1802
2046
  `
1803
2047
  Examples:
1804
2048
  $ jiradc token list
1805
2049
  $ jiradc token create --name my-pat
1806
- $ jiradc token revoke --id 173
2050
+ $ jiradc token revoke 173
1807
2051
 
1808
2052
  Auth:
1809
2053
  Requires JIRA_BASIC_USERNAME + JIRA_BASIC_PASSWORD
1810
2054
  (or --basic-username / --basic-password on any subcommand).
1811
2055
  `
1812
2056
  );
1813
- create4(token);
1814
- list6(token);
2057
+ create6(token);
2058
+ list7(token);
1815
2059
  revoke(token);
1816
2060
  }
1817
2061
 
@@ -1852,8 +2096,8 @@ function search3(parent) {
1852
2096
  }
1853
2097
 
1854
2098
  // src/commands/user/index.ts
1855
- function registerUserCommands(program2) {
1856
- const user = program2.command("user").description("User operations").addHelpText(
2099
+ function registerUserCommands(program) {
2100
+ const user = program.command("user").description("User operations").addHelpText(
1857
2101
  "after",
1858
2102
  `
1859
2103
  Examples:
@@ -1867,31 +2111,22 @@ Examples:
1867
2111
  search3(user);
1868
2112
  }
1869
2113
 
1870
- // src/index.ts
1871
- function readPackageVersion() {
1872
- try {
1873
- const here = dirname(fileURLToPath(import.meta.url));
1874
- const pkgPath = join4(here, "..", "package.json");
1875
- const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
1876
- return pkg.version ?? "0.0.0";
1877
- } catch {
1878
- return "0.0.0";
1879
- }
1880
- }
2114
+ // src/program.ts
1881
2115
  var DIM = "\x1B[2m";
1882
2116
  var RESET = "\x1B[0m";
1883
- var program = new Command8();
1884
- program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion()).configureHelp({
1885
- styleTitle: (str) => styleText("bold", str),
1886
- styleUsage: (str) => styleText("dim", str),
1887
- styleCommandDescription: (str) => styleText("dim", str),
1888
- styleOptionDescription: (str) => styleText("dim", str),
1889
- styleSubcommandDescription: (str) => styleText("dim", str)
1890
- }).addHelpText("beforeAll", `
2117
+ function buildProgram() {
2118
+ const program = new Command11();
2119
+ program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion(import.meta.url)).configureHelp({
2120
+ styleTitle: (str) => styleText("bold", str),
2121
+ styleUsage: (str) => styleText("dim", str),
2122
+ styleCommandDescription: (str) => styleText("dim", str),
2123
+ styleOptionDescription: (str) => styleText("dim", str),
2124
+ styleSubcommandDescription: (str) => styleText("dim", str)
2125
+ }).addHelpText("beforeAll", `
1891
2126
  ${styleText("bold", "jiradc")} ${DIM}\u2014 Jira Data Center CLI${RESET}
1892
2127
  `).addHelpText(
1893
- "after",
1894
- `
2128
+ "after",
2129
+ `
1895
2130
  ${styleText("bold", "Environment:")}
1896
2131
  JIRA_URL Jira Server base URL ${DIM}(e.g., https://jira.example.com)${RESET}
1897
2132
  JIRA_TOKEN Personal Access Token ${DIM}(generate in Jira > Profile > Personal Access Tokens)${RESET}
@@ -1905,21 +2140,46 @@ ${styleText("bold", "Examples:")}
1905
2140
  ${DIM}$${RESET} jiradc board list --type scrum
1906
2141
  ${DIM}$${RESET} jiradc sprint list --board 42 --state active
1907
2142
  `
1908
- );
1909
- program.option("--pretty", "Pretty-print JSON output");
1910
- program.hook("preAction", (thisCommand) => {
1911
- if (thisCommand.optsWithGlobals().pretty) setPretty(true);
1912
- });
1913
- registerIssueCommands(program);
1914
- registerProjectCommands(program);
1915
- registerComponentCommands(program);
1916
- registerBoardCommands(program);
1917
- registerSprintCommands(program);
1918
- registerFieldCommands(program);
1919
- registerUserCommands(program);
1920
- registerTokenCommands(program);
1921
- try {
1922
- await program.parseAsync();
1923
- } catch (err) {
1924
- handleError(err);
2143
+ );
2144
+ program.option("--pretty", "Pretty-print JSON output");
2145
+ program.hook("preAction", (thisCommand) => {
2146
+ if (thisCommand.optsWithGlobals().pretty) setPretty(true);
2147
+ });
2148
+ registerIssueCommands(program);
2149
+ registerProjectCommands(program);
2150
+ registerComponentCommands(program);
2151
+ registerBoardCommands(program);
2152
+ registerSprintCommands(program);
2153
+ registerFieldCommands(program);
2154
+ registerUserCommands(program);
2155
+ registerTokenCommands(program);
2156
+ return program;
2157
+ }
2158
+
2159
+ // src/utils/credentials.ts
2160
+ function getCredentialInfo() {
2161
+ const baseUrl = process.env.JIRA_URL;
2162
+ const token = process.env.JIRA_TOKEN;
2163
+ return {
2164
+ environment: {
2165
+ JIRA_URL: {
2166
+ value: baseUrl ?? null,
2167
+ description: "Your Jira Server base URL (e.g., https://jira.example.com)"
2168
+ },
2169
+ JIRA_TOKEN: {
2170
+ value: token ? "<set>" : null,
2171
+ description: "Personal Access Token"
2172
+ }
2173
+ },
2174
+ tokenUrl: `${baseUrl ?? "https://jira.example.com"}/secure/ViewProfile.jspa`,
2175
+ hint: "Set these as environment variables in the process that runs this CLI (e.g. shell export, container env, or CI/secret store)."
2176
+ };
1925
2177
  }
2178
+
2179
+ // src/index.ts
2180
+ await runCli(buildProgram(), {
2181
+ credentialInfo: getCredentialInfo,
2182
+ service: "Jira",
2183
+ authRecovery: "Provide JIRA_URL and JIRA_TOKEN as environment variables to this process (shell export, container env, or CI secret). error.detail lists which are currently set and where to create a token.",
2184
+ networkRecovery: "Verify JIRA_URL is correct, the server is reachable, and you are on the VPN if required."
2185
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jiradc-cli",
3
- "version": "1.0.20",
3
+ "version": "2.0.0-g7be3698.7",
4
4
  "publish": true,
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -12,7 +12,8 @@
12
12
  ],
13
13
  "dependencies": {
14
14
  "commander": "^13.1.0",
15
- "jira-data-center-client": "1.0.36"
15
+ "cli-utils": "1.0.0",
16
+ "jira-data-center-client": "1.0.37"
16
17
  },
17
18
  "devDependencies": {
18
19
  "@types/node": "24.10.4",
@@ -34,6 +35,6 @@
34
35
  "lint": "eslint src",
35
36
  "lint:fix": "eslint src --fix",
36
37
  "test": "vitest run",
37
- "test:integration": "vitest run tests/integration"
38
+ "test:integration": "vitest run --config vitest.integration.config.ts"
38
39
  }
39
40
  }