jiradc-cli 1.0.38 → 1.0.40

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.
package/dist/index.js CHANGED
@@ -1,688 +1,31 @@
1
1
  #!/usr/bin/env node
2
-
3
- // ../../cli-utils/dist/cache.js
4
- import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
5
- import { homedir } from "os";
6
- import { join } from "path";
7
- var DEFAULT_TTL = 36e5;
8
- function getCacheDir(name) {
9
- return join(homedir(), ".cache", name);
10
- }
11
- function getCachePath(name, key) {
12
- return join(getCacheDir(name), `${key}.json`);
13
- }
14
- function cacheGet(options, key) {
15
- const ttl = options.ttl ?? DEFAULT_TTL;
16
- const path = getCachePath(options.name, key);
17
- try {
18
- const stat = statSync(path);
19
- if (Date.now() - stat.mtimeMs > ttl)
20
- return null;
21
- const raw = readFileSync(path, "utf-8");
22
- const entry = JSON.parse(raw);
23
- return entry.data;
24
- } catch {
25
- return null;
26
- }
27
- }
28
- function cacheSet(options, key, data) {
29
- const dir = getCacheDir(options.name);
30
- const path = getCachePath(options.name, key);
31
- const entry = { data, timestamp: Date.now() };
32
- try {
33
- mkdirSync(dir, { recursive: true });
34
- writeFileSync(path, JSON.stringify(entry));
35
- } catch {
36
- }
37
- }
38
- async function cacheGetOrFetch(options, key, fetcher) {
39
- const cached = cacheGet(options, key);
40
- if (cached !== null)
41
- return cached;
42
- const data = await fetcher();
43
- cacheSet(options, key, data);
44
- return data;
45
- }
46
-
47
- // ../../cli-utils/dist/bootstrap.js
48
- import { readFileSync as readFileSync2 } from "fs";
49
- import { dirname, join as join2 } from "path";
50
- import { fileURLToPath } from "url";
51
- function readPackageVersion(importMetaUrl) {
52
- try {
53
- const here = dirname(fileURLToPath(importMetaUrl));
54
- const pkg = JSON.parse(readFileSync2(join2(here, "..", "package.json"), "utf-8"));
55
- return pkg.version ?? "0.0.0";
56
- } catch {
57
- return "0.0.0";
58
- }
59
- }
60
-
61
- // ../../cli-utils/dist/validators.js
62
- import { InvalidArgumentError } from "commander";
63
- function intInRange(min, max) {
64
- return (raw) => {
65
- const n = parseInt(raw, 10);
66
- if (Number.isNaN(n) || !Number.isFinite(n)) {
67
- throw new InvalidArgumentError("Must be an integer.");
68
- }
69
- if (n < min || n > max) {
70
- throw new InvalidArgumentError(`Must be between ${min} and ${max}.`);
71
- }
72
- return n;
73
- };
74
- }
75
- function nonNegativeInt(raw) {
76
- const n = parseInt(raw, 10);
77
- if (Number.isNaN(n) || n < 0) {
78
- throw new InvalidArgumentError("Must be a non-negative integer.");
79
- }
80
- return n;
81
- }
82
- function positiveInt(raw) {
83
- const n = parseInt(raw, 10);
84
- if (Number.isNaN(n) || n < 1) {
85
- throw new InvalidArgumentError("Must be a positive integer.");
86
- }
87
- return n;
88
- }
89
- function listOf(item) {
90
- return (raw, previous) => [...Array.isArray(previous) ? previous : [], item(raw)];
91
- }
92
- function text(raw) {
93
- return raw;
94
- }
95
- function nonEmpty(raw) {
96
- if (raw.trim() === "") {
97
- throw new InvalidArgumentError("Must not be empty.");
98
- }
99
- return raw;
100
- }
101
- function integer(raw) {
102
- if (!/^[+-]?\d+$/.test(raw.trim())) {
103
- throw new InvalidArgumentError("Must be an integer.");
104
- }
105
- return Number(raw);
106
- }
107
- function date(raw) {
108
- const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
109
- if (!m) {
110
- throw new InvalidArgumentError("Must be a date in YYYY-MM-DD format.");
111
- }
112
- const [, y, mo, d] = m;
113
- const year = Number(y);
114
- const month = Number(mo);
115
- const day = Number(d);
116
- const dt = new Date(Date.UTC(year, month - 1, day));
117
- if (dt.getUTCFullYear() !== year || dt.getUTCMonth() !== month - 1 || dt.getUTCDate() !== day) {
118
- throw new InvalidArgumentError("Not a real calendar date.");
119
- }
120
- return raw;
121
- }
122
- function dateTime(raw) {
123
- if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(raw) || Number.isNaN(Date.parse(raw))) {
124
- throw new InvalidArgumentError("Must be an ISO 8601 date-time, e.g. 2026-06-08T14:30:00Z.");
125
- }
126
- return raw;
127
- }
128
-
129
- // ../../cli-utils/dist/json.js
130
- import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
131
- function jsonShape(schema) {
132
- return (raw) => {
133
- let parsed;
134
- try {
135
- parsed = JSON.parse(raw);
136
- } catch {
137
- throw new InvalidArgumentError2("Must be valid JSON.");
138
- }
139
- const result = schema.safeParse(parsed);
140
- if (!result.success) {
141
- const issues3 = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
142
- throw new InvalidArgumentError2(`Invalid JSON shape \u2014 ${issues3}`);
143
- }
144
- return result.data;
145
- };
146
- }
147
-
148
- // ../../cli-utils/dist/text-or-file.js
149
- import { readFileSync as readFileSync3 } from "fs";
150
- import { InvalidArgumentError as InvalidArgumentError3, Option } from "commander";
151
- var STDIN_REF = "-";
152
- function rejectStdinSentinel(value) {
153
- if (value === "@-" || value === STDIN_REF) {
154
- throw new InvalidArgumentError3(`"${value}" looks like a stdin redirect, which is not supported here. Pass the text directly, or read it from a file/stdin with the matching --\u2026-file <path|-> option.`);
155
- }
156
- return value;
157
- }
158
- function readFileOrStdin(ref) {
159
- if (ref === STDIN_REF) {
160
- if (process.stdin.isTTY) {
161
- throw new InvalidArgumentError3('"--\u2026-file -" reads stdin, but stdin is a terminal (nothing piped).');
162
- }
163
- try {
164
- return readFileSync3(0, "utf8");
165
- } catch (err) {
166
- throw new InvalidArgumentError3(`failed to read stdin for "--\u2026-file -": ${err.message}`);
167
- }
168
- }
169
- try {
170
- return readFileSync3(ref, "utf8");
171
- } catch (err) {
172
- const e = err;
173
- if (e.code === "ENOENT") {
174
- throw new InvalidArgumentError3(`file not found: "${ref}".`);
175
- }
176
- throw new InvalidArgumentError3(`failed to read file "${ref}": ${e.message}`);
177
- }
178
- }
179
- function textOrFileOption(cmd, name, opts = {}) {
180
- const label = `${name.charAt(0).toUpperCase()}${name.slice(1)} content`;
181
- cmd.option(`--${name} <text>`, opts.description ?? label, rejectStdinSentinel);
182
- cmd.addOption(new Option(`--${name}-file <path>`, `Read --${name} from a file, or "-" for stdin (mutually exclusive with --${name})`).conflicts(name).argParser(text));
183
- return cmd;
184
- }
185
- function resolveTextOrFile(opts, name, { required = true } = {}) {
186
- const record = opts;
187
- const literal = record[name];
188
- const ref = record[`${name}File`];
189
- if (literal !== void 0 && ref !== void 0) {
190
- throw new InvalidArgumentError3(`--${name} and --${name}-file are mutually exclusive; provide only one.`);
191
- }
192
- if (ref !== void 0) {
193
- return readFileOrStdin(ref);
194
- }
195
- if (literal === void 0 && required) {
196
- throw new InvalidArgumentError3(`a ${name} is required: provide --${name} <text> or --${name}-file <path>.`);
197
- }
198
- return literal;
199
- }
200
-
201
- // ../../cli-utils/dist/registry.js
202
- var SUB_ENTITY_ID_NUMERIC = {
203
- comment: true,
204
- worklog: true,
205
- attachment: true,
206
- request: true,
207
- type: true,
208
- employee: true,
209
- message: false,
210
- membership: false
211
- };
212
-
213
- // ../../cli-utils/dist/builders.js
214
- function defineOption(cmd, flags, description, parser, mandatory) {
215
- if (mandatory) {
216
- return parser ? cmd.requiredOption(flags, description, parser) : cmd.requiredOption(flags, description);
217
- }
218
- return parser ? cmd.option(flags, description, parser) : cmd.option(flags, description);
219
- }
220
- function subjectArg(cmd, name, opts = {}) {
221
- const inner = opts.variadic ? `${name}...` : name;
222
- const token = opts.optional ? `[${inner}]` : `<${inner}>`;
223
- return opts.parser ? cmd.argument(token, opts.description ?? "", opts.parser) : cmd.argument(token, opts.description ?? "");
224
- }
225
- function subEntityOption(cmd, entity, opts = {}) {
226
- const numeric = opts.numeric ?? SUB_ENTITY_ID_NUMERIC[entity] ?? true;
227
- const description = `${entity.charAt(0).toUpperCase()}${entity.slice(1)} id`;
228
- return defineOption(cmd, `--${entity}-id <id>`, description, numeric ? positiveInt : text, opts.mandatory);
229
- }
230
- function paginationOptions(cmd, opts = {}) {
231
- const max = opts.maxLimit ?? 1e3;
232
- const def = opts.defaultLimit ?? 25;
233
- cmd.option("--limit <n>", `Max results per page (1-${max})`, intInRange(1, max), def);
234
- if (opts.startIsToken) {
235
- cmd.option("--start <indexOrToken>", "Pagination cursor: 0-based offset or opaque next-page token", text);
236
- } else {
237
- cmd.option("--start <index>", "Pagination offset (0-based)", nonNegativeInt);
238
- }
239
- return cmd;
240
- }
241
- function bodyOption(cmd, opts = {}) {
242
- return textOrFileOption(cmd, "body", { description: "Prose body content", ...opts });
243
- }
244
- function commentOption(cmd, opts = {}) {
245
- return textOrFileOption(cmd, "comment", { description: "Optional note attached to the action", ...opts });
246
- }
247
- var EXAMPLES = /* @__PURE__ */ new WeakSet();
248
- function commandPath(cmd) {
249
- const parts = [];
250
- for (let c = cmd; c; c = c.parent)
251
- parts.unshift(c.name());
252
- return parts.join(" ");
253
- }
254
- function exampleLine(path, entry) {
255
- if (typeof entry === "string")
256
- return entry ? ` ${path} ${entry}` : ` ${path}`;
257
- if ("raw" in entry)
258
- return ` ${entry.raw}`;
259
- const [args, comment] = entry;
260
- const line = args ? `${path} ${args}` : path;
261
- return ` ${line} # ${comment}`;
262
- }
263
- function examples(cmd, entries) {
264
- EXAMPLES.add(cmd);
265
- return cmd.addHelpText("after", () => {
266
- const path = commandPath(cmd);
267
- return `
268
- Examples:
269
- ${entries.map((e) => exampleLine(path, e)).join("\n")}`;
270
- });
271
- }
272
-
273
- // ../../cli-utils/dist/errors.js
274
- import { CommanderError } from "commander";
275
- var EXIT = {
276
- SUCCESS: 0,
277
- GENERIC: 1,
278
- USAGE: 2,
279
- NOT_FOUND: 3,
280
- FORBIDDEN: 4,
281
- CONFLICT: 5,
282
- AUTH: 6
283
- };
284
- var CliAuthError = class extends Error {
285
- constructor(message) {
286
- super(message);
287
- this.name = "CliAuthError";
288
- }
289
- };
290
- var NOT_FOUND_RECOVERY = "Verify the id/key, then check your access.";
291
- var NOT_FOUND_MESSAGE = "Not found: it may not exist, or you may not have permission to see it";
292
- var USAGE_RECOVERY = "Check the command syntax and flags; run the command with --help.";
293
- var CliNotFoundError = class extends Error {
294
- recovery;
295
- constructor(message, recovery = NOT_FOUND_RECOVERY) {
296
- super(message);
297
- this.name = "CliNotFoundError";
298
- this.recovery = recovery;
299
- }
300
- };
301
- var CliUsageError = class extends Error {
302
- recovery;
303
- detail;
304
- constructor(message, recovery = USAGE_RECOVERY, detail) {
305
- super(message);
306
- this.name = "CliUsageError";
307
- this.recovery = recovery;
308
- this.detail = detail;
309
- }
310
- };
311
- var SubcommandRequiredError = class extends Error {
312
- subcommands;
313
- constructor(commandPath3, subcommands) {
314
- super(`'${commandPath3}' requires a subcommand`);
315
- this.name = "SubcommandRequiredError";
316
- this.subcommands = subcommands;
317
- }
318
- };
319
- var UnknownSubcommandError = class extends Error {
320
- subcommands;
321
- suggestion;
322
- constructor(commandPath3, token, subcommands, suggestion) {
323
- super(`'${token}' is not a subcommand of '${commandPath3}'`);
324
- this.name = "UnknownSubcommandError";
325
- this.subcommands = subcommands;
326
- this.suggestion = suggestion;
327
- }
328
- };
329
- var TYPE_EXIT = {
330
- usage: EXIT.USAGE,
331
- not_found: EXIT.NOT_FOUND,
332
- forbidden: EXIT.FORBIDDEN,
333
- conflict: EXIT.CONFLICT,
334
- auth: EXIT.AUTH,
335
- rate_limited: EXIT.GENERIC,
336
- server: EXIT.GENERIC,
337
- timeout: EXIT.GENERIC,
338
- network: EXIT.GENERIC,
339
- unknown: EXIT.GENERIC
340
- };
341
- var TRANSPORT_CODES = {
342
- /** Our own timeout fired: connected (or tried to), no response in time. */
343
- readTimeout: ["ECONNABORTED"],
344
- /** The OS gave up establishing the connection. */
345
- connectTimeout: ["ETIMEDOUT"],
346
- /** No such host, or DNS itself is unavailable. */
347
- unresolved: ["ENOTFOUND", "EAI_AGAIN"],
348
- /** Host is there, nothing is listening on that port. */
349
- refused: ["ECONNREFUSED"],
350
- /** Established, then died mid-flight. */
351
- dropped: ["ECONNRESET", "EPIPE"]
352
- };
353
- function humanMs(ms) {
354
- return ms % 1e3 === 0 ? `${ms / 1e3}s` : `${ms}ms`;
355
- }
356
- function targetHost(err) {
357
- const config = err?.config;
358
- const raw = config?.baseURL ?? config?.url;
359
- if (raw === void 0 || raw === "")
360
- return void 0;
361
- try {
362
- return new URL(raw).host;
363
- } catch {
364
- return void 0;
365
- }
366
- }
367
- function configuredTimeoutMs(err) {
368
- const t = err?.config?.timeout;
369
- return typeof t === "number" && t > 0 ? t : void 0;
370
- }
371
- function retryAfterSeconds(err) {
372
- const headers = err?.response?.headers;
373
- const raw = headers?.["retry-after"];
374
- if (raw === void 0 || raw === "")
375
- return void 0;
376
- const seconds = Number(raw);
377
- if (Number.isFinite(seconds))
378
- return Math.max(0, Math.round(seconds));
379
- if (typeof raw !== "string")
380
- return void 0;
381
- const at = Date.parse(raw);
382
- if (Number.isNaN(at))
383
- return void 0;
384
- return Math.max(0, Math.round((at - Date.now()) / 1e3));
385
- }
386
- function httpStatus(err) {
387
- const e = err;
388
- return e?.response?.status ?? e?.statusCode;
389
- }
390
- function responseDetail(err) {
391
- const e = err;
392
- const data = e?.response?.data;
393
- if (data && typeof data === "object")
394
- return data;
395
- const body = e?.body;
396
- if (typeof body === "string") {
397
- try {
398
- const parsed = JSON.parse(body);
399
- return parsed.error ?? parsed;
400
- } catch {
401
- return void 0;
402
- }
403
- }
404
- if (body && typeof body === "object") {
405
- return body.error ?? body;
406
- }
407
- return void 0;
408
- }
409
- function errorCode(err) {
410
- return err?.code;
411
- }
412
- function normalize(err, opts) {
413
- const message = err instanceof Error ? err.message : String(err);
414
- if (err instanceof CommanderError) {
415
- return {
416
- type: "usage",
417
- message: (message || "Invalid command usage").replace(/^error:\s+/, ""),
418
- recovery: "Check the command syntax and flags; run the command with --help.",
419
- retryable: false
420
- };
421
- }
422
- if (err instanceof SubcommandRequiredError) {
423
- return {
424
- type: "usage",
425
- message,
426
- recovery: `Re-run with one of these subcommands: ${err.subcommands.join(", ")}.`,
427
- retryable: false,
428
- detail: { subcommands: err.subcommands }
429
- };
430
- }
431
- if (err instanceof UnknownSubcommandError) {
432
- const didYouMean = err.suggestion === void 0 ? "" : `Did you mean '${err.suggestion}'? `;
433
- return {
434
- type: "usage",
435
- message,
436
- recovery: `${didYouMean}Valid subcommands: ${err.subcommands.join(", ")}.`,
437
- retryable: false,
438
- detail: { subcommands: err.subcommands }
439
- };
440
- }
441
- if (err instanceof CliAuthError) {
442
- return { type: "auth", message: message || "Missing credentials", recovery: opts.authRecovery, retryable: false };
443
- }
444
- if (err instanceof CliNotFoundError) {
445
- return { type: "not_found", message, recovery: err.recovery, retryable: false };
446
- }
447
- if (err instanceof CliUsageError) {
448
- return {
449
- type: "usage",
450
- message,
451
- recovery: err.recovery,
452
- retryable: false,
453
- ...err.detail !== void 0 ? { detail: err.detail } : {}
454
- };
455
- }
456
- const status = httpStatus(err);
457
- const detail = responseDetail(err);
458
- if (status !== void 0) {
459
- switch (status) {
460
- case 400:
461
- return {
462
- type: "usage",
463
- status,
464
- message: "Bad request",
465
- recovery: "Check parameter values (ids, keys, query syntax) against the API.",
466
- retryable: false,
467
- detail
468
- };
469
- case 401:
470
- return {
471
- type: "auth",
472
- status,
473
- message: "Authentication failed",
474
- recovery: opts.authRecovery,
475
- retryable: false
476
- };
477
- case 403:
478
- return {
479
- type: "forbidden",
480
- status,
481
- message: "Forbidden",
482
- recovery: `Your ${opts.service} account lacks permission for this operation; check token scope and resource permissions.`,
483
- retryable: false,
484
- detail
485
- };
486
- case 404:
487
- return {
488
- type: "not_found",
489
- status,
490
- message: NOT_FOUND_MESSAGE,
491
- recovery: NOT_FOUND_RECOVERY,
492
- retryable: false,
493
- detail
494
- };
495
- case 409:
496
- return {
497
- type: "conflict",
498
- status,
499
- message: "Conflict",
500
- recovery: "The resource changed or already exists; re-fetch current state and retry.",
501
- retryable: false,
502
- detail
503
- };
504
- case 429: {
505
- const wait = retryAfterSeconds(err);
506
- return {
507
- type: "rate_limited",
508
- status,
509
- message: `${opts.service} is rate limiting these requests`,
510
- recovery: wait === void 0 ? `${opts.service} did not say how long to wait. Pause a few seconds before retrying, and make fewer requests at once.` : `${opts.service} asked for a ${wait}s pause before the next request. Wait at least that long, then retry.`,
511
- retryable: true,
512
- ...wait === void 0 ? {} : { detail: { retryAfterSeconds: wait } }
513
- };
514
- }
515
- }
516
- if (status >= 500) {
517
- return {
518
- type: "server",
519
- status,
520
- message: status === 503 ? `${opts.service} returned 503 (service unavailable)` : `${opts.service} returned ${status}`,
521
- recovery: status === 503 ? `${opts.service} is temporarily refusing work. Retry shortly.` : `${opts.service} failed to complete the request. Usually temporary, so retry. If it persists, the request itself may be at fault.`,
522
- retryable: true,
523
- detail
524
- };
525
- }
526
- return {
527
- type: "unknown",
528
- status,
529
- message: `${opts.service} error (HTTP ${status}): ${message}`,
530
- recovery: "Inspect the detail field for the API response.",
531
- retryable: false,
532
- detail
533
- };
534
- }
535
- const code = errorCode(err);
536
- const transport = classifyTransport(err, code, message, opts);
537
- if (transport)
538
- return transport;
539
- return { type: "unknown", message, recovery: "Unexpected error; inspect the message.", retryable: false };
540
- }
541
- function classifyTransport(err, code, message, opts) {
542
- const matches = (codes) => code !== void 0 && codes.includes(code) || codes.some((c) => message.includes(c));
543
- const host = targetHost(err);
544
- const where = host ?? opts.service;
545
- const urlVar = opts.urlEnvVar ?? "the base-URL environment variable";
546
- if (matches(TRANSPORT_CODES.readTimeout)) {
547
- const limit = configuredTimeoutMs(err);
548
- return {
549
- type: "timeout",
550
- message: `${opts.service} did not respond${limit === void 0 ? " in time" : ` within ${humanMs(limit)}`}`,
551
- recovery: `The request reached ${opts.service} but no response came back in time. Usually the server is busy or the query is too large. Retry, or ask for less: fewer results per page, fewer fields, a narrower query.`,
552
- retryable: true
553
- };
554
- }
555
- if (matches(TRANSPORT_CODES.connectTimeout)) {
556
- const limit = configuredTimeoutMs(err);
557
- return {
558
- type: "timeout",
559
- message: `Could not open a connection to ${where}${limit === void 0 ? "" : ` within ${humanMs(limit)}`}`,
560
- recovery: `The host did not accept a connection in time. If it is reachable at all it is likely overloaded, so retry. If this repeats, confirm ${urlVar} points at a host this machine can reach.`,
561
- retryable: true
562
- };
563
- }
564
- if (matches(TRANSPORT_CODES.unresolved)) {
565
- return {
566
- type: "network",
567
- message: `The host ${where} does not resolve`,
568
- recovery: `${urlVar} points at a hostname that cannot be looked up from this machine. Check it for a typo. Retrying will not help until it changes.`,
569
- retryable: false
570
- };
571
- }
572
- if (matches(TRANSPORT_CODES.refused)) {
573
- return {
574
- type: "network",
575
- message: `Nothing accepted a connection at ${where}`,
576
- recovery: `The host resolved but refused the connection, usually a wrong port or a service that is not running. Retrying will not help until that changes.`,
577
- retryable: false
578
- };
579
- }
580
- if (matches(TRANSPORT_CODES.dropped)) {
581
- return {
582
- type: "network",
583
- message: `The connection to ${opts.service} closed before a response arrived`,
584
- recovery: `The connection dropped mid-request. This is usually a blip, so retry.`,
585
- retryable: true
586
- };
587
- }
588
- return void 0;
589
- }
590
- function classifyError(err, opts) {
591
- const base = normalize(err, opts);
592
- const n = opts.adapt ? opts.adapt(base, err) : base;
593
- if (n.type === "auth" && n.detail === void 0 && opts.credentialInfo) {
594
- n.detail = opts.credentialInfo();
595
- }
596
- return {
597
- envelope: {
598
- error: {
599
- type: n.type,
600
- message: n.message,
601
- recovery: n.recovery,
602
- retryable: n.retryable,
603
- ...n.detail !== void 0 ? { detail: n.detail } : {}
604
- }
605
- },
606
- exitCode: TYPE_EXIT[n.type]
607
- };
608
- }
609
- function createErrorHandler(opts) {
610
- return (err) => {
611
- const { envelope, exitCode } = classifyError(err, opts);
612
- process.stderr.write(`${JSON.stringify(envelope)}
613
- `);
614
- return process.exit(exitCode);
615
- };
616
- }
617
- function isCleanCommanderExit(err) {
618
- return err instanceof CommanderError && (err.exitCode === 0 || err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version");
619
- }
620
- function routeErrors(cmd) {
621
- cmd.exitOverride();
622
- cmd.configureOutput({ writeErr: () => void 0 });
623
- cmd.commands.forEach(routeErrors);
624
- }
625
- function commandPath2(cmd) {
626
- const parts = [];
627
- let cur = cmd;
628
- while (cur) {
629
- parts.unshift(cur.name());
630
- cur = cur.parent;
631
- }
632
- return parts.join(" ");
633
- }
634
- function attachSubcommandGuards(cmd) {
635
- cmd.commands.forEach(attachSubcommandGuards);
636
- if (cmd.commands.length === 0)
637
- return;
638
- const hasAction = Boolean(cmd._actionHandler);
639
- if (hasAction)
640
- return;
641
- cmd.allowExcessArguments(true);
642
- cmd.action((...params) => {
643
- const invoked = params[params.length - 1];
644
- const names = cmd.commands.map((c) => c.name()).filter((name) => name !== "help");
645
- const [token] = invoked.args;
646
- if (token === void 0)
647
- throw new SubcommandRequiredError(commandPath2(cmd), names);
648
- throw new UnknownSubcommandError(commandPath2(cmd), token, names, suggestSubcommand(token, names));
649
- });
650
- }
651
- function editDistance(a, b) {
652
- let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
653
- for (let i = 1; i <= a.length; i++) {
654
- const row = [i];
655
- for (let j = 1; j <= b.length; j++) {
656
- const substitution = prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
657
- row[j] = Math.min(row[j - 1] + 1, prev[j] + 1, substitution);
658
- }
659
- prev = row;
660
- }
661
- return prev[b.length];
662
- }
663
- function suggestSubcommand(token, names) {
664
- const MAX_EDITS = 2;
665
- let best;
666
- for (const name of names) {
667
- const distance = editDistance(token.toLowerCase(), name.toLowerCase());
668
- if (distance <= MAX_EDITS && distance < token.length && (!best || distance < best.distance)) {
669
- best = { name, distance };
670
- }
671
- }
672
- return best?.name;
673
- }
674
- async function runCli(program, opts) {
675
- routeErrors(program);
676
- attachSubcommandGuards(program);
677
- try {
678
- await program.parseAsync();
679
- } catch (err) {
680
- if (isCleanCommanderExit(err)) {
681
- process.exit(err.exitCode ?? 0);
682
- }
683
- createErrorHandler(opts)(err);
684
- }
685
- }
2
+ import {
3
+ CliAuthError,
4
+ CliNotFoundError,
5
+ CliUsageError,
6
+ bodyOption,
7
+ cacheGetOrFetch,
8
+ commentOption,
9
+ date,
10
+ dateTime,
11
+ examples,
12
+ intInRange,
13
+ integer,
14
+ jsonShape,
15
+ listOf,
16
+ nonEmpty,
17
+ nonNegativeInt,
18
+ paginationOptions,
19
+ positiveInt,
20
+ readPackageVersion,
21
+ resolveTextOrFile,
22
+ runCli,
23
+ scopeIdOption,
24
+ subEntityOption,
25
+ subjectArg,
26
+ text,
27
+ textOrFileOption
28
+ } from "./chunk-3GFKV36K.js";
686
29
 
687
30
  // src/program.ts
688
31
  import { styleText } from "util";
@@ -1294,10 +637,10 @@ function issues(parent) {
1294
637
  }
1295
638
 
1296
639
  // src/commands/board/list.ts
1297
- import { Option as Option2 } from "commander";
640
+ import { Option } from "commander";
1298
641
  var BOARD_TYPES = ["scrum", "kanban", "simple"];
1299
642
  function list(parent) {
1300
- 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", text).addOption(new Option2("--type <type>", "Board type filter").choices(BOARD_TYPES)).option("--name <name>", "Filter boards by name", text);
643
+ 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", text).addOption(new Option("--type <type>", "Board type filter").choices(BOARD_TYPES)).option("--name <name>", "Filter boards by name", text);
1301
644
  examples(cmd, ["", "--limit 10", "--project PROJ", '--type scrum --name "Team Board"']);
1302
645
  cmd.action(async (opts) => {
1303
646
  const client = getClient();
@@ -1321,7 +664,7 @@ function registerBoardCommands(program) {
1321
664
  }
1322
665
 
1323
666
  // src/commands/component/create.ts
1324
- import { Option as Option3 } from "commander";
667
+ import { Option as Option2 } from "commander";
1325
668
  var ASSIGNEE_TYPES = [
1326
669
  "PROJECT_DEFAULT",
1327
670
  "COMPONENT_LEAD",
@@ -1329,7 +672,7 @@ var ASSIGNEE_TYPES = [
1329
672
  "UNASSIGNED"
1330
673
  ];
1331
674
  function create(parent) {
1332
- const cmd = parent.command("create").description("Create a new component in a project").requiredOption("--project <key>", "Project key (e.g., PROJ)", text).requiredOption("--name <name>", "Component name", text).option("--lead <username>", "Username of the component lead", text).addOption(new Option3("--assignee-type <type>", "Assignee strategy").choices(ASSIGNEE_TYPES));
675
+ const cmd = parent.command("create").description("Create a new component in a project").requiredOption("--project <key>", "Project key (e.g., PROJ)", text).requiredOption("--name <name>", "Component name", text).option("--lead <username>", "Username of the component lead", text).addOption(new Option2("--assignee-type <type>", "Assignee strategy").choices(ASSIGNEE_TYPES));
1333
676
  textOrFileOption(cmd, "description", { description: "Component description" });
1334
677
  examples(cmd, [
1335
678
  "--project PROJ --name Backend",
@@ -1397,7 +740,7 @@ function list2(parent) {
1397
740
  }
1398
741
 
1399
742
  // src/commands/component/update.ts
1400
- import { Option as Option4 } from "commander";
743
+ import { Option as Option3 } from "commander";
1401
744
  var ASSIGNEE_TYPES2 = [
1402
745
  "PROJECT_DEFAULT",
1403
746
  "COMPONENT_LEAD",
@@ -1405,7 +748,7 @@ var ASSIGNEE_TYPES2 = [
1405
748
  "UNASSIGNED"
1406
749
  ];
1407
750
  function update(parent) {
1408
- const cmd = parent.command("update").description("Update an existing component (only provided fields are changed)").argument("<id>", "Component ID", positiveInt).option("--name <name>", "New component name", text).option("--lead <username>", "Username of the component lead (empty string clears it)", text).addOption(new Option4("--assignee-type <type>", "New assignee strategy").choices(ASSIGNEE_TYPES2));
751
+ const cmd = parent.command("update").description("Update an existing component (only provided fields are changed)").argument("<id>", "Component ID", positiveInt).option("--name <name>", "New component name", text).option("--lead <username>", "Username of the component lead (empty string clears it)", text).addOption(new Option3("--assignee-type <type>", "New assignee strategy").choices(ASSIGNEE_TYPES2));
1409
752
  textOrFileOption(cmd, "description", { description: "New component description" });
1410
753
  examples(cmd, [
1411
754
  "11289 --name Backend",
@@ -1450,13 +793,29 @@ function registerComponentCommands(program) {
1450
793
  issueCount(component);
1451
794
  }
1452
795
 
796
+ // src/utils/field-search.ts
797
+ function tier(field, keyword) {
798
+ const id = field.id.toLowerCase();
799
+ const name = (field.name ?? "").toLowerCase();
800
+ if (id === keyword) return 0;
801
+ if (name === keyword) return field.custom === true ? 2 : 1;
802
+ return field.custom === true ? 4 : 3;
803
+ }
804
+ function rankFieldMatches(fields, keyword, limit) {
805
+ const needle = keyword.toLowerCase();
806
+ const matches = fields.filter(
807
+ (f) => f.id.toLowerCase().includes(needle) || (f.name ?? "").toLowerCase().includes(needle)
808
+ );
809
+ return matches.sort((a, b) => tier(a, needle) - tier(b, needle)).slice(0, limit);
810
+ }
811
+
1453
812
  // src/commands/field/search.ts
1454
813
  function search(parent) {
1455
814
  const cmd = parent.command("search").description("Search for fields by name or ID").argument("<keyword>", "Search keyword", nonEmpty).option("--limit <number>", "Maximum number of results (1-1000)", intInRange(1, 1e3), 25);
1456
815
  examples(cmd, ["epic", "customfield_10100", "priority --limit 5"]);
1457
816
  cmd.action(async (keyword, opts) => {
1458
817
  const client = getClient();
1459
- const result = await client.fields.search(keyword, opts.limit);
818
+ const result = rankFieldMatches(await client.fields.getAll(), keyword, opts.limit);
1460
819
  output(result.map(transformField));
1461
820
  });
1462
821
  }
@@ -1486,21 +845,21 @@ async function resolveUserToken(token) {
1486
845
 
1487
846
  // src/utils/validators.ts
1488
847
  import { existsSync } from "fs";
1489
- import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
848
+ import { InvalidArgumentError } from "commander";
1490
849
  function issueKey(raw) {
1491
850
  if (!/^(\d+|[A-Z][A-Z0-9]+-\d+)$/.test(raw)) {
1492
- throw new InvalidArgumentError4("Must be a Jira issue key (e.g. PROJ-123) or a numeric issue id.");
851
+ throw new InvalidArgumentError("Must be a Jira issue key (e.g. PROJ-123) or a numeric issue id.");
1493
852
  }
1494
853
  return raw;
1495
854
  }
1496
855
  function keyList(raw) {
1497
856
  const keys = raw.split(",").map((k) => k.trim()).filter(Boolean);
1498
- if (keys.length === 0) throw new InvalidArgumentError4("Provide at least one comma-separated issue key.");
857
+ if (keys.length === 0) throw new InvalidArgumentError("Provide at least one comma-separated issue key.");
1499
858
  for (const k of keys) issueKey(k);
1500
859
  return keys;
1501
860
  }
1502
861
  function filePath(raw) {
1503
- if (!existsSync(raw)) throw new InvalidArgumentError4(`File not found: ${raw}`);
862
+ if (!existsSync(raw)) throw new InvalidArgumentError(`File not found: ${raw}`);
1504
863
  return raw;
1505
864
  }
1506
865
 
@@ -1532,14 +891,14 @@ function deleteAttachment(parent) {
1532
891
  }
1533
892
 
1534
893
  // src/commands/issue/attachment/download-all.ts
1535
- import { mkdirSync as mkdirSync2 } from "fs";
1536
- import { join as join3 } from "path";
894
+ import { mkdirSync } from "fs";
895
+ import { join } from "path";
1537
896
  function downloadAll(parent) {
1538
897
  const cmd = parent.command("download-all").description("Download all attachments from an issue").argument("<key>", "Issue key", issueKey).requiredOption("--output <dir>", "Local directory to save attachments into", text);
1539
898
  examples(cmd, ["PROJ-123 --output ./downloads"]);
1540
899
  cmd.action(async (key, opts) => {
1541
900
  const client = getClient();
1542
- mkdirSync2(opts.output, { recursive: true });
901
+ mkdirSync(opts.output, { recursive: true });
1543
902
  const issue = await client.issues.get({
1544
903
  issueKeyOrId: key,
1545
904
  fields: ["attachment"]
@@ -1556,7 +915,7 @@ function downloadAll(parent) {
1556
915
  failed.push({ filename: att.filename, error: "No content URL" });
1557
916
  continue;
1558
917
  }
1559
- const destPath = join3(opts.output, att.filename);
918
+ const destPath = join(opts.output, att.filename);
1560
919
  try {
1561
920
  await client.issues.downloadAttachment({ url: att.content, destinationPath: destPath });
1562
921
  results.push({ filename: att.filename, size: att.size, path: destPath });
@@ -1727,7 +1086,7 @@ function changelog(parent) {
1727
1086
  // src/commands/issue/clone.ts
1728
1087
  import { unlink } from "fs/promises";
1729
1088
  import { tmpdir } from "os";
1730
- import { join as join4 } from "path";
1089
+ import { join as join2 } from "path";
1731
1090
  var CLONE_FIELDS = [
1732
1091
  "summary",
1733
1092
  "description",
@@ -1777,7 +1136,7 @@ function clone(parent) {
1777
1136
  const tmpFiles = [];
1778
1137
  const copied = await Promise.all(
1779
1138
  f.attachment.map(async (att) => {
1780
- const tmpPath = join4(tmpdir(), `jiradc-clone-${Date.now()}-${att.filename}`);
1139
+ const tmpPath = join2(tmpdir(), `jiradc-clone-${Date.now()}-${att.filename}`);
1781
1140
  tmpFiles.push(tmpPath);
1782
1141
  await client.issues.downloadAttachment({ url: att.content, destinationPath: tmpPath });
1783
1142
  await client.issues.addAttachment({ issueKeyOrId: newKey, filePath: tmpPath });
@@ -1910,7 +1269,7 @@ function registerCommentCommands(parent) {
1910
1269
  }
1911
1270
 
1912
1271
  // src/commands/issue/create.ts
1913
- import { Option as Option5 } from "commander";
1272
+ import { Option as Option4 } from "commander";
1914
1273
  import { z as z2 } from "zod";
1915
1274
  var fieldsSchema = z2.record(z2.unknown());
1916
1275
  function create3(parent) {
@@ -1919,7 +1278,7 @@ function create3(parent) {
1919
1278
  `Additional fields as JSON (e.g., '{"customfield_10100": "EPIC-1"}')`,
1920
1279
  jsonShape(fieldsSchema)
1921
1280
  ).addOption(
1922
- new Option5("--custom-fields <json>", "Deprecated alias for --fields").argParser(jsonShape(fieldsSchema)).hideHelp()
1281
+ new Option4("--custom-fields <json>", "Deprecated alias for --fields").argParser(jsonShape(fieldsSchema)).hideHelp()
1923
1282
  );
1924
1283
  textOrFileOption(cmd, "description", { description: "Issue description in wiki markup" });
1925
1284
  examples(cmd, [
@@ -2096,6 +1455,7 @@ var DEFAULT_FIELDS = [...SEARCH_DEFAULT_FIELDS, "description", "comment"];
2096
1455
  // src/utils/field-selectors.ts
2097
1456
  var WILDCARD = "*";
2098
1457
  var NEGATION = "-";
1458
+ var UNPUBLISHED_SELECTORS = ["parent"];
2099
1459
  function buildFieldNameIndex(fields) {
2100
1460
  const ids = /* @__PURE__ */ new Set();
2101
1461
  const idsByLower = /* @__PURE__ */ new Map();
@@ -2115,6 +1475,10 @@ function buildFieldNameIndex(fields) {
2115
1475
  record(field.name, field.id);
2116
1476
  for (const clause of field.clauseNames ?? []) record(clause, field.id);
2117
1477
  }
1478
+ for (const id of UNPUBLISHED_SELECTORS) {
1479
+ ids.add(id);
1480
+ if (!idsByLower.has(id)) idsByLower.set(id, id);
1481
+ }
2118
1482
  return { ids, idsByLower, byName };
2119
1483
  }
2120
1484
  function candidatesFor(token, index) {
@@ -2438,20 +1802,20 @@ function unlink2(parent) {
2438
1802
  import { z as z4 } from "zod";
2439
1803
 
2440
1804
  // src/utils/multi-value.ts
2441
- import { InvalidArgumentError as InvalidArgumentError5 } from "commander";
1805
+ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
2442
1806
  function parseMultiValue(flagName, raw) {
2443
1807
  const items = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
2444
1808
  if (items.length === 0) {
2445
- throw new InvalidArgumentError5(`${flagName} cannot be empty`);
1809
+ throw new InvalidArgumentError2(`${flagName} cannot be empty`);
2446
1810
  }
2447
1811
  const lonePrefix = items.find((s) => s === "+" || s === "-");
2448
1812
  if (lonePrefix !== void 0) {
2449
- throw new InvalidArgumentError5(`${flagName} has an empty value after '${lonePrefix}' prefix`);
1813
+ throw new InvalidArgumentError2(`${flagName} has an empty value after '${lonePrefix}' prefix`);
2450
1814
  }
2451
1815
  const prefixed = items.filter((s) => s.startsWith("+") || s.startsWith("-"));
2452
1816
  const bare = items.filter((s) => !s.startsWith("+") && !s.startsWith("-"));
2453
1817
  if (prefixed.length > 0 && bare.length > 0) {
2454
- throw new InvalidArgumentError5(
1818
+ throw new InvalidArgumentError2(
2455
1819
  `${flagName} mixes set and mutate syntax. Either all values have +/- prefix, or none do.`
2456
1820
  );
2457
1821
  }
@@ -2580,12 +1944,12 @@ function create4(parent) {
2580
1944
  }
2581
1945
 
2582
1946
  // src/commands/issue/worklog/delete.ts
2583
- import { Option as Option6 } from "commander";
1947
+ import { Option as Option5 } from "commander";
2584
1948
  var ADJUST_ESTIMATE = ["new", "leave", "manual", "auto"];
2585
1949
  function deleteWorklog(parent) {
2586
1950
  const cmd = parent.command("delete").description("Delete a worklog entry").argument("<key>", "Issue key", issueKey);
2587
1951
  subEntityOption(cmd, "worklog", { mandatory: true });
2588
- cmd.addOption(new Option6("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text).option(
1952
+ cmd.addOption(new Option5("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text).option(
2589
1953
  "--increase-by <amount>",
2590
1954
  'Amount to increase the estimate by; required when --adjust-estimate is "manual"',
2591
1955
  text
@@ -2626,12 +1990,12 @@ function list5(parent) {
2626
1990
  }
2627
1991
 
2628
1992
  // src/commands/issue/worklog/update.ts
2629
- import { Option as Option7 } from "commander";
1993
+ import { Option as Option6 } from "commander";
2630
1994
  var ADJUST_ESTIMATE2 = ["new", "leave", "auto"];
2631
1995
  function update4(parent) {
2632
1996
  const cmd = parent.command("update").description("Update an existing worklog entry").argument("<key>", "Issue key", issueKey);
2633
1997
  subEntityOption(cmd, "worklog", { mandatory: true });
2634
- cmd.option("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')", text).option("--started <datetime>", "Start time in ISO 8601 format", dateTime).addOption(new Option7("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE2)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text);
1998
+ cmd.option("--time <timeSpent>", "Time spent (e.g., '2h', '30m', '1d 4h')", text).option("--started <datetime>", "Start time in ISO 8601 format", dateTime).addOption(new Option6("--adjust-estimate <mode>", "How to adjust the remaining estimate").choices(ADJUST_ESTIMATE2)).option("--new-estimate <estimate>", 'New remaining estimate; required when --adjust-estimate is "new"', text);
2635
1999
  commentOption(cmd, { description: "Worklog comment" });
2636
2000
  examples(cmd, [
2637
2001
  'PROJ-123 --worklog-id 12345 --time "1h 30m"',
@@ -2815,10 +2179,10 @@ function issues2(parent) {
2815
2179
  }
2816
2180
 
2817
2181
  // src/commands/sprint/list.ts
2818
- import { Option as Option8 } from "commander";
2182
+ import { Option as Option7 } from "commander";
2819
2183
  var SPRINT_STATES = ["future", "active", "closed"];
2820
2184
  function list7(parent) {
2821
- const cmd = parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new Option8("--state <state>", "Filter by sprint state").choices(SPRINT_STATES));
2185
+ const cmd = parent.command("list").description("List sprints for a board").requiredOption("--board <id>", "Board ID", positiveInt).addOption(new Option7("--state <state>", "Filter by sprint state").choices(SPRINT_STATES));
2822
2186
  examples(cmd, ["--board 42", "--board 42 --state active"]);
2823
2187
  cmd.action(async (opts) => {
2824
2188
  const client = getClient();
@@ -2831,10 +2195,10 @@ function list7(parent) {
2831
2195
  }
2832
2196
 
2833
2197
  // src/commands/sprint/update.ts
2834
- import { Argument as Argument4, Option as Option9 } from "commander";
2198
+ import { Argument as Argument4, Option as Option8 } from "commander";
2835
2199
  var SPRINT_STATES2 = ["future", "active", "closed"];
2836
2200
  function update5(parent) {
2837
- const cmd = parent.command("update").description("Update an existing sprint").addArgument(new Argument4("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name", text).addOption(new Option9("--state <state>", "New sprint state").choices(SPRINT_STATES2)).option("--start-date <date>", "New start date in ISO 8601 format", text).option("--end-date <date>", "New end date in ISO 8601 format", text).option("--goal <goal>", "New sprint goal", text);
2201
+ const cmd = parent.command("update").description("Update an existing sprint").addArgument(new Argument4("<id>", "Sprint ID").argParser(positiveInt)).option("--name <name>", "New sprint name", text).addOption(new Option8("--state <state>", "New sprint state").choices(SPRINT_STATES2)).option("--start-date <date>", "New start date in ISO 8601 format", text).option("--end-date <date>", "New end date in ISO 8601 format", text).option("--goal <goal>", "New sprint goal", text);
2838
2202
  examples(cmd, [
2839
2203
  '100 --name "Sprint 10 - Extended"',
2840
2204
  "100 --state active",
@@ -2874,6 +2238,484 @@ function registerSprintCommands(program) {
2874
2238
  deleteSprint(sprint);
2875
2239
  }
2876
2240
 
2241
+ // src/commands/structure/create.ts
2242
+ function create6(parent) {
2243
+ const cmd = parent.command("create").description("Create a new, empty structure").requiredOption("--name <text>", "Structure name", text).option("--description <text>", "Structure description", text);
2244
+ examples(cmd, ['--name "Release 24.3"', '--name Scratch --description "Throwaway"']);
2245
+ cmd.action(async (opts) => {
2246
+ const client = getClient();
2247
+ output(await client.structure.create({ name: opts.name, description: opts.description }));
2248
+ });
2249
+ }
2250
+
2251
+ // src/commands/structure/delete.ts
2252
+ function deleteStructure(parent) {
2253
+ const cmd = parent.command("delete").description("Delete a structure and every row in it. The rows go; the underlying Jira issues do not.");
2254
+ subjectArg(cmd, "structureId", { description: "Structure id", parser: positiveInt });
2255
+ examples(cmd, ["119"]);
2256
+ cmd.action(async (structureId) => {
2257
+ const client = getClient();
2258
+ await client.structure.delete({ structureId });
2259
+ output({ deleted: true, structureId });
2260
+ });
2261
+ }
2262
+
2263
+ // src/utils/structure/forest.ts
2264
+ var FOLDER_TYPE_KEY = "com.almworks.jira.structure:type-folder";
2265
+ function parseForest(forest) {
2266
+ const formula = forest.formula ?? "";
2267
+ if (formula.trim() === "") return [];
2268
+ const rows = [];
2269
+ const lastRowAtDepth = /* @__PURE__ */ new Map();
2270
+ for (const component of formula.split(",")) {
2271
+ const parts = component.split(":");
2272
+ if (parts.length < 3) {
2273
+ throw new CliUsageError(`Malformed forest component "${component}": expected rowId:depth:itemId`);
2274
+ }
2275
+ const [rawRowId, rawDepth, rawItemId] = parts;
2276
+ const rowId = Number(rawRowId);
2277
+ const depth = Number(rawDepth);
2278
+ if (!Number.isInteger(rowId) || !Number.isInteger(depth) || depth < 0) {
2279
+ throw new CliUsageError(`Malformed forest component "${component}": rowId and depth must be integers`);
2280
+ }
2281
+ const slash = rawItemId.indexOf("/");
2282
+ const prefix = slash === -1 ? void 0 : rawItemId.slice(0, slash);
2283
+ const numeric = Number(slash === -1 ? rawItemId : rawItemId.slice(slash + 1));
2284
+ if (!Number.isInteger(numeric)) {
2285
+ throw new CliUsageError(`Malformed forest component "${component}": item id is not numeric`);
2286
+ }
2287
+ const itemType = prefix === void 0 ? "issue" : forest.itemTypes?.[prefix] ?? `unknown:${prefix}`;
2288
+ rows.push({
2289
+ rowId,
2290
+ depth,
2291
+ itemId: rawItemId,
2292
+ itemType,
2293
+ itemNumericId: numeric,
2294
+ parentRowId: depth === 0 ? 0 : lastRowAtDepth.get(depth - 1) ?? 0,
2295
+ isFolder: itemType === FOLDER_TYPE_KEY
2296
+ });
2297
+ lastRowAtDepth.set(depth, rowId);
2298
+ for (const seen of [...lastRowAtDepth.keys()]) {
2299
+ if (seen > depth) lastRowAtDepth.delete(seen);
2300
+ }
2301
+ }
2302
+ return rows;
2303
+ }
2304
+ function childrenOf(rows, parentRowId) {
2305
+ return rows.filter((r) => r.parentRowId === parentRowId);
2306
+ }
2307
+ function descendantsOf(rows, parentRowId) {
2308
+ const start = rows.findIndex((r) => r.rowId === parentRowId);
2309
+ if (start === -1) return [];
2310
+ const parentDepth = rows[start].depth;
2311
+ const out = [];
2312
+ for (let i = start + 1; i < rows.length && rows[i].depth > parentDepth; i += 1) out.push(rows[i]);
2313
+ return out;
2314
+ }
2315
+ function withinDepth(rows, parentRowId, maxDepth) {
2316
+ const base = parentRowId === 0 ? -1 : rows.find((r) => r.rowId === parentRowId)?.depth ?? -1;
2317
+ const scope = parentRowId === 0 ? rows : descendantsOf(rows, parentRowId);
2318
+ return scope.filter((r) => r.depth - base <= maxDepth);
2319
+ }
2320
+ function addSpec(itemId, placeholderRowId = -100) {
2321
+ if (placeholderRowId >= 0) {
2322
+ throw new CliUsageError(`Placeholder row id must be negative, got ${placeholderRowId}`);
2323
+ }
2324
+ return `${placeholderRowId}:0:${itemId}`;
2325
+ }
2326
+ function assertRowExists(rows, rowId, structureId, what = "Row") {
2327
+ if (!rows.some((r) => r.rowId === rowId)) {
2328
+ throw new CliNotFoundError(`${what} ${rowId} is not in structure ${structureId}`);
2329
+ }
2330
+ }
2331
+
2332
+ // src/utils/structure/resolve.ts
2333
+ var VALUE_BATCH_SIZE = 500;
2334
+ async function resolveStructureId(client, idOrName) {
2335
+ if (/^\d+$/.test(idOrName)) return Number(idOrName);
2336
+ const { structures } = await client.structure.list();
2337
+ const wanted = idOrName.trim().toLowerCase();
2338
+ const matches = structures.filter((s) => s.name?.trim().toLowerCase() === wanted);
2339
+ if (matches.length === 1) return matches[0].id;
2340
+ if (matches.length === 0) {
2341
+ throw new CliUsageError(
2342
+ `No structure named "${idOrName}"`,
2343
+ "Pass a structure id, or one of the names in detail.available.",
2344
+ { available: structures.map((s) => ({ id: s.id, name: s.name })).slice(0, 50) }
2345
+ );
2346
+ }
2347
+ throw new CliUsageError(
2348
+ `"${idOrName}" matches ${matches.length} structures`,
2349
+ "Names are not unique here; pass the numeric id of the one you meant.",
2350
+ { matches: matches.map((s) => ({ id: s.id, name: s.name })) }
2351
+ );
2352
+ }
2353
+ async function resolveRowNames(client, structureId, rowIds) {
2354
+ const out = /* @__PURE__ */ new Map();
2355
+ if (rowIds.length === 0) return out;
2356
+ for (let i = 0; i < rowIds.length; i += VALUE_BATCH_SIZE) {
2357
+ const batch = rowIds.slice(i, i + VALUE_BATCH_SIZE);
2358
+ const res = await client.structure.getValues({
2359
+ requests: [
2360
+ {
2361
+ forestSpec: { structureId },
2362
+ rows: batch,
2363
+ // `format` is required; without it the API returns an empty `data` array.
2364
+ attributes: [
2365
+ { id: "summary", format: "text" },
2366
+ { id: "key", format: "text" }
2367
+ ]
2368
+ }
2369
+ ]
2370
+ });
2371
+ const data = res.responses?.[0]?.data ?? [];
2372
+ const summaries = data.find((d) => d.attribute.id === "summary")?.values ?? [];
2373
+ const keys = data.find((d) => d.attribute.id === "key")?.values ?? [];
2374
+ batch.forEach((rowId, idx) => {
2375
+ out.set(rowId, { name: summaries[idx] ?? null, key: keys[idx] ?? null });
2376
+ });
2377
+ }
2378
+ return out;
2379
+ }
2380
+ async function withNames(client, structureId, rows) {
2381
+ const names = await resolveRowNames(
2382
+ client,
2383
+ structureId,
2384
+ rows.map((r) => r.rowId)
2385
+ );
2386
+ return rows.map((r) => ({
2387
+ rowId: r.rowId,
2388
+ depth: r.depth,
2389
+ parentRowId: r.parentRowId,
2390
+ type: r.isFolder ? "folder" : r.itemType === "issue" ? "issue" : r.itemType,
2391
+ name: names.get(r.rowId)?.name ?? null,
2392
+ key: names.get(r.rowId)?.key ?? null,
2393
+ itemId: r.itemId
2394
+ }));
2395
+ }
2396
+
2397
+ // src/commands/structure/folder/create.ts
2398
+ function create7(parent) {
2399
+ const cmd = parent.command("create").description("Create a folder in a structure");
2400
+ scopeIdOption(cmd, "structure", { mandatory: true });
2401
+ cmd.requiredOption("--name <text>", "Folder name", text);
2402
+ cmd.option("--under <rowId>", "Parent folder row id (0 = top level)", integer, 0);
2403
+ examples(cmd, ["--structure 42 --name Backlog", '--structure 42 --name "Phase Two" --under 1001']);
2404
+ cmd.action(async (opts) => {
2405
+ const client = getClient();
2406
+ const structureId = await resolveStructureId(client, opts.structure);
2407
+ const forest = await client.structure.getForest({ structureId });
2408
+ if (opts.under !== 0) {
2409
+ assertRowExists(parseForest(forest), opts.under, structureId, "Target parent row");
2410
+ }
2411
+ const result = await client.structure.createFolder({
2412
+ structureId,
2413
+ version: forest.version,
2414
+ name: opts.name,
2415
+ under: opts.under
2416
+ });
2417
+ output({
2418
+ structureId,
2419
+ itemId: result.itemId,
2420
+ rowId: result.newRowIds?.[0] ?? null,
2421
+ name: opts.name,
2422
+ under: opts.under
2423
+ });
2424
+ });
2425
+ }
2426
+
2427
+ // src/commands/structure/folder/get.ts
2428
+ function get7(parent) {
2429
+ const cmd = parent.command("get").description("Get one folder row, with its path and child counts");
2430
+ subjectArg(cmd, "rowId", { description: "Folder row id", parser: positiveInt });
2431
+ scopeIdOption(cmd, "structure", { mandatory: true });
2432
+ examples(cmd, ['1001 --structure "My Structure"', "1001 --structure 42"]);
2433
+ cmd.action(async (rowId, opts) => {
2434
+ const client = getClient();
2435
+ const structureId = await resolveStructureId(client, opts.structure);
2436
+ const rows = parseForest(await client.structure.getForest({ structureId }));
2437
+ const row = rows.find((r) => r.rowId === rowId);
2438
+ if (!row) throw new CliNotFoundError(`Row ${rowId} is not in structure ${structureId}`);
2439
+ const ancestry = [];
2440
+ for (let cur = row; cur.parentRowId !== 0; ) {
2441
+ const next = rows.find((r) => r.rowId === cur.parentRowId);
2442
+ if (!next) break;
2443
+ ancestry.unshift(next);
2444
+ cur = next;
2445
+ }
2446
+ const children = childrenOf(rows, rowId);
2447
+ const [self, ...path] = await withNames(client, structureId, [row, ...ancestry]);
2448
+ output({
2449
+ structureId,
2450
+ ...self,
2451
+ path: path.map((p) => p.name),
2452
+ childCount: children.length,
2453
+ childFolderCount: children.filter((c) => c.isFolder).length,
2454
+ descendantCount: descendantsOf(rows, rowId).length
2455
+ });
2456
+ });
2457
+ }
2458
+
2459
+ // src/commands/structure/folder/list.ts
2460
+ function list8(parent) {
2461
+ const cmd = parent.command("list").description("List folders in a structure");
2462
+ scopeIdOption(cmd, "structure", { mandatory: true });
2463
+ cmd.option("--parent <rowId>", "Only look under this folder row (default: top level)", integer, 0);
2464
+ cmd.option("--depth <n>", "How many levels below the parent to include", integer, 1);
2465
+ cmd.option("--query <text>", "Only folders whose name contains this text, at any depth", text);
2466
+ paginationOptions(cmd, { defaultLimit: 25 });
2467
+ cmd.option("--all", "Collect every match, not just one page");
2468
+ examples(cmd, [
2469
+ '--structure "My Structure"',
2470
+ '--structure "My Structure" --query billing',
2471
+ "--structure 42 --parent 1001 --depth 2"
2472
+ ]);
2473
+ cmd.action(
2474
+ async (opts) => {
2475
+ const client = getClient();
2476
+ const structureId = await resolveStructureId(client, opts.structure);
2477
+ const forest = await client.structure.getForest({ structureId });
2478
+ const rows = parseForest(forest);
2479
+ const scoped = opts.query ? opts.parent === 0 ? rows : descendantsOf(rows, opts.parent) : withinDepth(rows, opts.parent, opts.depth);
2480
+ const folders = scoped.filter((r) => r.isFolder);
2481
+ let named = await withNames(client, structureId, folders);
2482
+ if (opts.query) {
2483
+ const needle = opts.query.toLowerCase();
2484
+ named = named.filter((r) => (r.name ?? "").toLowerCase().includes(needle));
2485
+ }
2486
+ const start = opts.start ?? 0;
2487
+ const page = opts.all ? named.slice(start) : named.slice(start, start + opts.limit);
2488
+ output({ structureId, total: named.length, start, count: page.length, folders: page });
2489
+ }
2490
+ );
2491
+ }
2492
+
2493
+ // src/commands/structure/folder/index.ts
2494
+ function registerFolderCommands(structure) {
2495
+ const folder = structure.command("folder").description("Find and create folders inside a structure");
2496
+ examples(folder, ['list --structure "My Structure" --query billing']);
2497
+ list8(folder);
2498
+ get7(folder);
2499
+ create7(folder);
2500
+ }
2501
+
2502
+ // src/commands/structure/get.ts
2503
+ function get8(parent) {
2504
+ const cmd = parent.command("get").description("Get one structure by id");
2505
+ subjectArg(cmd, "structureId", { description: "Structure id", parser: positiveInt });
2506
+ cmd.option("--permissions", "Include the structure\u2019s permission rules");
2507
+ examples(cmd, ["1", "1 --permissions"]);
2508
+ cmd.action(async (structureId, opts) => {
2509
+ const client = getClient();
2510
+ output(await client.structure.get({ structureId, withPermissions: opts.permissions }));
2511
+ });
2512
+ }
2513
+
2514
+ // src/commands/structure/list.ts
2515
+ function list9(parent) {
2516
+ const cmd = parent.command("list").description("List every structure visible to you");
2517
+ cmd.option("--permissions", "Include each structure\u2019s permission rules");
2518
+ paginationOptions(cmd, { defaultLimit: 25 });
2519
+ cmd.option("--all", "Collect every match, not just one page");
2520
+ examples(cmd, ["", "--limit 50", "--all --permissions"]);
2521
+ cmd.action(async (opts) => {
2522
+ if (opts.all && process.argv.includes("--limit")) {
2523
+ const { CliUsageError: CliUsageError2 } = await import("./dist-JXPWCMQP.js");
2524
+ throw new CliUsageError2("--all and --limit ask for opposite things; pass one or the other");
2525
+ }
2526
+ const client = getClient();
2527
+ const { structures } = await client.structure.list({ withPermissions: opts.permissions });
2528
+ const start = opts.start ?? 0;
2529
+ const page = opts.all ? structures.slice(start) : structures.slice(start, start + opts.limit);
2530
+ output({ total: structures.length, start, count: page.length, structures: page });
2531
+ });
2532
+ }
2533
+
2534
+ // src/commands/structure/row/add.ts
2535
+ function add(parent) {
2536
+ const cmd = parent.command("add").description("Add an issue to a structure, under a folder");
2537
+ scopeIdOption(cmd, "structure", { mandatory: true });
2538
+ cmd.requiredOption("--issue <key>", "Issue key to place, e.g. PROJ-123", text);
2539
+ cmd.option("--under <rowId>", "Parent folder row id (0 = top level)", integer, 0);
2540
+ examples(cmd, ["--structure 42 --issue PROJ-123 --under 1001", '--structure "My Structure" --issue PROJ-123']);
2541
+ cmd.action(async (opts) => {
2542
+ const client = getClient();
2543
+ const structureId = await resolveStructureId(client, opts.structure);
2544
+ const issue = await client.issues.get({ issueKeyOrId: opts.issue, fields: ["summary"] });
2545
+ const issueId = Number(issue.id);
2546
+ const forest = await client.structure.getForest({ structureId });
2547
+ const rows = parseForest(forest);
2548
+ if (opts.under !== 0) assertRowExists(rows, opts.under, structureId, "Target parent row");
2549
+ const already = childrenOf(rows, opts.under).find((r) => r.itemType === "issue" && r.itemNumericId === issueId);
2550
+ const elsewhere = rows.filter((r) => r.itemType === "issue" && r.itemNumericId === issueId && r.parentRowId !== opts.under).map((r) => ({ rowId: r.rowId, parentRowId: r.parentRowId }));
2551
+ if (already) {
2552
+ output({
2553
+ structureId,
2554
+ issue: issue.key,
2555
+ rowId: already.rowId,
2556
+ under: opts.under,
2557
+ added: false,
2558
+ reason: "already under this parent",
2559
+ alsoAppearsAt: elsewhere
2560
+ });
2561
+ return;
2562
+ }
2563
+ const result = await client.structure.updateForest({
2564
+ structureId,
2565
+ version: forest.version,
2566
+ actions: [{ action: "add", under: opts.under, after: 0, before: 0, forest: addSpec(issueId) }]
2567
+ });
2568
+ output({
2569
+ structureId,
2570
+ issue: issue.key,
2571
+ rowId: result.newRowIds?.[0] ?? null,
2572
+ under: opts.under,
2573
+ added: true,
2574
+ alsoAppearsAt: elsewhere
2575
+ });
2576
+ });
2577
+ }
2578
+
2579
+ // src/commands/structure/row/list.ts
2580
+ function list10(parent) {
2581
+ const cmd = parent.command("list").description("List rows (issues and folders) under a parent row");
2582
+ scopeIdOption(cmd, "structure", { mandatory: true });
2583
+ cmd.option("--parent <rowId>", "Parent row id (0 = top level)", integer, 0);
2584
+ cmd.option("--depth <n>", "How many levels below the parent to include", integer, 1);
2585
+ paginationOptions(cmd, { defaultLimit: 25 });
2586
+ cmd.option("--all", "Collect every match, not just one page");
2587
+ examples(cmd, ["--structure 42", '--structure "My Structure" --parent 1001 --depth 2']);
2588
+ cmd.action(
2589
+ async (opts) => {
2590
+ const client = getClient();
2591
+ const structureId = await resolveStructureId(client, opts.structure);
2592
+ const rows = parseForest(await client.structure.getForest({ structureId }));
2593
+ const scoped = withinDepth(rows, opts.parent, opts.depth);
2594
+ const start = opts.start ?? 0;
2595
+ const page = opts.all ? scoped.slice(start) : scoped.slice(start, start + opts.limit);
2596
+ output({
2597
+ structureId,
2598
+ total: scoped.length,
2599
+ start,
2600
+ count: page.length,
2601
+ rows: await withNames(client, structureId, page)
2602
+ });
2603
+ }
2604
+ );
2605
+ }
2606
+
2607
+ // src/commands/structure/row/move.ts
2608
+ function move(parent) {
2609
+ const cmd = parent.command("move").description("Move a row to a different parent");
2610
+ subjectArg(cmd, "rowId", { description: "Row id to move (not the issue id)", parser: positiveInt });
2611
+ scopeIdOption(cmd, "structure", { mandatory: true });
2612
+ cmd.requiredOption("--under <rowId>", "New parent row id (0 = top level)", integer);
2613
+ examples(cmd, ["1002 --structure 42 --under 1001", '1002 --structure "My Structure" --under 0']);
2614
+ cmd.action(async (rowId, opts) => {
2615
+ const client = getClient();
2616
+ const structureId = await resolveStructureId(client, opts.structure);
2617
+ const forest = await client.structure.getForest({ structureId });
2618
+ const rows = parseForest(forest);
2619
+ assertRowExists(rows, rowId, structureId);
2620
+ if (opts.under !== 0) assertRowExists(rows, opts.under, structureId, "Target parent row");
2621
+ const row = rows.find((r) => r.rowId === rowId);
2622
+ const result = await client.structure.updateForest({
2623
+ structureId,
2624
+ version: forest.version,
2625
+ actions: [{ action: "move", rowId, under: opts.under, after: 0, before: 0 }]
2626
+ });
2627
+ output({ structureId, rowId, from: row.parentRowId, under: opts.under, moved: result.successfulActions > 0 });
2628
+ });
2629
+ }
2630
+
2631
+ // src/commands/structure/row/remove.ts
2632
+ function remove(parent) {
2633
+ const cmd = parent.command("remove").description("Remove a row from a structure. The row goes; the underlying Jira issue does not.");
2634
+ subjectArg(cmd, "rowId", { description: "Row id to remove (not the issue id)", parser: positiveInt });
2635
+ scopeIdOption(cmd, "structure", { mandatory: true });
2636
+ examples(cmd, ["1002 --structure 42", '1002 --structure "My Structure"']);
2637
+ cmd.action(async (rowId, opts) => {
2638
+ const client = getClient();
2639
+ const structureId = await resolveStructureId(client, opts.structure);
2640
+ const forest = await client.structure.getForest({ structureId });
2641
+ const rows = parseForest(forest);
2642
+ assertRowExists(rows, rowId, structureId);
2643
+ const removedDescendants = descendantsOf(rows, rowId).length;
2644
+ const result = await client.structure.updateForest({
2645
+ structureId,
2646
+ version: forest.version,
2647
+ actions: [{ action: "remove", rowId }]
2648
+ });
2649
+ output({ structureId, rowId, removed: result.successfulActions > 0, removedDescendants });
2650
+ });
2651
+ }
2652
+
2653
+ // src/commands/structure/row/index.ts
2654
+ function registerRowCommands(structure) {
2655
+ const row = structure.command("row").description("Place, move and remove rows in a structure. A row is one appearance of an issue, not the issue.");
2656
+ examples(row, ["add --structure 42 --issue PROJ-123 --under 1001"]);
2657
+ list10(row);
2658
+ add(row);
2659
+ move(row);
2660
+ remove(row);
2661
+ }
2662
+
2663
+ // src/commands/structure/search.ts
2664
+ function search3(parent) {
2665
+ const cmd = parent.command("search").description("Find issues by their place in a structure, using S-JQL");
2666
+ subjectArg(cmd, "sjql", {
2667
+ description: `S-JQL query, e.g. "child of folder('Billing')"`,
2668
+ parser: nonEmpty
2669
+ });
2670
+ scopeIdOption(cmd, "structure", { mandatory: true });
2671
+ cmd.option("--fields <fields>", "Comma-separated fields to return", text);
2672
+ paginationOptions(cmd, { defaultLimit: 25, maxLimit: 50 });
2673
+ examples(cmd, [
2674
+ `"child of folder('Billing')" --structure "My Structure"`,
2675
+ `"descendants of folder('Billing')" --structure 42 --limit 50`
2676
+ ]);
2677
+ cmd.action(async (sjql, opts) => {
2678
+ for (const [name, value] of [
2679
+ ["--structure", opts.structure],
2680
+ ["<sjql>", sjql]
2681
+ ]) {
2682
+ if (value.includes('"')) {
2683
+ throw new CliUsageError(
2684
+ `${name} must not contain a double quote \u2014 S-JQL uses single quotes inside, e.g. "child of folder('Billing')"`
2685
+ );
2686
+ }
2687
+ }
2688
+ const client = getClient();
2689
+ const jql = `issue in structure("${opts.structure}", "${sjql}")`;
2690
+ const res = await client.issues.search({
2691
+ jql,
2692
+ startAt: opts.start ?? 0,
2693
+ maxResults: opts.limit,
2694
+ fields: opts.fields
2695
+ });
2696
+ output({
2697
+ jql,
2698
+ total: res.total,
2699
+ startAt: res.startAt,
2700
+ count: res.issues?.length ?? 0,
2701
+ issues: (res.issues ?? []).map(transformIssue)
2702
+ });
2703
+ });
2704
+ }
2705
+
2706
+ // src/commands/structure/index.ts
2707
+ function registerStructureCommands(program) {
2708
+ const structure = program.command("structure").description("Structure plugin \u2014 browse and edit the hierarchies issues are filed into");
2709
+ examples(structure, ["list", `search "child of folder('Billing')" --structure "My Structure"`]);
2710
+ list9(structure);
2711
+ get8(structure);
2712
+ create6(structure);
2713
+ deleteStructure(structure);
2714
+ search3(structure);
2715
+ registerFolderCommands(structure);
2716
+ registerRowCommands(structure);
2717
+ }
2718
+
2877
2719
  // src/commands/token/client.ts
2878
2720
  import { JiraClient as JiraClient2 } from "jira-data-center-client";
2879
2721
  function getTokenClient(options = {}) {
@@ -2893,7 +2735,7 @@ function getTokenClient(options = {}) {
2893
2735
  }
2894
2736
 
2895
2737
  // src/commands/token/create.ts
2896
- function create6(parent) {
2738
+ function create8(parent) {
2897
2739
  const cmd = parent.command("create").description("Create a Personal Access Token. The secret is returned exactly once.").requiredOption("--name <name>", "Token name", text).option(
2898
2740
  "--expiration-duration <days>",
2899
2741
  "Token lifetime in days. Omit for a non-expiring token (admin policy permitting).",
@@ -2920,7 +2762,7 @@ function create6(parent) {
2920
2762
  }
2921
2763
 
2922
2764
  // src/commands/token/list.ts
2923
- function list8(parent) {
2765
+ function list11(parent) {
2924
2766
  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", text).option(
2925
2767
  "--basic-password <p>",
2926
2768
  "Override $JIRA_BASIC_PASSWORD (caution: visible in process table; prefer the env var)",
@@ -2963,13 +2805,13 @@ function registerTokenCommands(program) {
2963
2805
  "after",
2964
2806
  "\nAuth:\n Requires JIRA_BASIC_USERNAME + JIRA_BASIC_PASSWORD\n (or --basic-username / --basic-password on any subcommand)."
2965
2807
  );
2966
- create6(token);
2967
- list8(token);
2808
+ create8(token);
2809
+ list11(token);
2968
2810
  revoke(token);
2969
2811
  }
2970
2812
 
2971
2813
  // src/commands/user/get.ts
2972
- function get7(parent) {
2814
+ function get9(parent) {
2973
2815
  const cmd = parent.command("get").description("Get a user profile by exact username or key").argument("<username>", "Username or user key", nonEmpty).option("--by-key", "Treat the positional argument as a user key instead of a username");
2974
2816
  examples(cmd, ["jsmith", "JIRAUSER10100 --by-key"]);
2975
2817
  cmd.action(async (identifier, opts) => {
@@ -2991,7 +2833,7 @@ function me(parent) {
2991
2833
  }
2992
2834
 
2993
2835
  // src/commands/user/search.ts
2994
- function search3(parent) {
2836
+ function search4(parent) {
2995
2837
  const cmd = parent.command("search").description("Search users by partial username, display name or email").argument("<query>", "Search query", nonEmpty).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);
2996
2838
  examples(cmd, ["Smith", '"John Smith"', "jsmith@example.com --limit 5"]);
2997
2839
  cmd.action(async (query, opts) => {
@@ -3012,8 +2854,8 @@ function registerUserCommands(program) {
3012
2854
  const user = program.command("user").description("User operations");
3013
2855
  examples(user, ["me", "get jsmith", "search Smith"]);
3014
2856
  me(user);
3015
- get7(user);
3016
- search3(user);
2857
+ get9(user);
2858
+ search4(user);
3017
2859
  }
3018
2860
 
3019
2861
  // src/utils/xray/guards.ts
@@ -3029,7 +2871,7 @@ function assertAtLeastOne(opts, keys) {
3029
2871
  }
3030
2872
 
3031
2873
  // src/commands/xray/execution/add.ts
3032
- function add(parent) {
2874
+ function add2(parent) {
3033
2875
  const cmd = parent.command("add").description("Add tests to a Test Execution, directly (--test) and/or by expanding Test Sets (--set)").argument("<execKey>", "Test Execution issue key (e.g. PROJ-200)", issueKey).option("--test <keys>", "Comma-separated test issue keys to add (e.g. PROJ-1,PROJ-2)", keyList).option(
3034
2876
  "--set <keys>",
3035
2877
  "Comma-separated Test Set keys; each set's current tests are added to the execution (e.g. PROJ-300)",
@@ -3047,8 +2889,8 @@ function add(parent) {
3047
2889
  const keys = new Set(opts.test ?? []);
3048
2890
  if (opts.set) {
3049
2891
  const lists = await Promise.all(opts.set.map((setKey) => client.testSets.listTests({ setKey })));
3050
- for (const list21 of lists) {
3051
- const tests = Array.isArray(list21) ? list21 : list21.tests ?? [];
2892
+ for (const list24 of lists) {
2893
+ const tests = Array.isArray(list24) ? list24 : list24.tests ?? [];
3052
2894
  for (const t of tests) {
3053
2895
  if (t.key) keys.add(String(t.key));
3054
2896
  }
@@ -3094,7 +2936,7 @@ function deleteXrayIssue(client, key) {
3094
2936
  }
3095
2937
 
3096
2938
  // src/commands/xray/execution/create.ts
3097
- function create7(parent) {
2939
+ function create9(parent) {
3098
2940
  const cmd = parent.command("create").description("Create a new Xray Test Execution issue").requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--summary <text>", "Test Execution summary (title)", text);
3099
2941
  textOrFileOption(cmd, "description", { description: "Test Execution description" });
3100
2942
  examples(cmd, [
@@ -3126,7 +2968,7 @@ function deleteExecution(parent) {
3126
2968
  }
3127
2969
 
3128
2970
  // src/commands/xray/execution/list.ts
3129
- function list9(parent) {
2971
+ function list12(parent) {
3130
2972
  const cmd = parent.command("list").description(
3131
2973
  "List Test Executions that contain a given test (inverse view). For the tests inside an execution, use: xray test list --execution <key>."
3132
2974
  ).requiredOption("--test <key>", "Test issue key whose executions to list (e.g. PROJ-584)", issueKey);
@@ -3139,7 +2981,7 @@ function list9(parent) {
3139
2981
  }
3140
2982
 
3141
2983
  // src/commands/xray/execution/remove.ts
3142
- function remove(parent) {
2984
+ function remove2(parent) {
3143
2985
  const cmd = parent.command("remove").description("Remove tests from a Test Execution").argument("<execKey>", "Test Execution issue key (e.g. PROJ-200)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. PROJ-1,PROJ-2)", keyList);
3144
2986
  examples(cmd, ["PROJ-200 --test PROJ-1", "PROJ-200 --test PROJ-1,PROJ-2"]);
3145
2987
  cmd.action(async (execKey, opts) => {
@@ -3172,17 +3014,17 @@ function registerExecutionCommands(xray) {
3172
3014
  "add PROJ-200 --test PROJ-1,PROJ-2",
3173
3015
  "list --test PROJ-584"
3174
3016
  ]);
3175
- create7(execution2);
3017
+ create9(execution2);
3176
3018
  update6(execution2);
3177
3019
  deleteExecution(execution2);
3178
- add(execution2);
3179
- remove(execution2);
3180
- list9(execution2);
3020
+ add2(execution2);
3021
+ remove2(execution2);
3022
+ list12(execution2);
3181
3023
  }
3182
3024
 
3183
3025
  // src/commands/xray/export/feature.ts
3184
- import { writeFileSync as writeFileSync2 } from "fs";
3185
- import { basename, join as join5 } from "path";
3026
+ import { writeFileSync } from "fs";
3027
+ import { basename, join as join3 } from "path";
3186
3028
  function feature(parent) {
3187
3029
  const cmd = parent.command("feature").description("Export Gherkin .feature files for the given test keys").requiredOption("--test <keys>", "Comma-separated Test, Set, Plan, or Execution issue keys", keyList).option("--output <path>", "Write the exported file to this path (default: ./<filename> from response)", text).option("--zip", "Request a zip archive instead of a single .feature file");
3188
3030
  examples(cmd, ["--test PROJ-584 --output ai-584.feature"]);
@@ -3192,8 +3034,8 @@ function feature(parent) {
3192
3034
  keys: opts.test,
3193
3035
  zip: opts.zip
3194
3036
  });
3195
- const writePath = opts.output ?? join5(".", basename(filename));
3196
- writeFileSync2(writePath, data);
3037
+ const writePath = opts.output ?? join3(".", basename(filename));
3038
+ writeFileSync(writePath, data);
3197
3039
  output({ written: writePath, bytes: data.length });
3198
3040
  });
3199
3041
  }
@@ -3205,7 +3047,7 @@ function registerExportCommands(xray) {
3205
3047
  }
3206
3048
 
3207
3049
  // src/commands/xray/field/list.ts
3208
- function list10(parent) {
3050
+ function list13(parent) {
3209
3051
  const cmd = parent.command("list").description("List Xray custom fields available on this instance");
3210
3052
  examples(cmd, [""]);
3211
3053
  cmd.action(async () => {
@@ -3218,11 +3060,11 @@ function list10(parent) {
3218
3060
  function registerFieldCommands2(xray) {
3219
3061
  const field = xray.command("field").description("Xray field discovery");
3220
3062
  examples(field, ["list"]);
3221
- list10(field);
3063
+ list13(field);
3222
3064
  }
3223
3065
 
3224
3066
  // src/commands/xray/folder/add.ts
3225
- function add2(parent) {
3067
+ function add3(parent) {
3226
3068
  const cmd = parent.command("add").description("Add tests to a folder in the test repository").argument("<folderId>", "Folder id (from folder list)", positiveInt).requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--test <keys>", "Comma-separated test issue keys to add (e.g. PROJ-1,PROJ-2)", keyList);
3227
3069
  examples(cmd, ["818 --project PROJ --test PROJ-1", "818 --project PROJ --test PROJ-1,PROJ-2,PROJ-3"]);
3228
3070
  cmd.action(async (folderId, opts) => {
@@ -3237,7 +3079,7 @@ function add2(parent) {
3237
3079
  }
3238
3080
 
3239
3081
  // src/commands/xray/folder/create.ts
3240
- function create8(parent) {
3082
+ function create10(parent) {
3241
3083
  const cmd = parent.command("create").description("Create a new folder in the test repository").requiredOption("--project <key>", "Project key (e.g. PROJ)", text).option("--parent-id <id>", "Parent folder id (-1 = root)", integer, -1).requiredOption("--name <text>", "Folder name", text);
3242
3084
  examples(cmd, [
3243
3085
  "--project PROJ --name Smoke",
@@ -3267,7 +3109,7 @@ function deleteFolder(parent) {
3267
3109
  }
3268
3110
 
3269
3111
  // src/commands/xray/folder/list.ts
3270
- function list11(parent) {
3112
+ function list14(parent) {
3271
3113
  const cmd = parent.command("list").description("List the test repository folder tree for a project").requiredOption("--project <key>", "Project key (e.g. PROJ)", text);
3272
3114
  examples(cmd, ["--project PROJ", "--project PROJ"]);
3273
3115
  cmd.action(async (opts) => {
@@ -3278,7 +3120,7 @@ function list11(parent) {
3278
3120
  }
3279
3121
 
3280
3122
  // src/commands/xray/folder/remove.ts
3281
- function remove2(parent) {
3123
+ function remove3(parent) {
3282
3124
  const cmd = parent.command("remove").description("Remove tests from a folder in the test repository").argument("<folderId>", "Folder id (from folder list)", positiveInt).requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. PROJ-1,PROJ-2)", keyList);
3283
3125
  examples(cmd, ["818 --project PROJ --test PROJ-1", "818 --project PROJ --test PROJ-1,PROJ-2"]);
3284
3126
  cmd.action(async (folderId, opts) => {
@@ -3316,28 +3158,28 @@ function update7(parent) {
3316
3158
  }
3317
3159
 
3318
3160
  // src/commands/xray/folder/index.ts
3319
- function registerFolderCommands(xray) {
3161
+ function registerFolderCommands2(xray) {
3320
3162
  const folder = xray.command("folder").description(
3321
3163
  "Xray Test Repository folder management (rename/reorder within parent only \u2014 cross-parent moves are UI-only, not supported by the API). To list tests in a folder use: xray test list --folder <id> --project <key>"
3322
3164
  );
3323
3165
  examples(folder, ["list --project PROJ", "create --project PROJ --parent-id -1 --name Smoke"]);
3324
- list11(folder);
3325
- create8(folder);
3166
+ list14(folder);
3167
+ create10(folder);
3326
3168
  update7(folder);
3327
3169
  deleteFolder(folder);
3328
- add2(folder);
3329
- remove2(folder);
3170
+ add3(folder);
3171
+ remove3(folder);
3330
3172
  }
3331
3173
 
3332
3174
  // src/commands/xray/import/execution.ts
3333
- import { readFileSync as readFileSync4 } from "fs";
3175
+ import { readFileSync } from "fs";
3334
3176
  import { basename as basename2 } from "path";
3335
- import { Option as Option10 } from "commander";
3177
+ import { Option as Option9 } from "commander";
3336
3178
  var FORMATS = ["xray", "junit", "testng", "nunit", "xunit", "robot", "cucumber", "behave"];
3337
3179
  var NEEDS_SCOPE = /* @__PURE__ */ new Set(["junit", "testng", "nunit", "xunit", "robot"]);
3338
3180
  function execution(parent) {
3339
3181
  const cmd = parent.command("execution").description("Import test execution results into Xray").requiredOption("--file <path>", "Path to the result file", filePath).addOption(
3340
- new Option10(
3182
+ new Option9(
3341
3183
  "--format <format>",
3342
3184
  "Result format. xray/cucumber/behave: JSON body, no --project/--execution required. junit/testng/nunit/xunit/robot: XML body, requires either --project or --execution."
3343
3185
  ).choices(FORMATS).makeOptionMandatory()
@@ -3362,7 +3204,7 @@ function execution(parent) {
3362
3204
  `--project or --execution is required for ${opts.format} format. Provide --project <key> to create a new execution, or --execution <key> to import into an existing one.`
3363
3205
  );
3364
3206
  }
3365
- const body = readFileSync4(opts.file);
3207
+ const body = readFileSync(opts.file);
3366
3208
  const query = {};
3367
3209
  if (opts.project) query["projectKey"] = opts.project;
3368
3210
  if (opts.execution) query["testExecKey"] = opts.execution;
@@ -3383,13 +3225,13 @@ function execution(parent) {
3383
3225
  }
3384
3226
 
3385
3227
  // src/commands/xray/import/feature.ts
3386
- import { readFileSync as readFileSync5 } from "fs";
3228
+ import { readFileSync as readFileSync2 } from "fs";
3387
3229
  import { basename as basename3 } from "path";
3388
3230
  function feature2(parent) {
3389
3231
  const cmd = parent.command("feature").description("Import a Gherkin .feature file into the Xray test repository (multipart)").requiredOption("--file <path>", "Path to the .feature file", filePath).requiredOption("--project <text>", "Project key to import the feature into", text);
3390
3232
  examples(cmd, ["--file login.feature --project PROJ"]);
3391
3233
  cmd.action(async (opts) => {
3392
- const fileBuffer = readFileSync5(opts.file);
3234
+ const fileBuffer = readFileSync2(opts.file);
3393
3235
  const filename = basename3(opts.file);
3394
3236
  const client = getClient();
3395
3237
  const result = await client.xrayImport.importFeature({
@@ -3409,7 +3251,7 @@ function registerImportCommands(xray) {
3409
3251
  }
3410
3252
 
3411
3253
  // src/commands/xray/plan/add.ts
3412
- function add3(parent) {
3254
+ function add4(parent) {
3413
3255
  const cmd = parent.command("add").description("Add tests and/or executions to a Test Plan").argument("<planKey>", "Test Plan issue key (e.g. PROJ-573)", issueKey).option("--test <keys>", "Comma-separated test issue keys to add (e.g. PROJ-1,PROJ-2)", keyList).option("--execution <keys>", "Comma-separated test execution keys to add (e.g. PROJ-200,PROJ-201)", keyList);
3414
3256
  examples(cmd, [
3415
3257
  "PROJ-573 --test PROJ-1,PROJ-2",
@@ -3428,7 +3270,7 @@ function add3(parent) {
3428
3270
  }
3429
3271
 
3430
3272
  // src/commands/xray/plan/create.ts
3431
- function create9(parent) {
3273
+ function create11(parent) {
3432
3274
  const cmd = parent.command("create").description("Create a new Xray Test Plan issue").requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--summary <text>", "Test Plan summary (title)", text);
3433
3275
  textOrFileOption(cmd, "description", { description: "Test Plan description" });
3434
3276
  examples(cmd, [
@@ -3460,7 +3302,7 @@ function deletePlan(parent) {
3460
3302
  }
3461
3303
 
3462
3304
  // src/commands/xray/plan/list.ts
3463
- function list12(parent) {
3305
+ function list15(parent) {
3464
3306
  const cmd = parent.command("list").description(
3465
3307
  "List Test Plans that contain a given test (inverse view). For the tests inside a plan, use: xray test list --plan <key>. For executions in a plan, use: xray execution list --plan <key>."
3466
3308
  ).requiredOption("--test <key>", "Test issue key whose plans to list (e.g. PROJ-574)", issueKey);
@@ -3473,7 +3315,7 @@ function list12(parent) {
3473
3315
  }
3474
3316
 
3475
3317
  // src/commands/xray/plan/remove.ts
3476
- function remove3(parent) {
3318
+ function remove4(parent) {
3477
3319
  const cmd = parent.command("remove").description("Remove tests and/or executions from a Test Plan").argument("<planKey>", "Test Plan issue key (e.g. PROJ-573)", issueKey).option("--test <keys>", "Comma-separated test issue keys to remove (e.g. PROJ-1,PROJ-2)", keyList).option("--execution <keys>", "Comma-separated test execution keys to remove (e.g. PROJ-200)", keyList);
3478
3320
  examples(cmd, [
3479
3321
  "PROJ-573 --test PROJ-1",
@@ -3515,16 +3357,16 @@ function registerPlanCommands(xray) {
3515
3357
  "add PROJ-573 --test PROJ-1 --execution PROJ-200",
3516
3358
  "list --test PROJ-574"
3517
3359
  ]);
3518
- create9(plan);
3360
+ create11(plan);
3519
3361
  update8(plan);
3520
3362
  deletePlan(plan);
3521
- add3(plan);
3522
- remove3(plan);
3523
- list12(plan);
3363
+ add4(plan);
3364
+ remove4(plan);
3365
+ list15(plan);
3524
3366
  }
3525
3367
 
3526
3368
  // src/commands/xray/precondition/add.ts
3527
- function add4(parent) {
3369
+ function add5(parent) {
3528
3370
  const cmd = parent.command("add").description("Add tests to a Pre-Condition").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to add (e.g. PROJ-1,PROJ-2)", keyList);
3529
3371
  examples(cmd, ["PROJ-50 --test PROJ-1", "PROJ-50 --test PROJ-1,PROJ-2,PROJ-3"]);
3530
3372
  cmd.action(async (preKey, opts) => {
@@ -3535,7 +3377,7 @@ function add4(parent) {
3535
3377
  }
3536
3378
 
3537
3379
  // src/commands/xray/precondition/create.ts
3538
- import { Option as Option11 } from "commander";
3380
+ import { Option as Option10 } from "commander";
3539
3381
 
3540
3382
  // src/utils/xray/fields.ts
3541
3383
  var CONCEPT_SUFFIX = {
@@ -3636,11 +3478,11 @@ function buildPreconditionCustomFields(map, opts) {
3636
3478
  }
3637
3479
 
3638
3480
  // src/commands/xray/precondition/create.ts
3639
- function create10(parent) {
3481
+ function create12(parent) {
3640
3482
  const cmd = parent.command("create").description(
3641
3483
  "Create a new Xray Pre-Condition issue. Note: the Pre-Condition issue type is only available in projects configured for it; it may not be on your project scheme."
3642
3484
  ).requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--summary <text>", "Pre-Condition summary (title)", text).addOption(
3643
- new Option11("--type <type>", "Pre-Condition type").choices(["manual", "generic", "cucumber"]).default("manual")
3485
+ new Option10("--type <type>", "Pre-Condition type").choices(["manual", "generic", "cucumber"]).default("manual")
3644
3486
  );
3645
3487
  textOrFileOption(cmd, "condition", { description: "Pre-condition body / definition text" });
3646
3488
  textOrFileOption(cmd, "description", { description: "Pre-Condition issue description" });
@@ -3687,7 +3529,7 @@ function deletePrecondition(parent) {
3687
3529
  }
3688
3530
 
3689
3531
  // src/commands/xray/precondition/list.ts
3690
- function list13(parent) {
3532
+ function list16(parent) {
3691
3533
  const cmd = parent.command("list").description(
3692
3534
  "List Pre-Conditions that apply to a given test (inverse view). For the forward view (tests covered by a precondition), use: xray precondition add/remove."
3693
3535
  ).requiredOption("--test <key>", "Test issue key whose pre-conditions to list (e.g. PROJ-584)", issueKey);
@@ -3700,7 +3542,7 @@ function list13(parent) {
3700
3542
  }
3701
3543
 
3702
3544
  // src/commands/xray/precondition/remove.ts
3703
- function remove4(parent) {
3545
+ function remove5(parent) {
3704
3546
  const cmd = parent.command("remove").description("Remove tests from a Pre-Condition").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. PROJ-1,PROJ-2)", keyList);
3705
3547
  examples(cmd, ["PROJ-50 --test PROJ-1", "PROJ-50 --test PROJ-1,PROJ-2"]);
3706
3548
  cmd.action(async (preKey, opts) => {
@@ -3711,9 +3553,9 @@ function remove4(parent) {
3711
3553
  }
3712
3554
 
3713
3555
  // src/commands/xray/precondition/update.ts
3714
- import { Option as Option12 } from "commander";
3556
+ import { Option as Option11 } from "commander";
3715
3557
  function update9(parent) {
3716
- const cmd = parent.command("update").description("Update an Xray Pre-Condition issue").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option12("--type <type>", "New Pre-Condition type").choices(["manual", "generic", "cucumber"]));
3558
+ const cmd = parent.command("update").description("Update an Xray Pre-Condition issue").argument("<preKey>", "Pre-Condition issue key (e.g. PROJ-50)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option11("--type <type>", "New Pre-Condition type").choices(["manual", "generic", "cucumber"]));
3717
3559
  textOrFileOption(cmd, "condition", { description: "New pre-condition body / definition text" });
3718
3560
  textOrFileOption(cmd, "description", { description: "New Pre-Condition issue description" });
3719
3561
  examples(cmd, ['PROJ-50 --summary "Updated precondition"', 'PROJ-50 --type generic --condition "apiKey != null"']);
@@ -3761,12 +3603,12 @@ function registerPreconditionCommands(xray) {
3761
3603
  "add PROJ-50 --test PROJ-1,PROJ-2",
3762
3604
  "list --test PROJ-584"
3763
3605
  ]);
3764
- create10(precondition);
3606
+ create12(precondition);
3765
3607
  update9(precondition);
3766
3608
  deletePrecondition(precondition);
3767
- add4(precondition);
3768
- remove4(precondition);
3769
- list13(precondition);
3609
+ add5(precondition);
3610
+ remove5(precondition);
3611
+ list16(precondition);
3770
3612
  }
3771
3613
 
3772
3614
  // src/utils/xray/run-id.ts
@@ -3780,7 +3622,7 @@ async function resolveRunId(client, o) {
3780
3622
  }
3781
3623
 
3782
3624
  // src/commands/xray/run/defect/add.ts
3783
- function add5(parent) {
3625
+ function add6(parent) {
3784
3626
  const cmd = parent.command("add").description("Link defect issues to a test run").argument("[runId]", "Test run id (from run list)", positiveInt).requiredOption("--defect <keys>", "Comma-separated defect issue keys to link", keyList).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
3785
3627
  examples(cmd, ["42 --defect BUG-1", "--execution PROJ-1 --test PROJ-2 --defect BUG-1,BUG-2"]);
3786
3628
  cmd.action(async (runId, opts) => {
@@ -3792,7 +3634,7 @@ function add5(parent) {
3792
3634
  }
3793
3635
 
3794
3636
  // src/commands/xray/run/defect/remove.ts
3795
- function remove5(parent) {
3637
+ function remove6(parent) {
3796
3638
  const cmd = parent.command("remove").description("Unlink defect issues from a test run (per-key, reports failures without aborting)").argument("[runId]", "Test run id (from run list)", positiveInt).requiredOption("--defect <keys>", "Comma-separated defect issue keys to unlink", keyList).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
3797
3639
  examples(cmd, ["42 --defect BUG-1", "--execution PROJ-1 --test PROJ-2 --defect BUG-1,BUG-2"]);
3798
3640
  cmd.action(async (runId, opts) => {
@@ -3829,12 +3671,12 @@ function registerDefectCommands(run) {
3829
3671
  "Manage defects linked to a test run. See also: run update --defect for the one-command fail+link loop."
3830
3672
  );
3831
3673
  examples(defect, ["add --execution PROJ-1 --test PROJ-2 --defect BUG-1", "remove 42 --defect BUG-1"]);
3832
- add5(defect);
3833
- remove5(defect);
3674
+ add6(defect);
3675
+ remove6(defect);
3834
3676
  }
3835
3677
 
3836
3678
  // src/commands/xray/run/evidence/add.ts
3837
- import { readFileSync as readFileSync6 } from "fs";
3679
+ import { readFileSync as readFileSync3 } from "fs";
3838
3680
  import { basename as basename4, extname } from "path";
3839
3681
  var CONTENT_TYPES = {
3840
3682
  ".png": "image/png",
@@ -3852,13 +3694,13 @@ var CONTENT_TYPES = {
3852
3694
  function inferContentType(fp) {
3853
3695
  return CONTENT_TYPES[extname(fp).toLowerCase()] ?? "application/octet-stream";
3854
3696
  }
3855
- function add6(parent) {
3697
+ function add7(parent) {
3856
3698
  const cmd = parent.command("add").description("Attach a file as evidence to a test run").argument("[runId]", "Test run id (from run list)", positiveInt).requiredOption("--file <path>", "Path to the file to attach", filePath).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
3857
3699
  examples(cmd, ["42 --file screenshot.png", "--execution PROJ-1 --test PROJ-2 --file report.pdf"]);
3858
3700
  cmd.action(async (runId, opts) => {
3859
3701
  const client = getClient();
3860
3702
  const id = await resolveRunId(client, { runId, execution: opts.execution, test: opts.test });
3861
- const fileBuf = readFileSync6(opts.file);
3703
+ const fileBuf = readFileSync3(opts.file);
3862
3704
  const filename = basename4(opts.file);
3863
3705
  const contentType = inferContentType(opts.file);
3864
3706
  const data = fileBuf.toString("base64");
@@ -3880,7 +3722,7 @@ function deleteEvidence(parent) {
3880
3722
  }
3881
3723
 
3882
3724
  // src/commands/xray/run/evidence/list.ts
3883
- function list14(parent) {
3725
+ function list17(parent) {
3884
3726
  const cmd = parent.command("list").description("List evidence (attachments) on a test run").argument("[runId]", "Test run id (from run list)", positiveInt).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
3885
3727
  examples(cmd, ["42", "--execution PROJ-1 --test PROJ-2"]);
3886
3728
  cmd.action(async (runId, opts) => {
@@ -3899,13 +3741,13 @@ function registerEvidenceCommands(run) {
3899
3741
  "list 42",
3900
3742
  "delete 42 --evidence-id 7"
3901
3743
  ]);
3902
- add6(evidence);
3903
- list14(evidence);
3744
+ add7(evidence);
3745
+ list17(evidence);
3904
3746
  deleteEvidence(evidence);
3905
3747
  }
3906
3748
 
3907
3749
  // src/commands/xray/run/field/get.ts
3908
- function get8(parent) {
3750
+ function get10(parent) {
3909
3751
  const cmd = parent.command("get").description("Get a custom field value on a test run").argument("[runId]", "Test run id (from run list)", positiveInt).requiredOption("--field-id <id>", "Custom field id (from xray field list)", text).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
3910
3752
  examples(cmd, ["42 --field-id customfield_10100", "--execution PROJ-1 --test PROJ-2 --field-id customfield_10100"]);
3911
3753
  cmd.action(async (runId, opts) => {
@@ -3937,12 +3779,12 @@ function set(parent) {
3937
3779
  function registerRunFieldCommands(run) {
3938
3780
  const field = run.command("field").description("Get or set Xray custom field values on a test run");
3939
3781
  examples(field, ["get 42 --field-id customfield_10100", 'set 42 --field-id customfield_10100 --value "approved"']);
3940
- get8(field);
3782
+ get10(field);
3941
3783
  set(field);
3942
3784
  }
3943
3785
 
3944
3786
  // src/commands/xray/run/get.ts
3945
- function get9(parent) {
3787
+ function get11(parent) {
3946
3788
  const cmd = parent.command("get").description("Get a test run by id or by execution + test key").argument("[runId]", "Test run id (from run list)", positiveInt).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
3947
3789
  examples(cmd, ["42", "--execution PROJ-1 --test PROJ-2"]);
3948
3790
  cmd.action(async (runId, opts) => {
@@ -3954,7 +3796,7 @@ function get9(parent) {
3954
3796
  }
3955
3797
 
3956
3798
  // src/commands/xray/run/list.ts
3957
- function list15(parent) {
3799
+ function list18(parent) {
3958
3800
  const cmd = parent.command("list").description("List test runs for a test execution (CLI-side slice with --limit/--start)").requiredOption("--execution <key>", "Test execution issue key", issueKey).option("--limit <number>", "Max results to return (1-200)", intInRange(1, 200), 50).option("--start <number>", "Starting index for pagination", nonNegativeInt);
3959
3801
  examples(cmd, ["--execution PROJ-1", "--execution PROJ-1 --limit 10", "--execution PROJ-1 --limit 10 --start 10"]);
3960
3802
  cmd.action(async (opts) => {
@@ -3967,7 +3809,7 @@ function list15(parent) {
3967
3809
  }
3968
3810
 
3969
3811
  // src/commands/xray/run/step/list.ts
3970
- function list16(parent) {
3812
+ function list19(parent) {
3971
3813
  const cmd = parent.command("list").description("List step results for a test run").argument("[runId]", "Test run id (from run list)", positiveInt).option("--execution <key>", "Test execution issue key (used with --test to resolve run id)", issueKey).option("--test <key>", "Test issue key (used with --execution to resolve run id)", issueKey);
3972
3814
  examples(cmd, ["42", "--execution PROJ-1 --test PROJ-2"]);
3973
3815
  cmd.action(async (runId, opts) => {
@@ -4014,7 +3856,7 @@ function update10(parent) {
4014
3856
  function registerRunStepCommands(run) {
4015
3857
  const step = run.command("step").description("Manage step results within a test run. See also: xray step for test step definitions.");
4016
3858
  examples(step, ["list --execution PROJ-1 --test PROJ-2", "update 42 --step-id 1 --status PASS"]);
4017
- list16(step);
3859
+ list19(step);
4018
3860
  update10(step);
4019
3861
  }
4020
3862
 
@@ -4060,8 +3902,8 @@ function registerRunCommands(xray) {
4060
3902
  "step list --execution PROJ-1 --test PROJ-2",
4061
3903
  "field get 42 --field-id customfield_10100"
4062
3904
  ]);
4063
- get9(run);
4064
- list15(run);
3905
+ get11(run);
3906
+ list18(run);
4065
3907
  update11(run);
4066
3908
  registerDefectCommands(run);
4067
3909
  registerEvidenceCommands(run);
@@ -4070,7 +3912,7 @@ function registerRunCommands(xray) {
4070
3912
  }
4071
3913
 
4072
3914
  // src/commands/xray/status/list.ts
4073
- function list17(parent) {
3915
+ function list20(parent) {
4074
3916
  const cmd = parent.command("list").description("List configured Xray test (run) statuses").option("--step", "List test-step statuses instead of run statuses");
4075
3917
  examples(cmd, ["", ["--step", "step statuses"]]);
4076
3918
  cmd.action(async (opts) => {
@@ -4084,11 +3926,11 @@ function list17(parent) {
4084
3926
  function registerStatusCommands(xray) {
4085
3927
  const status = xray.command("status").description("Xray status discovery");
4086
3928
  examples(status, ["list", ["list --step", "step statuses"]]);
4087
- list17(status);
3929
+ list20(status);
4088
3930
  }
4089
3931
 
4090
3932
  // src/commands/xray/step/add.ts
4091
- function add7(parent) {
3933
+ function add8(parent) {
4092
3934
  const cmd = parent.command("add").description("Add a manual test step to a Test issue").requiredOption("--test <key>", "Test issue key (e.g. PROJ-584)", issueKey);
4093
3935
  textOrFileOption(cmd, "action", { description: "Step action text (required: provide --action or --action-file)" });
4094
3936
  textOrFileOption(cmd, "data", { description: "Step test data text" });
@@ -4121,7 +3963,7 @@ function deleteStep(parent) {
4121
3963
  }
4122
3964
 
4123
3965
  // src/commands/xray/step/list.ts
4124
- function list18(parent) {
3966
+ function list21(parent) {
4125
3967
  const cmd = parent.command("list").description("List manual test steps for a Test issue").requiredOption("--test <key>", "Test issue key (e.g. PROJ-584)", issueKey);
4126
3968
  examples(cmd, ["--test PROJ-584"]);
4127
3969
  cmd.action(async (opts) => {
@@ -4164,23 +4006,23 @@ function registerStepCommands(xray) {
4164
4006
  "Manage manual test steps for a Test issue (scope: --test). Steps are ordered by insertion; use step list to see current ids."
4165
4007
  );
4166
4008
  examples(step, ['add --test PROJ-584 --action "Open login" --result "Form shows"', "list --test PROJ-584"]);
4167
- list18(step);
4168
- add7(step);
4009
+ list21(step);
4010
+ add8(step);
4169
4011
  update12(step);
4170
4012
  deleteStep(step);
4171
4013
  }
4172
4014
 
4173
4015
  // src/commands/xray/test/create.ts
4174
- import { Option as Option13 } from "commander";
4016
+ import { Option as Option12 } from "commander";
4175
4017
  import { z as z5 } from "zod";
4176
4018
  var stepsSchema = z5.array(
4177
4019
  z5.object({ action: z5.string(), data: z5.string().optional(), result: z5.string().optional() })
4178
4020
  );
4179
- function create11(parent) {
4021
+ function create13(parent) {
4180
4022
  const cmd = parent.command("create").description("Create a new Xray Test issue").requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--summary <text>", "Test summary (title)", text).addOption(
4181
- new Option13("--type <type>", "Test type (when cucumber: also pass --gherkin; when generic: --definition)").choices(["manual", "cucumber", "generic"]).default("manual")
4023
+ new Option12("--type <type>", "Test type (when cucumber: also pass --gherkin; when generic: --definition)").choices(["manual", "cucumber", "generic"]).default("manual")
4182
4024
  ).addOption(
4183
- new Option13("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
4025
+ new Option12("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
4184
4026
  "scenario",
4185
4027
  "scenario-outline"
4186
4028
  ])
@@ -4265,7 +4107,7 @@ function deleteTest(parent) {
4265
4107
  }
4266
4108
 
4267
4109
  // src/commands/xray/test/get.ts
4268
- function get10(parent) {
4110
+ function get12(parent) {
4269
4111
  const cmd = parent.command("get").description("Get one or more Xray test issues by key").argument("<testKey...>", "One or more test issue keys (e.g. PROJ-584)", listOf(issueKey));
4270
4112
  examples(cmd, ["PROJ-584", "PROJ-584 PROJ-585"]);
4271
4113
  cmd.action(async (testKeys) => {
@@ -4276,7 +4118,7 @@ function get10(parent) {
4276
4118
  }
4277
4119
 
4278
4120
  // src/commands/xray/test/list.ts
4279
- function list19(parent) {
4121
+ function list22(parent) {
4280
4122
  const cmd = parent.command("list").description(
4281
4123
  "List Xray Test issues by scope: all tests in a project, or tests belonging to a set, plan, execution, precondition, or folder. Note: use `jiradc issue search` for richer JQL when scoping by project."
4282
4124
  ).option("--project <text>", "Project key \u2014 list all tests in the project (or companion to --folder)", text).option("--set <key>", "Test Set issue key \u2014 list tests in this set", issueKey).option("--plan <key>", "Test Plan issue key \u2014 list tests in this plan", issueKey).option("--execution <key>", "Test Execution issue key \u2014 list tests in this execution", issueKey).option("--precondition <key>", "Precondition issue key \u2014 list tests with this precondition", issueKey).option("--folder <id>", "Folder id \u2014 list tests in this folder (requires --project)", positiveInt).option("--limit <number>", "Max results (1-50, applies to --project scope)", intInRange(1, 50), 25).option("--start <number>", "Starting index for pagination (applies to --project scope)", nonNegativeInt);
@@ -4350,10 +4192,10 @@ function list19(parent) {
4350
4192
  }
4351
4193
 
4352
4194
  // src/commands/xray/test/update.ts
4353
- import { Option as Option14 } from "commander";
4195
+ import { Option as Option13 } from "commander";
4354
4196
  function update13(parent) {
4355
- const cmd = parent.command("update").description("Update an Xray Test issue").argument("<testKey>", "Test issue key (e.g. PROJ-584)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option14("--type <type>", "Test type").choices(["manual", "cucumber", "generic"])).addOption(
4356
- new Option14("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
4197
+ const cmd = parent.command("update").description("Update an Xray Test issue").argument("<testKey>", "Test issue key (e.g. PROJ-584)", issueKey).option("--summary <text>", "New summary (title)", text).addOption(new Option13("--type <type>", "Test type").choices(["manual", "cucumber", "generic"])).addOption(
4198
+ new Option13("--cucumber-type <cucumberType>", "Cucumber scenario type (required when --type cucumber)").choices([
4357
4199
  "scenario",
4358
4200
  "scenario-outline"
4359
4201
  ])
@@ -4418,15 +4260,15 @@ function update13(parent) {
4418
4260
  function registerTestCommands(xray) {
4419
4261
  const test = xray.command("test").description("Xray Test issue management (CRUD)");
4420
4262
  examples(test, ["get PROJ-584", 'create --project PROJ --summary "Login" --type manual']);
4421
- get10(test);
4422
- create11(test);
4263
+ get12(test);
4264
+ create13(test);
4423
4265
  update13(test);
4424
4266
  deleteTest(test);
4425
- list19(test);
4267
+ list22(test);
4426
4268
  }
4427
4269
 
4428
4270
  // src/commands/xray/testset/add.ts
4429
- function add8(parent) {
4271
+ function add9(parent) {
4430
4272
  const cmd = parent.command("add").description("Add tests to a Test Set").argument("<setKey>", "Test Set issue key (e.g. PROJ-100)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to add (e.g. PROJ-1,PROJ-2)", keyList);
4431
4273
  examples(cmd, ["PROJ-100 --test PROJ-1", "PROJ-100 --test PROJ-1,PROJ-2,PROJ-3"]);
4432
4274
  cmd.action(async (setKey, opts) => {
@@ -4437,7 +4279,7 @@ function add8(parent) {
4437
4279
  }
4438
4280
 
4439
4281
  // src/commands/xray/testset/create.ts
4440
- function create12(parent) {
4282
+ function create14(parent) {
4441
4283
  const cmd = parent.command("create").description("Create a new Xray Test Set issue").requiredOption("--project <key>", "Project key (e.g. PROJ)", text).requiredOption("--summary <text>", "Test Set summary (title)", text);
4442
4284
  textOrFileOption(cmd, "description", { description: "Test Set description" });
4443
4285
  examples(cmd, [
@@ -4469,7 +4311,7 @@ function deleteTestset(parent) {
4469
4311
  }
4470
4312
 
4471
4313
  // src/commands/xray/testset/list.ts
4472
- function list20(parent) {
4314
+ function list23(parent) {
4473
4315
  const cmd = parent.command("list").description(
4474
4316
  "List Test Sets that contain a given test (inverse view). For the forward view (tests inside a set), use: xray test list --set <key>"
4475
4317
  ).requiredOption("--test <key>", "Test issue key whose sets to list (e.g. PROJ-584)", issueKey);
@@ -4482,7 +4324,7 @@ function list20(parent) {
4482
4324
  }
4483
4325
 
4484
4326
  // src/commands/xray/testset/remove.ts
4485
- function remove6(parent) {
4327
+ function remove7(parent) {
4486
4328
  const cmd = parent.command("remove").description("Remove tests from a Test Set").argument("<setKey>", "Test Set issue key (e.g. PROJ-100)", issueKey).requiredOption("--test <keys>", "Comma-separated test issue keys to remove (e.g. PROJ-1,PROJ-2)", keyList);
4487
4329
  examples(cmd, ["PROJ-100 --test PROJ-1", "PROJ-100 --test PROJ-1,PROJ-2"]);
4488
4330
  cmd.action(async (setKey, opts) => {
@@ -4511,12 +4353,12 @@ function registerTestsetCommands(xray) {
4511
4353
  "Xray Test Set issue management (CRUD, membership). To list tests inside a set, use: xray test list --set <key>"
4512
4354
  );
4513
4355
  examples(testset, ['create --project PROJ --summary "Smoke tests"', "add PROJ-100 --test PROJ-1,PROJ-2"]);
4514
- create12(testset);
4356
+ create14(testset);
4515
4357
  update14(testset);
4516
4358
  deleteTestset(testset);
4517
- add8(testset);
4518
- remove6(testset);
4519
- list20(testset);
4359
+ add9(testset);
4360
+ remove7(testset);
4361
+ list23(testset);
4520
4362
  }
4521
4363
 
4522
4364
  // src/commands/xray/index.ts
@@ -4532,7 +4374,7 @@ function registerXrayCommands(program) {
4532
4374
  registerPlanCommands(xray);
4533
4375
  registerStepCommands(xray);
4534
4376
  registerRunCommands(xray);
4535
- registerFolderCommands(xray);
4377
+ registerFolderCommands2(xray);
4536
4378
  registerImportCommands(xray);
4537
4379
  registerExportCommands(xray);
4538
4380
  }
@@ -4579,6 +4421,7 @@ ${styleText("bold", "Examples:")}
4579
4421
  registerFieldCommands(program);
4580
4422
  registerUserCommands(program);
4581
4423
  registerTokenCommands(program);
4424
+ registerStructureCommands(program);
4582
4425
  registerXrayCommands(program);
4583
4426
  return program;
4584
4427
  }