jiradc-cli 1.0.20 → 1.0.22

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 +789 -545
  3. package/package.json +6 -5
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,281 @@ function positiveInt(raw) {
39
48
  return n;
40
49
  }
41
50
 
42
- // src/utils/client.ts
43
- import { JiraClient } from "jira-data-center-client";
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
+ var EXAMPLES = /* @__PURE__ */ new WeakSet();
84
+ function commandPath(cmd) {
85
+ const parts = [];
86
+ for (let c = cmd; c; c = c.parent)
87
+ parts.unshift(c.name());
88
+ return parts.join(" ");
89
+ }
90
+ function exampleLine(path, entry) {
91
+ if (typeof entry === "string")
92
+ return entry ? ` ${path} ${entry}` : ` ${path}`;
93
+ if ("raw" in entry)
94
+ return ` ${entry.raw}`;
95
+ const [args, comment] = entry;
96
+ const line = args ? `${path} ${args}` : path;
97
+ return ` ${line} # ${comment}`;
98
+ }
99
+ function examples(cmd, entries) {
100
+ EXAMPLES.add(cmd);
101
+ return cmd.addHelpText("after", () => {
102
+ const path = commandPath(cmd);
103
+ return `
104
+ Examples:
105
+ ${entries.map((e) => exampleLine(path, e)).join("\n")}`;
106
+ });
107
+ }
44
108
 
45
- // src/utils/credentials.ts
46
- function getCredentialInfo() {
47
- const baseUrl = process.env.JIRA_URL;
48
- const token = process.env.JIRA_TOKEN;
109
+ // ../../cli-utils/dist/errors.js
110
+ import { CommanderError } from "commander";
111
+ var EXIT = {
112
+ SUCCESS: 0,
113
+ GENERIC: 1,
114
+ USAGE: 2,
115
+ NOT_FOUND: 3,
116
+ FORBIDDEN: 4,
117
+ CONFLICT: 5,
118
+ AUTH: 6
119
+ };
120
+ var CliAuthError = class extends Error {
121
+ constructor(message) {
122
+ super(message);
123
+ this.name = "CliAuthError";
124
+ }
125
+ };
126
+ var TYPE_EXIT = {
127
+ usage: EXIT.USAGE,
128
+ not_found: EXIT.NOT_FOUND,
129
+ forbidden: EXIT.FORBIDDEN,
130
+ conflict: EXIT.CONFLICT,
131
+ auth: EXIT.AUTH,
132
+ rate_limited: EXIT.GENERIC,
133
+ server: EXIT.GENERIC,
134
+ network: EXIT.GENERIC,
135
+ unknown: EXIT.GENERIC
136
+ };
137
+ var NETWORK_CODES = ["ENOTFOUND", "ECONNREFUSED", "ECONNRESET", "ETIMEDOUT"];
138
+ function httpStatus(err) {
139
+ const e = err;
140
+ return e?.response?.status ?? e?.statusCode;
141
+ }
142
+ function responseDetail(err) {
143
+ const e = err;
144
+ const data = e?.response?.data;
145
+ if (data && typeof data === "object")
146
+ return data;
147
+ const body = e?.body;
148
+ if (typeof body === "string") {
149
+ try {
150
+ const parsed = JSON.parse(body);
151
+ return parsed.error ?? parsed;
152
+ } catch {
153
+ return void 0;
154
+ }
155
+ }
156
+ if (body && typeof body === "object") {
157
+ return body.error ?? body;
158
+ }
159
+ return void 0;
160
+ }
161
+ function errorCode(err) {
162
+ return err?.code;
163
+ }
164
+ function normalize(err, opts) {
165
+ const message = err instanceof Error ? err.message : String(err);
166
+ if (err instanceof CommanderError) {
167
+ return {
168
+ type: "usage",
169
+ message: (message || "Invalid command usage").replace(/^error:\s+/, ""),
170
+ recovery: "Check the command syntax and flags; run the command with --help.",
171
+ retryable: false
172
+ };
173
+ }
174
+ if (err instanceof CliAuthError) {
175
+ return { type: "auth", message: message || "Missing credentials", recovery: opts.authRecovery, retryable: false };
176
+ }
177
+ const status = httpStatus(err);
178
+ const detail = responseDetail(err);
179
+ if (status !== void 0) {
180
+ switch (status) {
181
+ case 400:
182
+ return {
183
+ type: "usage",
184
+ status,
185
+ message: "Bad request (HTTP 400)",
186
+ recovery: "Check parameter values (ids, keys, query syntax) against the API.",
187
+ retryable: false,
188
+ detail
189
+ };
190
+ case 401:
191
+ return {
192
+ type: "auth",
193
+ status,
194
+ message: "Authentication failed (HTTP 401)",
195
+ recovery: opts.authRecovery,
196
+ retryable: false
197
+ };
198
+ case 403:
199
+ return {
200
+ type: "forbidden",
201
+ status,
202
+ message: "Forbidden (HTTP 403)",
203
+ recovery: `Your ${opts.service} account lacks permission for this operation; check token scope and resource permissions.`,
204
+ retryable: false,
205
+ detail
206
+ };
207
+ case 404:
208
+ return {
209
+ type: "not_found",
210
+ status,
211
+ message: "Not found (HTTP 404)",
212
+ recovery: "Verify the id/key exists and that you have access to it.",
213
+ retryable: false,
214
+ detail
215
+ };
216
+ case 409:
217
+ return {
218
+ type: "conflict",
219
+ status,
220
+ message: "Conflict (HTTP 409)",
221
+ recovery: "The resource changed or already exists; re-fetch current state and retry.",
222
+ retryable: false,
223
+ detail
224
+ };
225
+ case 429:
226
+ return {
227
+ type: "rate_limited",
228
+ status,
229
+ message: "Rate limited (HTTP 429)",
230
+ recovery: "Wait and retry the request.",
231
+ retryable: true
232
+ };
233
+ }
234
+ if (status >= 500) {
235
+ return {
236
+ type: "server",
237
+ status,
238
+ message: `Server error (HTTP ${status})`,
239
+ recovery: `${opts.service} returned an internal error; retry shortly.`,
240
+ retryable: true,
241
+ detail
242
+ };
243
+ }
244
+ return {
245
+ type: "unknown",
246
+ status,
247
+ message: `${opts.service} error (HTTP ${status}): ${message}`,
248
+ recovery: "Inspect the detail field for the API response.",
249
+ retryable: false,
250
+ detail
251
+ };
252
+ }
253
+ const code = errorCode(err);
254
+ if (code && NETWORK_CODES.includes(code) || NETWORK_CODES.some((c) => message.includes(c))) {
255
+ return {
256
+ type: "network",
257
+ message: `Cannot connect to ${opts.service}: ${message}`,
258
+ recovery: opts.networkRecovery ?? "Verify the *_URL is correct, the server is reachable, and you are on the VPN if required.",
259
+ retryable: true
260
+ };
261
+ }
262
+ return { type: "unknown", message, recovery: "Unexpected error; inspect the message.", retryable: false };
263
+ }
264
+ function classifyError(err, opts) {
265
+ const base = normalize(err, opts);
266
+ const n = opts.adapt ? opts.adapt(base, err) : base;
267
+ if (n.type === "auth" && n.detail === void 0 && opts.credentialInfo) {
268
+ n.detail = opts.credentialInfo();
269
+ }
49
270
  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"
271
+ envelope: {
272
+ error: {
273
+ type: n.type,
274
+ message: n.message,
275
+ recovery: n.recovery,
276
+ retryable: n.retryable,
277
+ ...n.detail !== void 0 ? { detail: n.detail } : {}
58
278
  }
59
279
  },
60
- tokenUrl: `${baseUrl ?? "https://jira.example.com"}/secure/ViewProfile.jspa`,
61
- hint: "Export environment variables in your shell profile (e.g., ~/.zshrc)."
280
+ exitCode: TYPE_EXIT[n.type]
281
+ };
282
+ }
283
+ function createErrorHandler(opts) {
284
+ return (err) => {
285
+ const { envelope, exitCode } = classifyError(err, opts);
286
+ process.stderr.write(`${JSON.stringify(envelope)}
287
+ `);
288
+ return process.exit(exitCode);
62
289
  };
63
290
  }
291
+ function isCleanCommanderExit(err) {
292
+ return err instanceof CommanderError && (err.exitCode === 0 || err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version");
293
+ }
294
+ function routeErrors(cmd) {
295
+ cmd.exitOverride();
296
+ cmd.configureOutput({ writeErr: () => void 0 });
297
+ cmd.commands.forEach(routeErrors);
298
+ }
299
+ async function runCli(program, opts) {
300
+ routeErrors(program);
301
+ try {
302
+ await program.parseAsync();
303
+ } catch (err) {
304
+ if (isCleanCommanderExit(err)) {
305
+ process.exit(err.exitCode ?? 0);
306
+ }
307
+ createErrorHandler(opts)(err);
308
+ }
309
+ }
310
+
311
+ // src/program.ts
312
+ import { styleText } from "util";
313
+ import { Command as Command11 } from "commander";
314
+
315
+ // src/commands/board/issues.ts
316
+ import { Argument } from "commander";
64
317
 
65
318
  // src/utils/client.ts
319
+ import { JiraClient } from "jira-data-center-client";
66
320
  function getClient() {
67
321
  const baseUrl = process.env.JIRA_URL;
68
322
  const token = process.env.JIRA_TOKEN;
69
323
  if (!baseUrl || !token) {
70
324
  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);
325
+ throw new CliAuthError(`Missing required environment variables: ${missing.join(", ")}`);
79
326
  }
80
327
  return new JiraClient({ baseUrl, token });
81
328
  }
@@ -89,68 +336,6 @@ function output(data) {
89
336
  process.stdout.write(`${JSON.stringify(data, null, prettyPrint ? 2 : void 0)}
90
337
  `);
91
338
  }
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
339
 
155
340
  // src/utils/transformers/base.ts
156
341
  function jiraBaseUrl() {
@@ -379,8 +564,8 @@ function transformIssueFields(fields) {
379
564
  issuelinks,
380
565
  subtasks,
381
566
  parent,
382
- comment: comment2,
383
- worklog: worklog2,
567
+ comment,
568
+ worklog,
384
569
  attachment,
385
570
  // required scalars / structured fields we always keep verbatim
386
571
  summary,
@@ -412,20 +597,20 @@ function transformIssueFields(fields) {
412
597
  // Drop empty comment / worklog containers entirely. The default Jira
413
598
  // search response includes both wrappers on every issue regardless of
414
599
  // count; on a 25-issue page that's 25 × 2 empty objects of pure noise.
415
- ...comment2 && comment2.comments.length > 0 ? {
600
+ ...comment && comment.comments.length > 0 ? {
416
601
  comment: {
417
- comments: comment2.comments.map(transformComment),
418
- maxResults: comment2.maxResults,
419
- total: comment2.total,
420
- startAt: comment2.startAt
602
+ comments: comment.comments.map(transformComment),
603
+ maxResults: comment.maxResults,
604
+ total: comment.total,
605
+ startAt: comment.startAt
421
606
  }
422
607
  } : {},
423
- ...worklog2 && worklog2.worklogs.length > 0 ? {
608
+ ...worklog && worklog.worklogs.length > 0 ? {
424
609
  worklog: {
425
- worklogs: worklog2.worklogs.map(transformWorklog),
426
- maxResults: worklog2.maxResults,
427
- total: worklog2.total,
428
- startAt: worklog2.startAt
610
+ worklogs: worklog.worklogs.map(transformWorklog),
611
+ maxResults: worklog.maxResults,
612
+ total: worklog.total,
613
+ startAt: worklog.startAt
429
614
  }
430
615
  } : {},
431
616
  ...attachment && attachment.length > 0 ? { attachment: attachment.map(transformAttachment) } : {}
@@ -477,10 +662,14 @@ function transformTransitions(response) {
477
662
 
478
663
  // src/commands/board/issues.ts
479
664
  function issues(parent) {
480
- parent.command("issues").description("Get issues for a board").addArgument(new Argument("<id>", "Board ID").argParser(positiveInt)).option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the board").addHelpText(
481
- "after",
482
- '\nExamples:\n jiradc board issues 42\n jiradc board issues 42 --limit 10\n jiradc board issues 42 --jql "status = Open" --fields summary,status\n jiradc board issues 42 --start 50 --limit 25'
483
- ).action(async (id, opts) => {
665
+ const cmd = parent.command("issues").description("Get issues for a board").addArgument(new Argument("<id>", "Board ID").argParser(positiveInt)).option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated field names to return").option("--jql <jql>", "Additional JQL filter within the board");
666
+ examples(cmd, [
667
+ "42",
668
+ "42 --limit 10",
669
+ '42 --jql "status = Open" --fields summary,status',
670
+ "42 --start 50 --limit 25"
671
+ ]);
672
+ cmd.action(async (id, opts) => {
484
673
  const client = getClient();
485
674
  const result = await client.agile.getBoardIssues({
486
675
  boardId: id,
@@ -497,10 +686,9 @@ function issues(parent) {
497
686
  import { Option } from "commander";
498
687
  var BOARD_TYPES = ["scrum", "kanban", "simple"];
499
688
  function list(parent) {
500
- parent.command("list").description("List agile boards").option("--limit <number>", "Max results (1-1000)", intInRange(1, 1e3), 25).option("--project <key>", "Filter boards by project key or ID").addOption(new Option("--type <type>", "Board type filter").choices(BOARD_TYPES)).option("--name <name>", "Filter boards by name").addHelpText(
501
- "after",
502
- '\nExamples:\n jiradc board list\n jiradc board list --limit 10\n jiradc board list --project PROJ\n jiradc board list --type scrum --name "Team Board"'
503
- ).action(async (opts) => {
689
+ const cmd = parent.command("list").description("List agile boards").option("--limit <number>", "Max results (1-1000)", intInRange(1, 1e3), 25).option("--project <key>", "Filter boards by project key or ID").addOption(new Option("--type <type>", "Board type filter").choices(BOARD_TYPES)).option("--name <name>", "Filter boards by name");
690
+ examples(cmd, ["", "--limit 10", "--project PROJ", '--type scrum --name "Team Board"']);
691
+ cmd.action(async (opts) => {
504
692
  const client = getClient();
505
693
  const result = await client.agile.getBoards({
506
694
  maxResults: opts.limit,
@@ -513,16 +701,9 @@ function list(parent) {
513
701
  }
514
702
 
515
703
  // src/commands/board/index.ts
516
- function registerBoardCommands(program2) {
517
- const board = program2.command("board").description("Board operations").addHelpText(
518
- "after",
519
- `
520
- Examples:
521
- $ jiradc board list
522
- $ jiradc board list --type scrum --project PROJ
523
- $ jiradc board issues 42 --limit 20
524
- `
525
- );
704
+ function registerBoardCommands(program) {
705
+ const board = program.command("board").description("Board operations");
706
+ examples(board, ["list", "list --type scrum --project PROJ", "issues 42 --limit 20"]);
526
707
  list(board);
527
708
  issues(board);
528
709
  }
@@ -536,14 +717,13 @@ var ASSIGNEE_TYPES = [
536
717
  "UNASSIGNED"
537
718
  ];
538
719
  function create(parent) {
539
- parent.command("create").description("Create a new component in a project").requiredOption("--project <key>", "Project key (e.g., AI)").requiredOption("--name <name>", "Component name").option("--description <text>", "Component description").option("--lead <username>", "Username of the component lead").addOption(new Option2("--assignee-type <type>", "Assignee strategy").choices(ASSIGNEE_TYPES)).addHelpText(
540
- "after",
541
- `
542
- Examples:
543
- jiradc component create --project AI --name Backend
544
- jiradc component create --project AI --name Frontend --description "UI work" --lead jsmith
545
- jiradc component create --project AI --name Infra --assignee-type COMPONENT_LEAD --lead jsmith`
546
- ).action(
720
+ const cmd = parent.command("create").description("Create a new component in a project").requiredOption("--project <key>", "Project key (e.g., AI)").requiredOption("--name <name>", "Component name").option("--description <text>", "Component description").option("--lead <username>", "Username of the component lead").addOption(new Option2("--assignee-type <type>", "Assignee strategy").choices(ASSIGNEE_TYPES));
721
+ examples(cmd, [
722
+ "--project AI --name Backend",
723
+ '--project AI --name Frontend --description "UI work" --lead jsmith',
724
+ "--project AI --name Infra --assignee-type COMPONENT_LEAD --lead jsmith"
725
+ ]);
726
+ cmd.action(
547
727
  async (opts) => {
548
728
  const client = getClient();
549
729
  const result = await client.components.create({
@@ -560,13 +740,9 @@ Examples:
560
740
 
561
741
  // src/commands/component/delete.ts
562
742
  function deleteComponent(parent) {
563
- parent.command("delete <id>").description("Delete a component, optionally reassigning its issues to another component").option("--move-issues-to <id>", "Reassign existing issues to this component ID before deletion").addHelpText(
564
- "after",
565
- `
566
- Examples:
567
- jiradc component delete 11289
568
- jiradc component delete 11289 --move-issues-to 11290`
569
- ).action(async (id, opts) => {
743
+ const cmd = parent.command("delete <id>").description("Delete a component, optionally reassigning its issues to another component").option("--move-issues-to <id>", "Reassign existing issues to this component ID before deletion");
744
+ examples(cmd, ["11289", "11289 --move-issues-to 11290"]);
745
+ cmd.action(async (id, opts) => {
570
746
  const client = getClient();
571
747
  await client.components.delete({ id, moveIssuesTo: opts.moveIssuesTo });
572
748
  output({ deleted: true, componentId: id, ...opts.moveIssuesTo && { movedIssuesTo: opts.moveIssuesTo } });
@@ -575,7 +751,9 @@ Examples:
575
751
 
576
752
  // src/commands/component/get.ts
577
753
  function get(parent) {
578
- parent.command("get <id>").description("Get a component by ID").addHelpText("after", "\nExamples:\n jiradc component get 11289").action(async (id) => {
754
+ const cmd = parent.command("get <id>").description("Get a component by ID");
755
+ examples(cmd, ["11289"]);
756
+ cmd.action(async (id) => {
579
757
  const client = getClient();
580
758
  const result = await client.components.get({ id });
581
759
  output(transformComponent(result));
@@ -584,7 +762,9 @@ function get(parent) {
584
762
 
585
763
  // src/commands/component/issue-count.ts
586
764
  function issueCount(parent) {
587
- parent.command("issue-count <id>").description("Get the number of issues currently using this component").addHelpText("after", "\nExamples:\n jiradc component issue-count 11289").action(async (id) => {
765
+ const cmd = parent.command("issue-count <id>").description("Get the number of issues currently using this component");
766
+ examples(cmd, ["11289"]);
767
+ cmd.action(async (id) => {
588
768
  const client = getClient();
589
769
  const result = await client.components.getRelatedIssueCounts({ id });
590
770
  output(transformComponentIssueCounts(result));
@@ -593,7 +773,9 @@ function issueCount(parent) {
593
773
 
594
774
  // src/commands/component/list.ts
595
775
  function list2(parent) {
596
- parent.command("list").description("List all components for a project").requiredOption("--project <key>", "Project key (e.g., AI)").addHelpText("after", "\nExamples:\n jiradc component list --project AI").action(async (opts) => {
776
+ const cmd = parent.command("list").description("List all components for a project").requiredOption("--project <key>", "Project key (e.g., AI)");
777
+ examples(cmd, ["--project AI"]);
778
+ cmd.action(async (opts) => {
597
779
  const client = getClient();
598
780
  const result = await client.components.list({ projectKeyOrId: opts.project });
599
781
  output(result.map(transformComponent));
@@ -609,14 +791,13 @@ var ASSIGNEE_TYPES2 = [
609
791
  "UNASSIGNED"
610
792
  ];
611
793
  function update(parent) {
612
- parent.command("update <id>").description("Update an existing component (only provided fields are changed)").option("--name <name>", "New component name").option("--description <text>", "New component description").option("--lead <username>", "Username of the component lead (empty string clears it)").addOption(new Option3("--assignee-type <type>", "New assignee strategy").choices(ASSIGNEE_TYPES2)).addHelpText(
613
- "after",
614
- `
615
- Examples:
616
- jiradc component update 11289 --name Backend
617
- jiradc component update 11289 --description "Server-side code"
618
- jiradc component update 11289 --lead jsmith --assignee-type COMPONENT_LEAD`
619
- ).action(
794
+ const cmd = parent.command("update <id>").description("Update an existing component (only provided fields are changed)").option("--name <name>", "New component name").option("--description <text>", "New component description").option("--lead <username>", "Username of the component lead (empty string clears it)").addOption(new Option3("--assignee-type <type>", "New assignee strategy").choices(ASSIGNEE_TYPES2));
795
+ examples(cmd, [
796
+ "11289 --name Backend",
797
+ '11289 --description "Server-side code"',
798
+ "11289 --lead jsmith --assignee-type COMPONENT_LEAD"
799
+ ]);
800
+ cmd.action(
620
801
  async (id, opts) => {
621
802
  if (opts.name === void 0 && opts.description === void 0 && opts.lead === void 0 && opts.assigneeType === void 0) {
622
803
  throw new Error("Provide at least one of --name, --description, --lead, --assignee-type");
@@ -635,19 +816,16 @@ Examples:
635
816
  }
636
817
 
637
818
  // src/commands/component/index.ts
638
- function registerComponentCommands(program2) {
639
- const component = program2.command("component").description("Project component operations").addHelpText(
640
- "after",
641
- `
642
- Examples:
643
- $ jiradc component list --project AI
644
- $ jiradc component get 11289
645
- $ jiradc component create --project AI --name Backend
646
- $ jiradc component update 11289 --name Backend
647
- $ jiradc component delete 11289 --move-issues-to 11290
648
- $ jiradc component issue-count 11289
649
- `
650
- );
819
+ function registerComponentCommands(program) {
820
+ const component = program.command("component").description("Project component operations");
821
+ examples(component, [
822
+ "list --project AI",
823
+ "get 11289",
824
+ "create --project AI --name Backend",
825
+ "update 11289 --name Backend",
826
+ "delete 11289 --move-issues-to 11290",
827
+ "issue-count 11289"
828
+ ]);
651
829
  list2(component);
652
830
  get(component);
653
831
  create(component);
@@ -658,16 +836,15 @@ Examples:
658
836
 
659
837
  // src/commands/field/options.ts
660
838
  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(
662
- "after",
663
- '\nExamples:\n jiradc field options 10001\n jiradc field options 10001 --query "High"\n jiradc field options 10001 --limit 20 --page 2'
664
- ).action(async (id, opts) => {
839
+ const cmd = 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);
840
+ examples(cmd, ["10001", '10001 --query "High"', "10001 --limit 20 --start 2"]);
841
+ cmd.action(async (id, opts) => {
665
842
  const client = getClient();
666
843
  const result = await client.fields.getFieldOptions({
667
844
  fieldId: id,
668
845
  query: opts.query,
669
846
  maxResults: opts.limit,
670
- page: opts.page
847
+ page: opts.start
671
848
  });
672
849
  output(transformPaged({ ...result, startAt: result.startAt ?? 0 }, transformCustomFieldOption));
673
850
  });
@@ -675,10 +852,9 @@ function options(parent) {
675
852
 
676
853
  // src/commands/field/search.ts
677
854
  function search(parent) {
678
- parent.command("search <keyword>").description("Search for fields by name or ID").option("--limit <number>", "Maximum number of results (1-1000)", intInRange(1, 1e3), 25).addHelpText(
679
- "after",
680
- "\nExamples:\n jiradc field search epic\n jiradc field search customfield_10100\n jiradc field search priority --limit 5"
681
- ).action(async (keyword, opts) => {
855
+ const cmd = parent.command("search <keyword>").description("Search for fields by name or ID").option("--limit <number>", "Maximum number of results (1-1000)", intInRange(1, 1e3), 25);
856
+ examples(cmd, ["epic", "customfield_10100", "priority --limit 5"]);
857
+ cmd.action(async (keyword, opts) => {
682
858
  const client = getClient();
683
859
  const result = await client.fields.search(keyword, opts.limit);
684
860
  output(result.map(transformField));
@@ -686,16 +862,9 @@ function search(parent) {
686
862
  }
687
863
 
688
864
  // src/commands/field/index.ts
689
- function registerFieldCommands(program2) {
690
- const field = program2.command("field").description("Field operations").addHelpText(
691
- "after",
692
- `
693
- Examples:
694
- $ jiradc field search "epic"
695
- $ jiradc field search "priority"
696
- $ jiradc field options 10120
697
- `
698
- );
865
+ function registerFieldCommands(program) {
866
+ const field = program.command("field").description("Field operations");
867
+ examples(field, ['search "epic"', 'search "priority"', "options 10120"]);
699
868
  search(field);
700
869
  options(field);
701
870
  }
@@ -718,15 +887,10 @@ async function resolveUserToken(token) {
718
887
 
719
888
  // src/commands/issue/assign.ts
720
889
  function assign(parent) {
721
- parent.command("assign <key> <user>").description('Assign an issue. <user> is a username, "me", or "none" to unassign.').addHelpText(
722
- "after",
723
- `
724
- 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);
890
+ const cmd = 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');
891
+ examples(cmd, ["PROJ-123 --assignee jsmith", "PROJ-123 --assignee me", "PROJ-123 --assignee none"]);
892
+ cmd.action(async (key, opts) => {
893
+ const resolved = await resolveUserToken(opts.assignee);
730
894
  const client = getClient();
731
895
  await client.issues.update({
732
896
  issueKeyOrId: key,
@@ -738,20 +902,25 @@ Examples:
738
902
 
739
903
  // src/commands/issue/attachment/delete.ts
740
904
  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) => {
905
+ const cmd = parent.command("delete <key>").description("Delete an attachment by ID");
906
+ subEntityOption(cmd, "attachment", { mandatory: true });
907
+ examples(cmd, ["PROJ-123 --attachment-id 12345"]);
908
+ cmd.action(async (key, opts) => {
742
909
  const client = getClient();
743
- await client.issues.deleteAttachment({ attachmentId: opts.id });
744
- output({ deleted: true, attachmentId: opts.id });
910
+ await client.issues.deleteAttachment({ attachmentId: String(opts.attachmentId) });
911
+ output({ deleted: true, issueKey: key, attachmentId: opts.attachmentId });
745
912
  });
746
913
  }
747
914
 
748
915
  // src/commands/issue/attachment/download-all.ts
749
- import { mkdirSync } from "fs";
750
- import { join } from "path";
916
+ import { mkdirSync as mkdirSync2 } from "fs";
917
+ import { join as join3 } from "path";
751
918
  function downloadAll(parent) {
752
- 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) => {
919
+ const cmd = parent.command("download-all <key>").description("Download all attachments from an issue").requiredOption("--output <dir>", "Local directory to save attachments into");
920
+ examples(cmd, ["PROJ-123 --output ./downloads"]);
921
+ cmd.action(async (key, opts) => {
753
922
  const client = getClient();
754
- mkdirSync(opts.output, { recursive: true });
923
+ mkdirSync2(opts.output, { recursive: true });
755
924
  const issue = await client.issues.get({
756
925
  issueKeyOrId: key,
757
926
  fields: ["attachment"]
@@ -768,7 +937,7 @@ function downloadAll(parent) {
768
937
  failed.push({ filename: att.filename, error: "No content URL" });
769
938
  continue;
770
939
  }
771
- const destPath = join(opts.output, att.filename);
940
+ const destPath = join3(opts.output, att.filename);
772
941
  try {
773
942
  await client.issues.downloadAttachment({ url: att.content, destinationPath: destPath });
774
943
  results.push({ filename: att.filename, size: att.size, path: destPath });
@@ -788,11 +957,16 @@ function downloadAll(parent) {
788
957
 
789
958
  // src/commands/issue/attachment/download.ts
790
959
  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) => {
960
+ const cmd = parent.command("download <key>").description("Download a single attachment by ID");
961
+ subEntityOption(cmd, "attachment", { mandatory: true });
962
+ cmd.requiredOption("--output <path>", "Local file path to save the attachment");
963
+ examples(cmd, ["PROJ-123 --attachment-id 12345 --output ./report.pdf"]);
964
+ cmd.action(async (key, opts) => {
792
965
  const client = getClient();
793
- const attachment = await client.issues.getAttachment({ attachmentId: opts.id });
966
+ const attachmentId = String(opts.attachmentId);
967
+ const attachment = await client.issues.getAttachment({ attachmentId });
794
968
  if (!attachment.content) {
795
- throw new Error(`Attachment ${opts.id} has no content URL`);
969
+ throw new Error(`Attachment ${attachmentId} has no content URL`);
796
970
  }
797
971
  await client.issues.downloadAttachment({
798
972
  url: attachment.content,
@@ -810,7 +984,9 @@ function download(parent) {
810
984
 
811
985
  // src/commands/issue/attachment/list.ts
812
986
  function list3(parent) {
813
- parent.command("list <key>").description("List attachments on an issue").addHelpText("after", "\nExamples:\n jiradc issue attachment list PROJ-123").action(async (key) => {
987
+ const cmd = parent.command("list <key>").description("List attachments on an issue");
988
+ examples(cmd, ["PROJ-123"]);
989
+ cmd.action(async (key) => {
814
990
  const client = getClient();
815
991
  const issue = await client.issues.get({
816
992
  issueKeyOrId: key,
@@ -833,16 +1009,15 @@ function list3(parent) {
833
1009
 
834
1010
  // src/commands/issue/attachment/upload.ts
835
1011
  function upload(parent) {
836
- parent.command("upload <key>").description("Upload attachments to an issue").requiredOption("--files <paths>", "Comma-separated file paths to upload").addHelpText(
837
- "after",
838
- "\nExamples:\n jiradc issue attachment upload PROJ-123 --files ./report.pdf\n jiradc issue attachment upload PROJ-123 --files ./a.txt,./b.png"
839
- ).action(async (key, opts) => {
1012
+ const cmd = parent.command("upload <key>").description("Upload attachments to an issue").requiredOption("--files <paths>", "Comma-separated file paths to upload");
1013
+ examples(cmd, ["PROJ-123 --files ./report.pdf", "PROJ-123 --files ./a.txt,./b.png"]);
1014
+ cmd.action(async (key, opts) => {
840
1015
  const client = getClient();
841
1016
  const filePaths = opts.files.split(",").map((f) => f.trim());
842
1017
  const results = [];
843
1018
  for (const filePath of filePaths) {
844
- const attachments2 = await client.issues.addAttachment({ issueKeyOrId: key, filePath });
845
- results.push(...attachments2);
1019
+ const attachments = await client.issues.addAttachment({ issueKeyOrId: key, filePath });
1020
+ results.push(...attachments);
846
1021
  }
847
1022
  output({
848
1023
  issueKey: key,
@@ -860,17 +1035,14 @@ function upload(parent) {
860
1035
 
861
1036
  // src/commands/issue/attachment/index.ts
862
1037
  function registerAttachmentCommands(parent) {
863
- const attachment = parent.command("attachment").description("Attachment operations").addHelpText(
864
- "after",
865
- `
866
- Examples:
867
- $ jiradc issue attachment list PROJ-123
868
- $ jiradc issue attachment upload PROJ-123 --files ./report.pdf
869
- $ jiradc issue attachment download PROJ-123 --id 12345 --output ./report.pdf
870
- $ jiradc issue attachment download-all PROJ-123 --output ./downloads
871
- $ jiradc issue attachment delete --id 12345
872
- `
873
- );
1038
+ const attachment = parent.command("attachment").description("Attachment operations");
1039
+ examples(attachment, [
1040
+ "list PROJ-123",
1041
+ "upload PROJ-123 --files ./report.pdf",
1042
+ "download PROJ-123 --attachment-id 12345 --output ./report.pdf",
1043
+ "download-all PROJ-123 --output ./downloads",
1044
+ "delete PROJ-123 --attachment-id 12345"
1045
+ ]);
874
1046
  upload(attachment);
875
1047
  list3(attachment);
876
1048
  download(attachment);
@@ -878,53 +1050,11 @@ Examples:
878
1050
  deleteAttachment(attachment);
879
1051
  }
880
1052
 
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
1053
  // src/commands/issue/batch-changelog.ts
923
1054
  function batchChangelog(parent) {
924
- 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(
925
- "after",
926
- "\nExamples:\n jiradc issue batch-changelog PROJ-1,PROJ-2,PROJ-3\n jiradc issue batch-changelog PROJ-123,PROJ-124 --limit 10"
927
- ).action(async (keys, opts) => {
1055
+ const cmd = 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);
1056
+ examples(cmd, ["PROJ-1,PROJ-2,PROJ-3", "PROJ-123,PROJ-124 --limit 10"]);
1057
+ cmd.action(async (keys, opts) => {
928
1058
  const client = getClient();
929
1059
  const keyList = keys.split(",").map((k) => k.trim());
930
1060
  const entries = await Promise.all(
@@ -943,12 +1073,11 @@ function batchChangelog(parent) {
943
1073
 
944
1074
  // src/commands/issue/batch-create.ts
945
1075
  function batchCreate(parent) {
946
- parent.command("batch-create").description("Create multiple issues from a JSON array").requiredOption("--issues <json>", "JSON array of issue objects").addHelpText(
947
- "after",
948
- `
949
- Examples:
950
- jiradc issue batch-create --issues '[{"projectKeyOrId":"PROJ","issueTypeName":"Task","summary":"Task 1"},{"projectKeyOrId":"PROJ","issueTypeName":"Task","summary":"Task 2"}]'`
951
- ).action(async (opts) => {
1076
+ const cmd = parent.command("batch-create").description("Create multiple issues from a JSON array").requiredOption("--issues <json>", "JSON array of issue objects");
1077
+ examples(cmd, [
1078
+ `--issues '[{"projectKeyOrId":"PROJ","issueTypeName":"Task","summary":"Task 1"},{"projectKeyOrId":"PROJ","issueTypeName":"Task","summary":"Task 2"}]'`
1079
+ ]);
1080
+ cmd.action(async (opts) => {
952
1081
  const client = getClient();
953
1082
  const parsed = JSON.parse(opts.issues);
954
1083
  const results = [];
@@ -966,10 +1095,9 @@ Examples:
966
1095
 
967
1096
  // src/commands/issue/changelog.ts
968
1097
  function changelog(parent) {
969
- parent.command("changelog <key>").description("Get changelog for an issue").option("--limit <number>", "Max changelog entries (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).addHelpText(
970
- "after",
971
- "\nExamples:\n jiradc issue changelog PROJ-123\n jiradc issue changelog PROJ-123 --limit 10"
972
- ).action(async (key, opts) => {
1098
+ const cmd = parent.command("changelog <key>").description("Get changelog for an issue").option("--limit <number>", "Max changelog entries (1-50, Jira DC caps at 50)", intInRange(1, 50), 25);
1099
+ examples(cmd, ["PROJ-123", "PROJ-123 --limit 10"]);
1100
+ cmd.action(async (key, opts) => {
973
1101
  const client = getClient();
974
1102
  const result = await client.issues.getChangelog({ issueKeyOrId: key, maxResults: opts.limit });
975
1103
  output(result);
@@ -979,7 +1107,7 @@ function changelog(parent) {
979
1107
  // src/commands/issue/clone.ts
980
1108
  import { unlink } from "fs/promises";
981
1109
  import { tmpdir } from "os";
982
- import { join as join3 } from "path";
1110
+ import { join as join4 } from "path";
983
1111
  var CLONE_FIELDS = [
984
1112
  "summary",
985
1113
  "description",
@@ -995,14 +1123,13 @@ var CLONE_FIELDS = [
995
1123
  "issuelinks"
996
1124
  ];
997
1125
  function clone(parent) {
998
- parent.command("clone <key>").description("Clone an issue (create a duplicate with the same fields)").option("--summary <text>", 'Override the summary (default: "CLONE - <original>")').option("--project <key>", "Create in a different project").option("--assignee <username>", "Override assignee").option("--include-attachments", "Copy attachments to the cloned issue").option("--include-links", "Copy issue links to the cloned issue").addHelpText(
999
- "after",
1000
- `
1001
- Examples:
1002
- jiradc issue clone PROJ-123
1003
- jiradc issue clone PROJ-123 --summary "Cloned: new title"
1004
- jiradc issue clone PROJ-123 --include-attachments --include-links`
1005
- ).action(
1126
+ const cmd = parent.command("clone <key>").description("Clone an issue (create a duplicate with the same fields)").option("--summary <text>", 'Override the summary (default: "CLONE - <original>")').option("--project <key>", "Create in a different project").option("--assignee <username>", "Override assignee").option("--include-attachments", "Copy attachments to the cloned issue").option("--include-links", "Copy issue links to the cloned issue");
1127
+ examples(cmd, [
1128
+ "PROJ-123",
1129
+ 'PROJ-123 --summary "Cloned: new title"',
1130
+ "PROJ-123 --include-attachments --include-links"
1131
+ ]);
1132
+ cmd.action(
1006
1133
  async (key, opts) => {
1007
1134
  const client = getClient();
1008
1135
  const source = await client.issues.get({ issueKeyOrId: key, fields: CLONE_FIELDS });
@@ -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,35 +1198,69 @@ 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");
1204
+ bodyOption(cmd, { mandatory: true });
1205
+ examples(cmd, ['PROJ-123 --body "Fixed in latest build"']);
1206
+ cmd.action(async (key, opts) => {
1077
1207
  const client = getClient();
1078
- const result = await client.issues.editComment({ issueKeyOrId: key, commentId: opts.id, body: opts.body });
1208
+ const result = await client.issues.addComment({ issueKeyOrId: key, body: opts.body });
1079
1209
  output(transformComment(result));
1080
1210
  });
1081
1211
  }
1082
1212
 
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) => {
1213
+ // src/commands/issue/comment/delete.ts
1214
+ function deleteComment(parent) {
1215
+ const cmd = parent.command("delete <key>").description("Delete a comment from an issue");
1216
+ subEntityOption(cmd, "comment", { mandatory: true });
1217
+ examples(cmd, ["PROJ-123 --comment-id 12345"]);
1218
+ cmd.action(async (key, opts) => {
1086
1219
  const client = getClient();
1087
- const result = await client.issues.addComment({ issueKeyOrId: key, body: opts.body });
1220
+ await client.issues.deleteComment({ issueKeyOrId: key, commentId: String(opts.commentId) });
1221
+ output({ deleted: true, issueKey: key, commentId: opts.commentId });
1222
+ });
1223
+ }
1224
+
1225
+ // src/commands/issue/comment/update.ts
1226
+ function update2(parent) {
1227
+ const cmd = parent.command("update <key>").description("Update an existing comment");
1228
+ subEntityOption(cmd, "comment", { mandatory: true });
1229
+ bodyOption(cmd, { mandatory: true });
1230
+ examples(cmd, ['PROJ-123 --comment-id 12345 --body "Updated comment text"']);
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");
1245
+ examples(comment, [
1246
+ 'create PROJ-123 --body "Fixed in latest build"',
1247
+ 'update PROJ-123 --comment-id 12345 --body "Updated comment text"',
1248
+ "delete PROJ-123 --comment-id 12345"
1249
+ ]);
1250
+ create2(comment);
1251
+ update2(comment);
1252
+ deleteComment(comment);
1253
+ }
1254
+
1092
1255
  // src/commands/issue/create.ts
1093
- function create2(parent) {
1094
- 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
- "after",
1096
- `
1097
- Examples:
1098
- jiradc issue create --project PROJ --type Task --summary "Fix login bug"
1099
- jiradc issue create --project PROJ --type Story --summary "New feature" --assignee jsmith --priority High
1100
- jiradc issue create --project PROJ --type Task --summary "Epic child" --custom-fields '{"customfield_10100": "PROJ-123"}'
1101
- jiradc issue create --project PROJ --type Bug --summary "Urgent" --labels urgent,production --components Backend`
1102
- ).action(
1256
+ function create3(parent) {
1257
+ const cmd = 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"}')`);
1258
+ examples(cmd, [
1259
+ '--project PROJ --type Task --summary "Fix login bug"',
1260
+ '--project PROJ --type Story --summary "New feature" --assignee jsmith --priority High',
1261
+ '--project PROJ --type Bug --summary "Urgent" --labels urgent,production --components Backend'
1262
+ ]);
1263
+ cmd.action(
1103
1264
  async (opts) => {
1104
1265
  const client = getClient();
1105
1266
  const result = await client.issues.create({
@@ -1124,10 +1285,9 @@ Examples:
1124
1285
 
1125
1286
  // src/commands/issue/delete.ts
1126
1287
  function deleteIssue(parent) {
1127
- parent.command("delete <key>").description("Delete an issue").option("--delete-subtasks", "Also delete subtasks (default: false)").addHelpText(
1128
- "after",
1129
- "\nExamples:\n jiradc issue delete PROJ-123\n jiradc issue delete PROJ-123 --delete-subtasks"
1130
- ).action(async (key, opts) => {
1288
+ const cmd = parent.command("delete <key>").description("Delete an issue").option("--delete-subtasks", "Also delete subtasks (default: false)");
1289
+ examples(cmd, ["PROJ-123", "PROJ-123 --delete-subtasks"]);
1290
+ cmd.action(async (key, opts) => {
1131
1291
  const client = getClient();
1132
1292
  await client.issues.delete({ issueKeyOrId: key, deleteSubtasks: opts.deleteSubtasks });
1133
1293
  output({ deleted: true, issueKey: key });
@@ -1136,10 +1296,9 @@ function deleteIssue(parent) {
1136
1296
 
1137
1297
  // src/commands/issue/dev-status.ts
1138
1298
  function devStatus(parent) {
1139
- parent.command("dev-status <key>").description("Get development status (PRs, commits, branches, builds) for an issue").option("--detail", "Include PR URLs, commit IDs, and other details").addHelpText(
1140
- "after",
1141
- "\nExamples:\n jiradc issue dev-status PROJ-123\n jiradc issue dev-status PROJ-123 --detail"
1142
- ).action(async (key, opts) => {
1299
+ const cmd = parent.command("dev-status <key>").description("Get development status (PRs, commits, branches, builds) for an issue").option("--detail", "Include PR URLs, commit IDs, and other details");
1300
+ examples(cmd, ["PROJ-123", "PROJ-123 --detail"]);
1301
+ cmd.action(async (key, opts) => {
1143
1302
  const client = getClient();
1144
1303
  const issue = await client.issues.get({ issueKeyOrId: key, fields: ["summary"] });
1145
1304
  const issueId = issue.id;
@@ -1213,22 +1372,6 @@ function devStatus(parent) {
1213
1372
  });
1214
1373
  }
1215
1374
 
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
1375
  // src/utils/constants.ts
1233
1376
  var DEFAULT_FIELDS = [
1234
1377
  "summary",
@@ -1247,10 +1390,14 @@ var DEFAULT_FIELDS = [
1247
1390
 
1248
1391
  // src/commands/issue/get.ts
1249
1392
  function get2(parent) {
1250
- parent.command("get <key>").description("Get issue details").option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults").option("--expand <expand>", 'Expand options (e.g., "transitions", "changelog")').addHelpText(
1251
- "after",
1252
- "\nExamples:\n jiradc issue get PROJ-123\n jiradc issue get PROJ-123 --fields summary,status,assignee\n jiradc issue get PROJ-123 --all-fields\n jiradc issue get PROJ-123 --expand changelog,transitions"
1253
- ).action(async (key, opts) => {
1393
+ const cmd = parent.command("get <key>").description("Get issue details").option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults").option("--expand <expand>", 'Expand options (e.g., "transitions", "changelog")');
1394
+ examples(cmd, [
1395
+ "PROJ-123",
1396
+ "PROJ-123 --fields summary,status,assignee",
1397
+ "PROJ-123 --all-fields",
1398
+ "PROJ-123 --expand changelog,transitions"
1399
+ ]);
1400
+ cmd.action(async (key, opts) => {
1254
1401
  const client = getClient();
1255
1402
  const fields = opts.allFields ? void 0 : opts.fields?.split(",").map((f) => f.trim()) ?? DEFAULT_FIELDS;
1256
1403
  const result = await client.issues.get({
@@ -1264,13 +1411,9 @@ function get2(parent) {
1264
1411
 
1265
1412
  // src/commands/issue/link-epic.ts
1266
1413
  function linkEpic(parent) {
1267
- parent.command("link-epic <keys...>").description("Link one or more issues to an epic").requiredOption("--epic <epicKey>", "Epic issue key").addHelpText(
1268
- "after",
1269
- `
1270
- Examples:
1271
- jiradc issue link-epic PROJ-456 --epic PROJ-123
1272
- jiradc issue link-epic PROJ-456 PROJ-457 PROJ-458 --epic PROJ-123`
1273
- ).action(async (keys, opts) => {
1414
+ const cmd = parent.command("link-epic <keys...>").description("Link one or more issues to an epic").requiredOption("--epic <epicKey>", "Epic issue key");
1415
+ examples(cmd, ["PROJ-456 --epic PROJ-123", "PROJ-456 PROJ-457 PROJ-458 --epic PROJ-123"]);
1416
+ cmd.action(async (keys, opts) => {
1274
1417
  const client = getClient();
1275
1418
  const results = await Promise.allSettled(
1276
1419
  keys.map(
@@ -1300,7 +1443,9 @@ Examples:
1300
1443
 
1301
1444
  // src/commands/issue/link-types.ts
1302
1445
  function linkTypes(parent) {
1303
- parent.command("link-types").description("List all issue link types").addHelpText("after", "\nExamples:\n jiradc issue link-types").action(async () => {
1446
+ const cmd = parent.command("link-types").description("List all issue link types");
1447
+ examples(cmd, [""]);
1448
+ cmd.action(async () => {
1304
1449
  const client = getClient();
1305
1450
  const result = await client.links.getTypes();
1306
1451
  output(result.map(transformIssueLinkType));
@@ -1309,10 +1454,12 @@ function linkTypes(parent) {
1309
1454
 
1310
1455
  // src/commands/issue/link.ts
1311
1456
  function link(parent) {
1312
- parent.command("link").description("Link two issues together").requiredOption("--type <name>", "Link type name (e.g., 'Blocks', 'Duplicate', 'Relates')").requiredOption("--from <key>", 'Source issue key (e.g., the issue that "blocks")').requiredOption("--to <key>", 'Target issue key (e.g., the issue that "is blocked by")').option("--comment <text>", "Optional comment").addHelpText(
1313
- "after",
1314
- "\nExamples:\n jiradc issue link --type Relates --from AI-154 --to AI-149\n jiradc issue link --type Blocks --from PROJ-456 --to PROJ-123 # PROJ-456 blocks PROJ-123"
1315
- ).action(async (opts) => {
1457
+ const cmd = parent.command("link").description("Link two issues together").requiredOption("--type <name>", "Link type name (e.g., 'Blocks', 'Duplicate', 'Relates')").requiredOption("--from <key>", 'Source issue key (e.g., the issue that "blocks")').requiredOption("--to <key>", 'Target issue key (e.g., the issue that "is blocked by")').option("--comment <text>", "Optional comment");
1458
+ examples(cmd, [
1459
+ "--type Relates --from AI-154 --to AI-149",
1460
+ ["--type Blocks --from PROJ-456 --to PROJ-123", "PROJ-456 blocks PROJ-123"]
1461
+ ]);
1462
+ cmd.action(async (opts) => {
1316
1463
  const client = getClient();
1317
1464
  await client.links.create({
1318
1465
  typeName: opts.type,
@@ -1331,10 +1478,13 @@ function link(parent) {
1331
1478
 
1332
1479
  // src/commands/issue/search.ts
1333
1480
  function search2(parent) {
1334
- parent.command("search <jql>").description("Search issues using JQL").option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults").addHelpText(
1335
- "after",
1336
- '\nExamples:\n jiradc issue search "project = PROJ AND status = Open"\n jiradc issue search "assignee = currentUser()" --limit 10 --fields summary,status\n jiradc issue search "project = PROJ" --start 50 --limit 50'
1337
- ).action(async (jql, opts) => {
1481
+ const cmd = parent.command("search <jql>").description("Search issues using JQL").option("--limit <number>", "Max results (1-50, Jira DC caps at 50)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination", nonNegativeInt).option("--fields <fields>", "Comma-separated fields to return (defaults to essential fields)").option("--all-fields", "Return all fields instead of defaults");
1482
+ examples(cmd, [
1483
+ '"project = PROJ AND status = Open"',
1484
+ '"assignee = currentUser()" --limit 10 --fields summary,status',
1485
+ '"project = PROJ" --start 50 --limit 50'
1486
+ ]);
1487
+ cmd.action(async (jql, opts) => {
1338
1488
  const client = getClient();
1339
1489
  const fields = opts.allFields ? void 0 : opts.fields?.split(",").map((f) => f.trim()) ?? DEFAULT_FIELDS;
1340
1490
  const result = await client.issues.search({
@@ -1349,14 +1499,13 @@ function search2(parent) {
1349
1499
 
1350
1500
  // src/commands/issue/transition.ts
1351
1501
  function transition(parent) {
1352
- parent.command("transition <key>").description("Transition issue to a new status").requiredOption("--to <idOrName>", "Transition ID, or status name (case-insensitive)").option("--comment <text>", "Comment to add during transition").addHelpText(
1353
- "after",
1354
- `
1355
- Examples:
1356
- jiradc issue transition PROJ-123 --to 31
1357
- jiradc issue transition PROJ-123 --to "In Review"
1358
- jiradc issue transition PROJ-123 --to Done --comment "Verified in staging"`
1359
- ).action(async (key, opts) => {
1502
+ const cmd = parent.command("transition <key>").description("Transition issue to a new status").requiredOption("--to <idOrName>", "Transition ID, or status name (case-insensitive)").option("--comment <text>", "Comment to add during transition");
1503
+ examples(cmd, [
1504
+ "PROJ-123 --to 31",
1505
+ 'PROJ-123 --to "In Review"',
1506
+ 'PROJ-123 --to Done --comment "Verified in staging"'
1507
+ ]);
1508
+ cmd.action(async (key, opts) => {
1360
1509
  const client = getClient();
1361
1510
  let transitionId;
1362
1511
  if (/^\d+$/.test(opts.to)) {
@@ -1386,7 +1535,9 @@ Examples:
1386
1535
 
1387
1536
  // src/commands/issue/transitions.ts
1388
1537
  function transitions(parent) {
1389
- parent.command("transitions <key>").description("Get available transitions for an issue").addHelpText("after", "\nExamples:\n jiradc issue transitions PROJ-123").action(async (key) => {
1538
+ const cmd = parent.command("transitions <key>").description("Get available transitions for an issue");
1539
+ examples(cmd, ["PROJ-123"]);
1540
+ cmd.action(async (key) => {
1390
1541
  const client = getClient();
1391
1542
  const result = await client.issues.getTransitions({
1392
1543
  issueKeyOrId: key,
@@ -1398,7 +1549,9 @@ function transitions(parent) {
1398
1549
 
1399
1550
  // src/commands/issue/unlink.ts
1400
1551
  function unlink2(parent) {
1401
- parent.command("unlink <id>").description("Remove a link between two issues").addHelpText("after", "\nExamples:\n jiradc issue unlink 12345").action(async (id) => {
1552
+ const cmd = parent.command("unlink <id>").description("Remove a link between two issues");
1553
+ examples(cmd, ["12345"]);
1554
+ cmd.action(async (id) => {
1402
1555
  const client = getClient();
1403
1556
  await client.links.remove({ linkId: id });
1404
1557
  output({ removed: true, linkId: id });
@@ -1440,19 +1593,17 @@ function buildSetValue(parsed, wrap) {
1440
1593
  if (parsed.mode !== "set") return void 0;
1441
1594
  return parsed.values.map(wrap);
1442
1595
  }
1443
- function update2(parent) {
1444
- 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
- "after",
1446
- `
1447
- Examples:
1448
- jiradc issue update PROJ-123 --summary "New title"
1449
- jiradc issue update PROJ-123 --priority High --assignee me
1450
- jiradc issue update PROJ-123 --labels backend,urgent
1451
- jiradc issue update PROJ-123 --labels +urgent,-backend
1452
- jiradc issue update PROJ-123 --components +Frontend
1453
- jiradc issue update PROJ-123 --fields '{"customfield_10100": "EPIC-1"}' --priority High
1454
- jiradc issue update PROJ-123 --attachments /path/to/file.pdf,/path/to/image.png`
1455
- ).action(async (key, opts) => {
1596
+ function update3(parent) {
1597
+ const cmd = 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")');
1598
+ examples(cmd, [
1599
+ 'PROJ-123 --summary "New title"',
1600
+ "PROJ-123 --priority High --assignee me",
1601
+ "PROJ-123 --labels backend,urgent",
1602
+ "PROJ-123 --labels +urgent,-backend",
1603
+ "PROJ-123 --components +Frontend",
1604
+ "PROJ-123 --attachments /path/to/file.pdf,/path/to/image.png"
1605
+ ]);
1606
+ cmd.action(async (key, opts) => {
1456
1607
  const hasShortcut = opts.summary !== void 0 || opts.description !== void 0 || opts.priority !== void 0 || opts.assignee !== void 0 || opts.labels !== void 0 || opts.components !== void 0 || opts.fixVersions !== void 0;
1457
1608
  if (!opts.fields && !opts.attachments && !hasShortcut) {
1458
1609
  throw new Error(
@@ -1521,12 +1672,15 @@ Examples:
1521
1672
  });
1522
1673
  }
1523
1674
 
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(
1527
- "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"'
1529
- ).action(async (key, opts) => {
1675
+ // src/commands/issue/worklog/create.ts
1676
+ function create4(parent) {
1677
+ const cmd = 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");
1678
+ examples(cmd, [
1679
+ "PROJ-123 --time 2h",
1680
+ 'PROJ-123 --time "1d 4h" --comment "Backend implementation"',
1681
+ 'PROJ-123 --time 3h --started "2026-03-19T09:00:00.000+0000"'
1682
+ ]);
1683
+ cmd.action(async (key, opts) => {
1530
1684
  const client = getClient();
1531
1685
  const result = await client.issues.addWorklog({
1532
1686
  issueKeyOrId: key,
@@ -1538,32 +1692,116 @@ function worklog(parent) {
1538
1692
  });
1539
1693
  }
1540
1694
 
1541
- // src/commands/issue/index.ts
1542
- function registerIssueCommands(program2) {
1543
- const issue = program2.command("issue").description("Issue operations").addHelpText(
1544
- "after",
1545
- `
1546
- Examples:
1547
- $ jiradc issue get PROJ-123
1548
- $ jiradc issue search "project = PROJ AND status = Open" --limit 10
1549
- $ jiradc issue create --project PROJ --type Task --summary "Fix the bug" --priority High
1550
- $ jiradc issue update PROJ-123 --fields '{"summary": "New title"}'
1551
- $ jiradc issue transition PROJ-123 --to 11
1552
- $ jiradc issue attachment list PROJ-123
1553
- `
1695
+ // src/commands/issue/worklog/delete.ts
1696
+ import { Option as Option4 } from "commander";
1697
+ var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
1698
+ function deleteWorklog(parent) {
1699
+ const cmd = parent.command("delete <key>").description("Delete a worklog entry");
1700
+ subEntityOption(cmd, "worklog", { mandatory: true });
1701
+ 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(
1702
+ "--increase-by <amount>",
1703
+ 'Amount to increase the estimate by; required when --adjust-estimate is "manual"'
1554
1704
  );
1705
+ examples(cmd, [
1706
+ "PROJ-123 --worklog-id 12345",
1707
+ "PROJ-123 --worklog-id 12345 --adjust-estimate leave",
1708
+ "PROJ-123 --worklog-id 12345 --adjust-estimate new --new-estimate 2h"
1709
+ ]);
1710
+ cmd.action(
1711
+ async (key, opts) => {
1712
+ const client = getClient();
1713
+ await client.issues.deleteWorklog({
1714
+ issueKeyOrId: key,
1715
+ worklogId: String(opts.worklogId),
1716
+ adjustEstimate: opts.adjustEstimate,
1717
+ newEstimate: opts.newEstimate,
1718
+ increaseBy: opts.increaseBy
1719
+ });
1720
+ output({ deleted: true, issueKey: key, worklogId: opts.worklogId });
1721
+ }
1722
+ );
1723
+ }
1724
+
1725
+ // src/commands/issue/worklog/list.ts
1726
+ function list4(parent) {
1727
+ const cmd = 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);
1728
+ examples(cmd, ["PROJ-123", "PROJ-123 --limit 10", "PROJ-123 --start 10 --limit 5"]);
1729
+ cmd.action(async (key, opts) => {
1730
+ const client = getClient();
1731
+ const result = await client.issues.getWorklogs({
1732
+ issueKeyOrId: key,
1733
+ startAt: opts.start,
1734
+ maxResults: opts.limit
1735
+ });
1736
+ output(transformPaged(result, transformWorklog));
1737
+ });
1738
+ }
1739
+
1740
+ // src/commands/issue/worklog/update.ts
1741
+ import { Option as Option5 } from "commander";
1742
+ var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
1743
+ function update4(parent) {
1744
+ const cmd = parent.command("update <key>").description("Update an existing worklog entry");
1745
+ subEntityOption(cmd, "worklog", { mandatory: true });
1746
+ 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"');
1747
+ examples(cmd, [
1748
+ 'PROJ-123 --worklog-id 12345 --time "1h 30m"',
1749
+ 'PROJ-123 --worklog-id 12345 --comment "Revised note"',
1750
+ "PROJ-123 --worklog-id 12345 --time 2h --adjust-estimate new --new-estimate 4h"
1751
+ ]);
1752
+ cmd.action(
1753
+ async (key, opts) => {
1754
+ const client = getClient();
1755
+ const result = await client.issues.updateWorklog({
1756
+ issueKeyOrId: key,
1757
+ worklogId: String(opts.worklogId),
1758
+ timeSpent: opts.time,
1759
+ comment: opts.comment,
1760
+ started: opts.started,
1761
+ adjustEstimate: opts.adjustEstimate,
1762
+ newEstimate: opts.newEstimate
1763
+ });
1764
+ output(transformWorklog(result));
1765
+ }
1766
+ );
1767
+ }
1768
+
1769
+ // src/commands/issue/worklog/index.ts
1770
+ function registerWorklogCommands(parent) {
1771
+ const worklog = parent.command("worklog").description("Worklog operations");
1772
+ examples(worklog, [
1773
+ 'create PROJ-123 --time 2h --comment "Backend work"',
1774
+ "list PROJ-123 --limit 10",
1775
+ 'update PROJ-123 --worklog-id 12345 --time "1h 30m"',
1776
+ "delete PROJ-123 --worklog-id 12345"
1777
+ ]);
1778
+ create4(worklog);
1779
+ list4(worklog);
1780
+ update4(worklog);
1781
+ deleteWorklog(worklog);
1782
+ }
1783
+
1784
+ // src/commands/issue/index.ts
1785
+ function registerIssueCommands(program) {
1786
+ const issue = program.command("issue").description("Issue operations");
1787
+ examples(issue, [
1788
+ "get PROJ-123",
1789
+ 'search "project = PROJ AND status = Open" --limit 10',
1790
+ 'create --project PROJ --type Task --summary "Fix the bug" --priority High',
1791
+ `update PROJ-123 --fields '{"summary": "New title"}'`,
1792
+ "transition PROJ-123 --to 11",
1793
+ "attachment list PROJ-123"
1794
+ ]);
1555
1795
  get2(issue);
1556
1796
  search2(issue);
1557
- create2(issue);
1558
- update2(issue);
1797
+ create3(issue);
1798
+ update3(issue);
1559
1799
  deleteIssue(issue);
1560
1800
  transition(issue);
1561
1801
  transitions(issue);
1562
1802
  assign(issue);
1563
- comment(issue);
1564
- commentEdit(issue);
1565
- worklog(issue);
1566
- getWorklog(issue);
1803
+ registerCommentCommands(issue);
1804
+ registerWorklogCommands(issue);
1567
1805
  changelog(issue);
1568
1806
  batchChangelog(issue);
1569
1807
  link(issue);
@@ -1571,18 +1809,16 @@ Examples:
1571
1809
  linkTypes(issue);
1572
1810
  linkEpic(issue);
1573
1811
  registerAttachmentCommands(issue);
1574
- attachments(issue);
1575
1812
  batchCreate(issue);
1576
1813
  clone(issue);
1577
1814
  devStatus(issue);
1578
1815
  }
1579
1816
 
1580
1817
  // src/commands/project/list.ts
1581
- function list4(parent) {
1582
- 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
- "after",
1584
- "\nExamples:\n jiradc project list\n jiradc project list --expand description,lead\n jiradc project list --include-archived"
1585
- ).action(async (opts) => {
1818
+ function list5(parent) {
1819
+ const cmd = parent.command("list").description("List all projects").option("--expand <expand>", 'Expand options (e.g., "description,lead")').option("--include-archived", "Include archived projects (default: false)");
1820
+ examples(cmd, ["", "--expand description,lead", "--include-archived"]);
1821
+ cmd.action(async (opts) => {
1586
1822
  const client = getClient();
1587
1823
  const result = await client.projects.getAll({ expand: opts.expand, includeArchived: opts.includeArchived });
1588
1824
  output(result.map(transformProject));
@@ -1591,10 +1827,9 @@ function list4(parent) {
1591
1827
 
1592
1828
  // src/commands/project/versions.ts
1593
1829
  function versions(parent) {
1594
- parent.command("versions <key>").description("Get all versions for a project").option("--expand <expand>", "Expand options").addHelpText(
1595
- "after",
1596
- "\nExamples:\n jiradc project versions PROJ\n jiradc project versions PROJ --expand operations"
1597
- ).action(async (key, opts) => {
1830
+ const cmd = parent.command("versions <key>").description("Get all versions for a project").option("--expand <expand>", "Expand options");
1831
+ examples(cmd, ["PROJ", "PROJ --expand operations"]);
1832
+ cmd.action(async (key, opts) => {
1598
1833
  const client = getClient();
1599
1834
  const result = await client.projects.getVersions({ projectKeyOrId: key, expand: opts.expand });
1600
1835
  output(result.map(transformVersion));
@@ -1602,25 +1837,21 @@ function versions(parent) {
1602
1837
  }
1603
1838
 
1604
1839
  // src/commands/project/index.ts
1605
- function registerProjectCommands(program2) {
1606
- const project = program2.command("project").description("Project operations").addHelpText(
1607
- "after",
1608
- `
1609
- Examples:
1610
- $ jiradc project list
1611
- $ jiradc project versions PROJ
1612
- `
1613
- );
1614
- list4(project);
1840
+ function registerProjectCommands(program) {
1841
+ const project = program.command("project").description("Project operations");
1842
+ examples(project, ["list", "versions PROJ"]);
1843
+ list5(project);
1615
1844
  versions(project);
1616
1845
  }
1617
1846
 
1618
1847
  // src/commands/sprint/create.ts
1619
- function create3(parent) {
1620
- 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
- "after",
1622
- '\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"'
1623
- ).action(async (opts) => {
1848
+ function create5(parent) {
1849
+ const cmd = 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");
1850
+ examples(cmd, [
1851
+ '--board 42 --name "Sprint 10"',
1852
+ '--board 42 --name "Sprint 10" --start-date 2026-03-20 --end-date 2026-04-03 --goal "Complete auth module"'
1853
+ ]);
1854
+ cmd.action(async (opts) => {
1624
1855
  const client = getClient();
1625
1856
  const result = await client.agile.createSprint({
1626
1857
  name: opts.name,
@@ -1633,13 +1864,29 @@ 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
+ const cmd = parent.command("delete").description("Delete a sprint (returns its issues to the backlog)").addArgument(new Argument2("<id>", "Sprint ID").argParser(positiveInt));
1871
+ examples(cmd, ["100"]);
1872
+ cmd.action(async (id) => {
1873
+ const client = getClient();
1874
+ await client.agile.deleteSprint({ sprintId: id });
1875
+ output({ deleted: true, sprintId: id });
1876
+ });
1877
+ }
1878
+
1879
+ // src/commands/sprint/issues.ts
1880
+ import { Argument as Argument3 } from "commander";
1638
1881
  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(
1640
- "after",
1641
- '\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
- ).action(async (id, opts) => {
1882
+ const cmd = 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");
1883
+ examples(cmd, [
1884
+ "100",
1885
+ "100 --limit 20",
1886
+ '100 --jql "status = Done" --fields summary,status',
1887
+ "100 --start 50 --limit 25"
1888
+ ]);
1889
+ cmd.action(async (id, opts) => {
1643
1890
  const client = getClient();
1644
1891
  const result = await client.agile.getSprintIssues({
1645
1892
  sprintId: id,
@@ -1653,13 +1900,12 @@ function issues2(parent) {
1653
1900
  }
1654
1901
 
1655
1902
  // src/commands/sprint/list.ts
1656
- import { Option as Option4 } from "commander";
1903
+ import { Option as Option6 } from "commander";
1657
1904
  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(
1660
- "after",
1661
- "\nExamples:\n jiradc sprint list --board 42\n jiradc sprint list --board 42 --state active"
1662
- ).action(async (opts) => {
1905
+ function list6(parent) {
1906
+ const cmd = 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));
1907
+ examples(cmd, ["--board 42", "--board 42 --state active"]);
1908
+ cmd.action(async (opts) => {
1663
1909
  const client = getClient();
1664
1910
  const result = await client.agile.getSprints({
1665
1911
  boardId: opts.board,
@@ -1670,13 +1916,16 @@ function list5(parent) {
1670
1916
  }
1671
1917
 
1672
1918
  // src/commands/sprint/update.ts
1673
- import { Argument as Argument3, Option as Option5 } from "commander";
1919
+ import { Argument as Argument4, Option as Option7 } from "commander";
1674
1920
  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(
1677
- "after",
1678
- '\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
- ).action(
1921
+ function update5(parent) {
1922
+ const cmd = 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");
1923
+ examples(cmd, [
1924
+ '100 --name "Sprint 10 - Extended"',
1925
+ "100 --state active",
1926
+ '100 --end-date 2026-04-10 --goal "Updated goal"'
1927
+ ]);
1928
+ cmd.action(
1680
1929
  async (id, opts) => {
1681
1930
  const client = getClient();
1682
1931
  const result = await client.agile.updateSprint({
@@ -1693,21 +1942,19 @@ function update3(parent) {
1693
1942
  }
1694
1943
 
1695
1944
  // src/commands/sprint/index.ts
1696
- function registerSprintCommands(program2) {
1697
- const sprint = program2.command("sprint").description("Sprint operations").addHelpText(
1698
- "after",
1699
- `
1700
- Examples:
1701
- $ jiradc sprint list --board 42
1702
- $ jiradc sprint list --board 42 --state active
1703
- $ jiradc sprint issues 123 --limit 20
1704
- $ jiradc sprint create --board 42 --name "Sprint 10" --start-date 2026-04-01 --end-date 2026-04-14
1705
- `
1706
- );
1707
- list5(sprint);
1945
+ function registerSprintCommands(program) {
1946
+ const sprint = program.command("sprint").description("Sprint operations");
1947
+ examples(sprint, [
1948
+ "list --board 42",
1949
+ "list --board 42 --state active",
1950
+ "issues 123 --limit 20",
1951
+ 'create --board 42 --name "Sprint 10" --start-date 2026-04-01 --end-date 2026-04-14'
1952
+ ]);
1953
+ list6(sprint);
1708
1954
  issues2(sprint);
1709
- create3(sprint);
1710
- update3(sprint);
1955
+ create5(sprint);
1956
+ update5(sprint);
1957
+ deleteSprint(sprint);
1711
1958
  }
1712
1959
 
1713
1960
  // src/commands/token/client.ts
@@ -1722,36 +1969,24 @@ function getTokenClient(options2 = {}) {
1722
1969
  ...!username ? ["JIRA_BASIC_USERNAME (or --basic-username)"] : [],
1723
1970
  ...!password ? ["JIRA_BASIC_PASSWORD (or --basic-password)"] : []
1724
1971
  ];
1725
- process.stderr.write(
1726
- `${JSON.stringify({
1727
- error: `Missing required credentials: ${missing.join(", ")}`,
1728
- hint: "Set JIRA_BASIC_USERNAME and JIRA_BASIC_PASSWORD in your shell profile, or pass --basic-username / --basic-password."
1729
- })}
1730
- `
1731
- );
1732
- process.exit(1);
1972
+ throw new CliAuthError(`Missing required credentials: ${missing.join(", ")}`);
1733
1973
  }
1734
1974
  const client = new JiraClient2({ baseUrl });
1735
1975
  return { client, username, password };
1736
1976
  }
1737
1977
 
1738
1978
  // src/commands/token/create.ts
1739
- function create4(parent) {
1740
- parent.command("create").description("Create a Personal Access Token. The secret is returned exactly once.").requiredOption("--name <name>", "Token name").option(
1979
+ function create6(parent) {
1980
+ const cmd = parent.command("create").description("Create a Personal Access Token. The secret is returned exactly once.").requiredOption("--name <name>", "Token name").option(
1741
1981
  "--expiration-duration <days>",
1742
1982
  "Token lifetime in days. Omit for a non-expiring token (admin policy permitting).",
1743
1983
  positiveInt
1744
1984
  ).option("--basic-username <u>", "Override $JIRA_BASIC_USERNAME").option(
1745
1985
  "--basic-password <p>",
1746
1986
  "Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)"
1747
- ).addHelpText(
1748
- "after",
1749
- `
1750
- Examples:
1751
- $ jiradc token create --name my-pat # non-expiring
1752
- $ jiradc token create --name svc-token --expiration-duration 365
1753
- `
1754
- ).action(async (opts) => {
1987
+ );
1988
+ examples(cmd, [["--name my-pat", "non-expiring"], "--name svc-token --expiration-duration 365"]);
1989
+ cmd.action(async (opts) => {
1755
1990
  const { client, username, password } = getTokenClient({
1756
1991
  basicUsername: opts.basicUsername,
1757
1992
  basicPassword: opts.basicPassword
@@ -1767,11 +2002,13 @@ Examples:
1767
2002
  }
1768
2003
 
1769
2004
  // src/commands/token/list.ts
1770
- function list6(parent) {
1771
- 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(
2005
+ function list7(parent) {
2006
+ const cmd = 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
2007
  "--basic-password <p>",
1773
2008
  "Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)"
1774
- ).action(async (opts) => {
2009
+ );
2010
+ examples(cmd, [""]);
2011
+ cmd.action(async (opts) => {
1775
2012
  const { client, username, password } = getTokenClient({
1776
2013
  basicUsername: opts.basicUsername,
1777
2014
  basicPassword: opts.basicPassword
@@ -1782,42 +2019,40 @@ function list6(parent) {
1782
2019
 
1783
2020
  // src/commands/token/revoke.ts
1784
2021
  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(
2022
+ const cmd = parent.command("revoke").description("Revoke a Personal Access Token by id").option("--basic-username <u>", "Override $JIRA_BASIC_USERNAME").option(
1786
2023
  "--basic-password <p>",
1787
2024
  "Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)"
1788
- ).action(async (opts) => {
2025
+ );
2026
+ subjectArg(cmd, "tokenId", { parser: positiveInt, description: "Token id to revoke" });
2027
+ examples(cmd, ["173"]);
2028
+ cmd.action(async (tokenId, opts) => {
1789
2029
  const { client, username, password } = getTokenClient({
1790
2030
  basicUsername: opts.basicUsername,
1791
2031
  basicPassword: opts.basicPassword
1792
2032
  });
1793
- await client.accessTokens.revoke({ username, password, tokenId: opts.id });
1794
- output({ revoked: opts.id });
2033
+ await client.accessTokens.revoke({ username, password, tokenId });
2034
+ output({ revoked: tokenId });
1795
2035
  });
1796
2036
  }
1797
2037
 
1798
2038
  // src/commands/token/index.ts
1799
- function registerTokenCommands(program2) {
1800
- const token = program2.command("token").description("Personal Access Token management").addHelpText(
2039
+ function registerTokenCommands(program) {
2040
+ const token = program.command("token").description("Personal Access Token management");
2041
+ examples(token, ["list", "create --name my-pat", "revoke 173"]);
2042
+ token.addHelpText(
1801
2043
  "after",
1802
- `
1803
- Examples:
1804
- $ jiradc token list
1805
- $ jiradc token create --name my-pat
1806
- $ jiradc token revoke --id 173
1807
-
1808
- Auth:
1809
- Requires JIRA_BASIC_USERNAME + JIRA_BASIC_PASSWORD
1810
- (or --basic-username / --basic-password on any subcommand).
1811
- `
2044
+ "\nAuth:\n Requires JIRA_BASIC_USERNAME + JIRA_BASIC_PASSWORD\n (or --basic-username / --basic-password on any subcommand)."
1812
2045
  );
1813
- create4(token);
1814
- list6(token);
2046
+ create6(token);
2047
+ list7(token);
1815
2048
  revoke(token);
1816
2049
  }
1817
2050
 
1818
2051
  // src/commands/user/get.ts
1819
2052
  function get3(parent) {
1820
- parent.command("get <username>").description("Get a user profile by exact username or key").option("--by-key", "Treat the positional argument as a user key instead of a username").addHelpText("after", "\nExamples:\n jiradc user get jsmith\n jiradc user get JIRAUSER10100 --by-key").action(async (identifier, opts) => {
2053
+ const cmd = parent.command("get <username>").description("Get a user profile by exact username or key").option("--by-key", "Treat the positional argument as a user key instead of a username");
2054
+ examples(cmd, ["jsmith", "JIRAUSER10100 --by-key"]);
2055
+ cmd.action(async (identifier, opts) => {
1821
2056
  const client = getClient();
1822
2057
  const result = await client.users.getUser(opts.byKey ? { key: identifier } : { username: identifier });
1823
2058
  output(transformUser(result));
@@ -1826,7 +2061,9 @@ function get3(parent) {
1826
2061
 
1827
2062
  // src/commands/user/me.ts
1828
2063
  function me(parent) {
1829
- parent.command("me").description("Get the authenticated user profile").addHelpText("after", "\nExamples:\n jiradc user me").action(async () => {
2064
+ const cmd = parent.command("me").description("Get the authenticated user profile");
2065
+ examples(cmd, [""]);
2066
+ cmd.action(async () => {
1830
2067
  const client = getClient();
1831
2068
  const result = await client.users.getMyself();
1832
2069
  output(transformUser(result));
@@ -1835,10 +2072,9 @@ function me(parent) {
1835
2072
 
1836
2073
  // src/commands/user/search.ts
1837
2074
  function search3(parent) {
1838
- parent.command("search <query>").description("Search users by partial username, display name or email").option("--limit <n>", "Maximum results (1-50)", intInRange(1, 50), 25).option("--start <n>", "Starting index (pagination offset)", nonNegativeInt, 0).option("--include-inactive", "Include inactive users in results", false).addHelpText(
1839
- "after",
1840
- '\nExamples:\n jiradc user search Drago\n jiradc user search "John Smith"\n jiradc user search dragomir@first.bet --limit 5'
1841
- ).action(async (query, opts) => {
2075
+ const cmd = parent.command("search <query>").description("Search users by partial username, display name or email").option("--limit <n>", "Maximum results (1-50)", intInRange(1, 50), 25).option("--start <n>", "Starting index (pagination offset)", nonNegativeInt, 0).option("--include-inactive", "Include inactive users in results", false);
2076
+ examples(cmd, ["Smith", '"John Smith"', "jsmith@example.com --limit 5"]);
2077
+ cmd.action(async (query, opts) => {
1842
2078
  const client = getClient();
1843
2079
  const result = await client.users.searchUsers({
1844
2080
  username: query,
@@ -1852,46 +2088,30 @@ function search3(parent) {
1852
2088
  }
1853
2089
 
1854
2090
  // src/commands/user/index.ts
1855
- function registerUserCommands(program2) {
1856
- const user = program2.command("user").description("User operations").addHelpText(
1857
- "after",
1858
- `
1859
- Examples:
1860
- $ jiradc user me
1861
- $ jiradc user get jsmith
1862
- $ jiradc user search Drago
1863
- `
1864
- );
2091
+ function registerUserCommands(program) {
2092
+ const user = program.command("user").description("User operations");
2093
+ examples(user, ["me", "get jsmith", "search Smith"]);
1865
2094
  me(user);
1866
2095
  get3(user);
1867
2096
  search3(user);
1868
2097
  }
1869
2098
 
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
- }
2099
+ // src/program.ts
1881
2100
  var DIM = "\x1B[2m";
1882
2101
  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", `
2102
+ function buildProgram() {
2103
+ const program = new Command11();
2104
+ program.name("jiradc").description("Jira Data Center CLI").version(readPackageVersion(import.meta.url)).configureHelp({
2105
+ styleTitle: (str) => styleText("bold", str),
2106
+ styleUsage: (str) => styleText("dim", str),
2107
+ styleCommandDescription: (str) => styleText("dim", str),
2108
+ styleOptionDescription: (str) => styleText("dim", str),
2109
+ styleSubcommandDescription: (str) => styleText("dim", str)
2110
+ }).addHelpText("beforeAll", `
1891
2111
  ${styleText("bold", "jiradc")} ${DIM}\u2014 Jira Data Center CLI${RESET}
1892
2112
  `).addHelpText(
1893
- "after",
1894
- `
2113
+ "after",
2114
+ `
1895
2115
  ${styleText("bold", "Environment:")}
1896
2116
  JIRA_URL Jira Server base URL ${DIM}(e.g., https://jira.example.com)${RESET}
1897
2117
  JIRA_TOKEN Personal Access Token ${DIM}(generate in Jira > Profile > Personal Access Tokens)${RESET}
@@ -1905,21 +2125,45 @@ ${styleText("bold", "Examples:")}
1905
2125
  ${DIM}$${RESET} jiradc board list --type scrum
1906
2126
  ${DIM}$${RESET} jiradc sprint list --board 42 --state active
1907
2127
  `
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);
2128
+ );
2129
+ program.option("--pretty", "Pretty-print JSON output");
2130
+ program.hook("preAction", (thisCommand) => {
2131
+ if (thisCommand.optsWithGlobals().pretty) setPretty(true);
2132
+ });
2133
+ registerIssueCommands(program);
2134
+ registerProjectCommands(program);
2135
+ registerComponentCommands(program);
2136
+ registerBoardCommands(program);
2137
+ registerSprintCommands(program);
2138
+ registerFieldCommands(program);
2139
+ registerUserCommands(program);
2140
+ registerTokenCommands(program);
2141
+ return program;
1925
2142
  }
2143
+
2144
+ // src/utils/credentials.ts
2145
+ function getCredentialInfo() {
2146
+ const baseUrl = process.env.JIRA_URL;
2147
+ const token = process.env.JIRA_TOKEN;
2148
+ return {
2149
+ environment: {
2150
+ JIRA_URL: {
2151
+ value: baseUrl ?? null,
2152
+ description: "Your Jira Server base URL (e.g., https://jira.example.com)"
2153
+ },
2154
+ JIRA_TOKEN: {
2155
+ value: token ? "<set>" : null,
2156
+ description: "Personal Access Token"
2157
+ }
2158
+ },
2159
+ tokenUrl: `${baseUrl ?? "https://jira.example.com"}/secure/ViewProfile.jspa`
2160
+ };
2161
+ }
2162
+
2163
+ // src/index.ts
2164
+ await runCli(buildProgram(), {
2165
+ credentialInfo: getCredentialInfo,
2166
+ service: "Jira",
2167
+ authRecovery: "Set the JIRA_URL and JIRA_TOKEN environment variables.",
2168
+ networkRecovery: "Verify JIRA_URL is correct, the server is reachable, and you are on the VPN if required."
2169
+ });