jiradc-cli 1.0.39 → 1.0.41
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/README.md +24 -0
- package/dist/chunk-3GFKV36K.js +1133 -0
- package/dist/dist-JXPWCMQP.js +102 -0
- package/dist/index.js +640 -818
- package/package.json +4 -4
|
@@ -0,0 +1,1133 @@
|
|
|
1
|
+
// ../../cli-utils/dist/cache.js
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
var DEFAULT_TTL = 36e5;
|
|
6
|
+
function getCacheDir(name) {
|
|
7
|
+
return join(homedir(), ".cache", name);
|
|
8
|
+
}
|
|
9
|
+
function getCachePath(name, key) {
|
|
10
|
+
return join(getCacheDir(name), `${key}.json`);
|
|
11
|
+
}
|
|
12
|
+
function cacheGet(options, key) {
|
|
13
|
+
const ttl = options.ttl ?? DEFAULT_TTL;
|
|
14
|
+
const path = getCachePath(options.name, key);
|
|
15
|
+
try {
|
|
16
|
+
const stat = statSync(path);
|
|
17
|
+
if (Date.now() - stat.mtimeMs > ttl)
|
|
18
|
+
return null;
|
|
19
|
+
const raw = readFileSync(path, "utf-8");
|
|
20
|
+
const entry = JSON.parse(raw);
|
|
21
|
+
return entry.data;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function cacheSet(options, key, data) {
|
|
27
|
+
const dir = getCacheDir(options.name);
|
|
28
|
+
const path = getCachePath(options.name, key);
|
|
29
|
+
const entry = { data, timestamp: Date.now() };
|
|
30
|
+
try {
|
|
31
|
+
mkdirSync(dir, { recursive: true });
|
|
32
|
+
writeFileSync(path, JSON.stringify(entry));
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function cacheGetOrFetch(options, key, fetcher) {
|
|
37
|
+
const cached = cacheGet(options, key);
|
|
38
|
+
if (cached !== null)
|
|
39
|
+
return cached;
|
|
40
|
+
const data = await fetcher();
|
|
41
|
+
cacheSet(options, key, data);
|
|
42
|
+
return data;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ../../cli-utils/dist/bootstrap.js
|
|
46
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
47
|
+
import { dirname, join as join2 } from "path";
|
|
48
|
+
import { fileURLToPath } from "url";
|
|
49
|
+
function readPackageVersion(importMetaUrl) {
|
|
50
|
+
try {
|
|
51
|
+
const here = dirname(fileURLToPath(importMetaUrl));
|
|
52
|
+
const pkg = JSON.parse(readFileSync2(join2(here, "..", "package.json"), "utf-8"));
|
|
53
|
+
return pkg.version ?? "0.0.0";
|
|
54
|
+
} catch {
|
|
55
|
+
return "0.0.0";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ../../cli-utils/dist/validators.js
|
|
60
|
+
import { InvalidArgumentError } from "commander";
|
|
61
|
+
function intInRange(min, max) {
|
|
62
|
+
return (raw) => {
|
|
63
|
+
const n = parseInt(raw, 10);
|
|
64
|
+
if (Number.isNaN(n) || !Number.isFinite(n)) {
|
|
65
|
+
throw new InvalidArgumentError("Must be an integer.");
|
|
66
|
+
}
|
|
67
|
+
if (n < min || n > max) {
|
|
68
|
+
throw new InvalidArgumentError(`Must be between ${min} and ${max}.`);
|
|
69
|
+
}
|
|
70
|
+
return n;
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function nonNegativeInt(raw) {
|
|
74
|
+
const n = parseInt(raw, 10);
|
|
75
|
+
if (Number.isNaN(n) || n < 0) {
|
|
76
|
+
throw new InvalidArgumentError("Must be a non-negative integer.");
|
|
77
|
+
}
|
|
78
|
+
return n;
|
|
79
|
+
}
|
|
80
|
+
function positiveInt(raw) {
|
|
81
|
+
const n = parseInt(raw, 10);
|
|
82
|
+
if (Number.isNaN(n) || n < 1) {
|
|
83
|
+
throw new InvalidArgumentError("Must be a positive integer.");
|
|
84
|
+
}
|
|
85
|
+
return n;
|
|
86
|
+
}
|
|
87
|
+
function enumArg(choices) {
|
|
88
|
+
return (raw) => {
|
|
89
|
+
if (!choices.includes(raw)) {
|
|
90
|
+
throw new InvalidArgumentError(`Must be one of: ${choices.join(", ")}`);
|
|
91
|
+
}
|
|
92
|
+
return raw;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function listOf(item) {
|
|
96
|
+
return (raw, previous) => [...Array.isArray(previous) ? previous : [], item(raw)];
|
|
97
|
+
}
|
|
98
|
+
function text(raw) {
|
|
99
|
+
return raw;
|
|
100
|
+
}
|
|
101
|
+
function nonEmpty(raw) {
|
|
102
|
+
if (raw.trim() === "") {
|
|
103
|
+
throw new InvalidArgumentError("Must not be empty.");
|
|
104
|
+
}
|
|
105
|
+
return raw;
|
|
106
|
+
}
|
|
107
|
+
function integer(raw) {
|
|
108
|
+
if (!/^[+-]?\d+$/.test(raw.trim())) {
|
|
109
|
+
throw new InvalidArgumentError("Must be an integer.");
|
|
110
|
+
}
|
|
111
|
+
return Number(raw);
|
|
112
|
+
}
|
|
113
|
+
function finiteNumber(raw) {
|
|
114
|
+
if (!/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/.test(raw.trim())) {
|
|
115
|
+
throw new InvalidArgumentError("Must be a finite number.");
|
|
116
|
+
}
|
|
117
|
+
return Number(raw);
|
|
118
|
+
}
|
|
119
|
+
function date(raw) {
|
|
120
|
+
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(raw);
|
|
121
|
+
if (!m) {
|
|
122
|
+
throw new InvalidArgumentError("Must be a date in YYYY-MM-DD format.");
|
|
123
|
+
}
|
|
124
|
+
const [, y, mo, d] = m;
|
|
125
|
+
const year = Number(y);
|
|
126
|
+
const month = Number(mo);
|
|
127
|
+
const day = Number(d);
|
|
128
|
+
const dt = new Date(Date.UTC(year, month - 1, day));
|
|
129
|
+
if (dt.getUTCFullYear() !== year || dt.getUTCMonth() !== month - 1 || dt.getUTCDate() !== day) {
|
|
130
|
+
throw new InvalidArgumentError("Not a real calendar date.");
|
|
131
|
+
}
|
|
132
|
+
return raw;
|
|
133
|
+
}
|
|
134
|
+
function dateTime(raw) {
|
|
135
|
+
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(raw) || Number.isNaN(Date.parse(raw))) {
|
|
136
|
+
throw new InvalidArgumentError("Must be an ISO 8601 date-time, e.g. 2026-06-08T14:30:00Z.");
|
|
137
|
+
}
|
|
138
|
+
return raw;
|
|
139
|
+
}
|
|
140
|
+
function uuid(raw) {
|
|
141
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(raw.trim())) {
|
|
142
|
+
throw new InvalidArgumentError("Must be a GUID, e.g. 1b2c3d4e-5f60-4a7b-8c9d-0e1f2a3b4c5d.");
|
|
143
|
+
}
|
|
144
|
+
return raw.trim().toLowerCase();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// ../../cli-utils/dist/json.js
|
|
148
|
+
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
149
|
+
function jsonShape(schema) {
|
|
150
|
+
return (raw) => {
|
|
151
|
+
let parsed;
|
|
152
|
+
try {
|
|
153
|
+
parsed = JSON.parse(raw);
|
|
154
|
+
} catch {
|
|
155
|
+
throw new InvalidArgumentError2("Must be valid JSON.");
|
|
156
|
+
}
|
|
157
|
+
const result = schema.safeParse(parsed);
|
|
158
|
+
if (!result.success) {
|
|
159
|
+
const issues = result.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
160
|
+
throw new InvalidArgumentError2(`Invalid JSON shape \u2014 ${issues}`);
|
|
161
|
+
}
|
|
162
|
+
return result.data;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ../../cli-utils/dist/text-or-file.js
|
|
167
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
168
|
+
import { InvalidArgumentError as InvalidArgumentError3, Option } from "commander";
|
|
169
|
+
var STDIN_REF = "-";
|
|
170
|
+
function rejectStdinSentinel(value) {
|
|
171
|
+
if (value === "@-" || value === STDIN_REF) {
|
|
172
|
+
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.`);
|
|
173
|
+
}
|
|
174
|
+
return value;
|
|
175
|
+
}
|
|
176
|
+
function readFileOrStdin(ref) {
|
|
177
|
+
if (ref === STDIN_REF) {
|
|
178
|
+
if (process.stdin.isTTY) {
|
|
179
|
+
throw new InvalidArgumentError3('"--\u2026-file -" reads stdin, but stdin is a terminal (nothing piped).');
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
return readFileSync3(0, "utf8");
|
|
183
|
+
} catch (err) {
|
|
184
|
+
throw new InvalidArgumentError3(`failed to read stdin for "--\u2026-file -": ${err.message}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
return readFileSync3(ref, "utf8");
|
|
189
|
+
} catch (err) {
|
|
190
|
+
const e = err;
|
|
191
|
+
if (e.code === "ENOENT") {
|
|
192
|
+
throw new InvalidArgumentError3(`file not found: "${ref}".`);
|
|
193
|
+
}
|
|
194
|
+
throw new InvalidArgumentError3(`failed to read file "${ref}": ${e.message}`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function textOrFileOption(cmd, name, opts = {}) {
|
|
198
|
+
const label = `${name.charAt(0).toUpperCase()}${name.slice(1)} content`;
|
|
199
|
+
cmd.option(`--${name} <text>`, opts.description ?? label, rejectStdinSentinel);
|
|
200
|
+
cmd.addOption(new Option(`--${name}-file <path>`, `Read --${name} from a file, or "-" for stdin (mutually exclusive with --${name})`).conflicts(name).argParser(text));
|
|
201
|
+
return cmd;
|
|
202
|
+
}
|
|
203
|
+
function resolveTextOrFile(opts, name, { required = true } = {}) {
|
|
204
|
+
const record = opts;
|
|
205
|
+
const literal = record[name];
|
|
206
|
+
const ref = record[`${name}File`];
|
|
207
|
+
if (literal !== void 0 && ref !== void 0) {
|
|
208
|
+
throw new InvalidArgumentError3(`--${name} and --${name}-file are mutually exclusive; provide only one.`);
|
|
209
|
+
}
|
|
210
|
+
if (ref !== void 0) {
|
|
211
|
+
return readFileOrStdin(ref);
|
|
212
|
+
}
|
|
213
|
+
if (literal === void 0 && required) {
|
|
214
|
+
throw new InvalidArgumentError3(`a ${name} is required: provide --${name} <text> or --${name}-file <path>.`);
|
|
215
|
+
}
|
|
216
|
+
return literal;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ../../cli-utils/dist/registry.js
|
|
220
|
+
var SCOPE_SPECS = {
|
|
221
|
+
project: { flag: "--project", placeholder: "<key>", description: "Project key", numeric: false },
|
|
222
|
+
repo: { flag: "--repo", placeholder: "<slug>", description: "Repository slug", numeric: false },
|
|
223
|
+
space: { flag: "--space", placeholder: "<key>", description: "Confluence space key", numeric: false },
|
|
224
|
+
board: { flag: "--board-id", placeholder: "<id>", description: "Agile board id", numeric: true },
|
|
225
|
+
drive: { flag: "--drive-id", placeholder: "<id>", description: "Graph drive id", numeric: false },
|
|
226
|
+
chat: { flag: "--chat-id", placeholder: "<id>", description: "Teams chat id", numeric: false },
|
|
227
|
+
page: { flag: "--page-id", placeholder: "<id>", description: "Confluence page id", numeric: false },
|
|
228
|
+
// Structure takes an id *or* a name: an agent is normally told "BTI Delivery",
|
|
229
|
+
// and forcing a lookup before every call is the round trip these CLIs remove.
|
|
230
|
+
structure: { flag: "--structure", placeholder: "<idOrName>", description: "Structure id or name", numeric: false }
|
|
231
|
+
};
|
|
232
|
+
var SUB_ENTITY_ID_NUMERIC = {
|
|
233
|
+
comment: true,
|
|
234
|
+
worklog: true,
|
|
235
|
+
attachment: true,
|
|
236
|
+
request: true,
|
|
237
|
+
type: true,
|
|
238
|
+
employee: true,
|
|
239
|
+
message: false,
|
|
240
|
+
membership: false
|
|
241
|
+
};
|
|
242
|
+
var BANNED_FLAGS = {
|
|
243
|
+
// Pagination (§4.7): --limit / --start, plus --all to collect every match.
|
|
244
|
+
// The original auto-pagination ban was later amended — the thin-client
|
|
245
|
+
// principle it rested on governs the client libraries, not the CLIs.
|
|
246
|
+
max: "use --limit",
|
|
247
|
+
"max-results": "use --limit",
|
|
248
|
+
"page-size": "use --limit",
|
|
249
|
+
"page-all": "use --all to collect every match, or --limit + --start to page",
|
|
250
|
+
page: "use --start for pagination, or --page-id for a page scope",
|
|
251
|
+
offset: "use --start",
|
|
252
|
+
skip: "use --start",
|
|
253
|
+
"start-at": "use --start",
|
|
254
|
+
// Prose payload (§4.6): prose is always --body, never a positional or alias.
|
|
255
|
+
text: "use --body",
|
|
256
|
+
content: "use --body",
|
|
257
|
+
message: "use --body for prose, or --message-id for a message id",
|
|
258
|
+
// Reactions (§7.5 Pattern C) unify on --emoji.
|
|
259
|
+
emoticon: "use --emoji"
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// ../../cli-utils/dist/builders.js
|
|
263
|
+
function defineOption(cmd, flags, description, parser, mandatory) {
|
|
264
|
+
if (mandatory) {
|
|
265
|
+
return parser ? cmd.requiredOption(flags, description, parser) : cmd.requiredOption(flags, description);
|
|
266
|
+
}
|
|
267
|
+
return parser ? cmd.option(flags, description, parser) : cmd.option(flags, description);
|
|
268
|
+
}
|
|
269
|
+
function scopeFlag(cmd, concept, opts) {
|
|
270
|
+
const spec = SCOPE_SPECS[concept];
|
|
271
|
+
return defineOption(cmd, `${spec.flag} ${spec.placeholder}`, spec.description, spec.numeric ? positiveInt : text, opts.mandatory);
|
|
272
|
+
}
|
|
273
|
+
function subjectArg(cmd, name, opts = {}) {
|
|
274
|
+
const inner = opts.variadic ? `${name}...` : name;
|
|
275
|
+
const token = opts.optional ? `[${inner}]` : `<${inner}>`;
|
|
276
|
+
return opts.parser ? cmd.argument(token, opts.description ?? "", opts.parser) : cmd.argument(token, opts.description ?? "");
|
|
277
|
+
}
|
|
278
|
+
var projectOption = (cmd, opts = {}) => scopeFlag(cmd, "project", opts);
|
|
279
|
+
var repoOption = (cmd, opts = {}) => scopeFlag(cmd, "repo", opts);
|
|
280
|
+
var spaceOption = (cmd, opts = {}) => scopeFlag(cmd, "space", opts);
|
|
281
|
+
function scopeIdOption(cmd, concept, opts = {}) {
|
|
282
|
+
return scopeFlag(cmd, concept, opts);
|
|
283
|
+
}
|
|
284
|
+
function subEntityOption(cmd, entity, opts = {}) {
|
|
285
|
+
const numeric = opts.numeric ?? SUB_ENTITY_ID_NUMERIC[entity] ?? true;
|
|
286
|
+
const description = `${entity.charAt(0).toUpperCase()}${entity.slice(1)} id`;
|
|
287
|
+
return defineOption(cmd, `--${entity}-id <id>`, description, numeric ? positiveInt : text, opts.mandatory);
|
|
288
|
+
}
|
|
289
|
+
function paginationOptions(cmd, opts = {}) {
|
|
290
|
+
const max = opts.maxLimit ?? 1e3;
|
|
291
|
+
const def = opts.defaultLimit ?? 25;
|
|
292
|
+
cmd.option("--limit <n>", `Max results per page (1-${max})`, intInRange(1, max), def);
|
|
293
|
+
if (opts.startIsToken) {
|
|
294
|
+
cmd.option("--start <indexOrToken>", "Pagination cursor: 0-based offset or opaque next-page token", text);
|
|
295
|
+
} else {
|
|
296
|
+
cmd.option("--start <index>", "Pagination offset (0-based)", nonNegativeInt);
|
|
297
|
+
}
|
|
298
|
+
return cmd;
|
|
299
|
+
}
|
|
300
|
+
function bodyOption(cmd, opts = {}) {
|
|
301
|
+
return textOrFileOption(cmd, "body", { description: "Prose body content", ...opts });
|
|
302
|
+
}
|
|
303
|
+
function commentOption(cmd, opts = {}) {
|
|
304
|
+
return textOrFileOption(cmd, "comment", { description: "Optional note attached to the action", ...opts });
|
|
305
|
+
}
|
|
306
|
+
function formatOption(cmd, choices, def) {
|
|
307
|
+
const option = cmd.createOption("--format <format>", "Output/content format").choices(choices);
|
|
308
|
+
if (def !== void 0)
|
|
309
|
+
option.default(def);
|
|
310
|
+
return cmd.addOption(option);
|
|
311
|
+
}
|
|
312
|
+
function fieldsOption(cmd) {
|
|
313
|
+
return cmd.option("--fields <csv>", "Comma-separated list of fields to return", text);
|
|
314
|
+
}
|
|
315
|
+
var EXAMPLES = /* @__PURE__ */ new WeakSet();
|
|
316
|
+
function commandPath(cmd) {
|
|
317
|
+
const parts = [];
|
|
318
|
+
for (let c = cmd; c; c = c.parent)
|
|
319
|
+
parts.unshift(c.name());
|
|
320
|
+
return parts.join(" ");
|
|
321
|
+
}
|
|
322
|
+
function exampleLine(path, entry) {
|
|
323
|
+
if (typeof entry === "string")
|
|
324
|
+
return entry ? ` ${path} ${entry}` : ` ${path}`;
|
|
325
|
+
if ("raw" in entry)
|
|
326
|
+
return ` ${entry.raw}`;
|
|
327
|
+
const [args, comment] = entry;
|
|
328
|
+
const line = args ? `${path} ${args}` : path;
|
|
329
|
+
return ` ${line} # ${comment}`;
|
|
330
|
+
}
|
|
331
|
+
function examples(cmd, entries) {
|
|
332
|
+
EXAMPLES.add(cmd);
|
|
333
|
+
return cmd.addHelpText("after", () => {
|
|
334
|
+
const path = commandPath(cmd);
|
|
335
|
+
return `
|
|
336
|
+
Examples:
|
|
337
|
+
${entries.map((e) => exampleLine(path, e)).join("\n")}`;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
function hasExamples(cmd) {
|
|
341
|
+
return EXAMPLES.has(cmd);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ../../cli-utils/dist/errors.js
|
|
345
|
+
import { CommanderError } from "commander";
|
|
346
|
+
var EXIT = {
|
|
347
|
+
SUCCESS: 0,
|
|
348
|
+
GENERIC: 1,
|
|
349
|
+
USAGE: 2,
|
|
350
|
+
NOT_FOUND: 3,
|
|
351
|
+
FORBIDDEN: 4,
|
|
352
|
+
CONFLICT: 5,
|
|
353
|
+
AUTH: 6
|
|
354
|
+
};
|
|
355
|
+
var CliAuthError = class extends Error {
|
|
356
|
+
constructor(message) {
|
|
357
|
+
super(message);
|
|
358
|
+
this.name = "CliAuthError";
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
var NOT_FOUND_RECOVERY = "Verify the id/key, then check your access.";
|
|
362
|
+
var NOT_FOUND_MESSAGE = "Not found: it may not exist, or you may not have permission to see it";
|
|
363
|
+
var USAGE_RECOVERY = "Check the command syntax and flags; run the command with --help.";
|
|
364
|
+
var CliNotFoundError = class extends Error {
|
|
365
|
+
recovery;
|
|
366
|
+
constructor(message, recovery = NOT_FOUND_RECOVERY) {
|
|
367
|
+
super(message);
|
|
368
|
+
this.name = "CliNotFoundError";
|
|
369
|
+
this.recovery = recovery;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
var CliUsageError = class extends Error {
|
|
373
|
+
recovery;
|
|
374
|
+
detail;
|
|
375
|
+
constructor(message, recovery = USAGE_RECOVERY, detail) {
|
|
376
|
+
super(message);
|
|
377
|
+
this.name = "CliUsageError";
|
|
378
|
+
this.recovery = recovery;
|
|
379
|
+
this.detail = detail;
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
var SubcommandRequiredError = class extends Error {
|
|
383
|
+
subcommands;
|
|
384
|
+
constructor(commandPath3, subcommands) {
|
|
385
|
+
super(`'${commandPath3}' requires a subcommand`);
|
|
386
|
+
this.name = "SubcommandRequiredError";
|
|
387
|
+
this.subcommands = subcommands;
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
var UnknownSubcommandError = class extends Error {
|
|
391
|
+
subcommands;
|
|
392
|
+
suggestion;
|
|
393
|
+
constructor(commandPath3, token, subcommands, suggestion) {
|
|
394
|
+
super(`'${token}' is not a subcommand of '${commandPath3}'`);
|
|
395
|
+
this.name = "UnknownSubcommandError";
|
|
396
|
+
this.subcommands = subcommands;
|
|
397
|
+
this.suggestion = suggestion;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
var TYPE_EXIT = {
|
|
401
|
+
usage: EXIT.USAGE,
|
|
402
|
+
not_found: EXIT.NOT_FOUND,
|
|
403
|
+
forbidden: EXIT.FORBIDDEN,
|
|
404
|
+
conflict: EXIT.CONFLICT,
|
|
405
|
+
auth: EXIT.AUTH,
|
|
406
|
+
rate_limited: EXIT.GENERIC,
|
|
407
|
+
server: EXIT.GENERIC,
|
|
408
|
+
timeout: EXIT.GENERIC,
|
|
409
|
+
network: EXIT.GENERIC,
|
|
410
|
+
unknown: EXIT.GENERIC
|
|
411
|
+
};
|
|
412
|
+
var TRANSPORT_CODES = {
|
|
413
|
+
/** Our own timeout fired: connected (or tried to), no response in time. */
|
|
414
|
+
readTimeout: ["ECONNABORTED"],
|
|
415
|
+
/** The OS gave up establishing the connection. */
|
|
416
|
+
connectTimeout: ["ETIMEDOUT"],
|
|
417
|
+
/** No such host, or DNS itself is unavailable. */
|
|
418
|
+
unresolved: ["ENOTFOUND", "EAI_AGAIN"],
|
|
419
|
+
/** Host is there, nothing is listening on that port. */
|
|
420
|
+
refused: ["ECONNREFUSED"],
|
|
421
|
+
/** Established, then died mid-flight. */
|
|
422
|
+
dropped: ["ECONNRESET", "EPIPE"]
|
|
423
|
+
};
|
|
424
|
+
function humanMs(ms) {
|
|
425
|
+
return ms % 1e3 === 0 ? `${ms / 1e3}s` : `${ms}ms`;
|
|
426
|
+
}
|
|
427
|
+
function targetHost(err) {
|
|
428
|
+
const config = err?.config;
|
|
429
|
+
const raw = config?.baseURL ?? config?.url;
|
|
430
|
+
if (raw === void 0 || raw === "")
|
|
431
|
+
return void 0;
|
|
432
|
+
try {
|
|
433
|
+
return new URL(raw).host;
|
|
434
|
+
} catch {
|
|
435
|
+
return void 0;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function configuredTimeoutMs(err) {
|
|
439
|
+
const t = err?.config?.timeout;
|
|
440
|
+
return typeof t === "number" && t > 0 ? t : void 0;
|
|
441
|
+
}
|
|
442
|
+
function retryAfterSeconds(err) {
|
|
443
|
+
const headers = err?.response?.headers;
|
|
444
|
+
const raw = headers?.["retry-after"];
|
|
445
|
+
if (raw === void 0 || raw === "")
|
|
446
|
+
return void 0;
|
|
447
|
+
const seconds = Number(raw);
|
|
448
|
+
if (Number.isFinite(seconds))
|
|
449
|
+
return Math.max(0, Math.round(seconds));
|
|
450
|
+
if (typeof raw !== "string")
|
|
451
|
+
return void 0;
|
|
452
|
+
const at = Date.parse(raw);
|
|
453
|
+
if (Number.isNaN(at))
|
|
454
|
+
return void 0;
|
|
455
|
+
return Math.max(0, Math.round((at - Date.now()) / 1e3));
|
|
456
|
+
}
|
|
457
|
+
function httpStatus(err) {
|
|
458
|
+
const e = err;
|
|
459
|
+
return e?.response?.status ?? e?.statusCode;
|
|
460
|
+
}
|
|
461
|
+
function responseDetail(err) {
|
|
462
|
+
const e = err;
|
|
463
|
+
const data = e?.response?.data;
|
|
464
|
+
if (data && typeof data === "object")
|
|
465
|
+
return data;
|
|
466
|
+
const body = e?.body;
|
|
467
|
+
if (typeof body === "string") {
|
|
468
|
+
try {
|
|
469
|
+
const parsed = JSON.parse(body);
|
|
470
|
+
return parsed.error ?? parsed;
|
|
471
|
+
} catch {
|
|
472
|
+
return void 0;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
if (body && typeof body === "object") {
|
|
476
|
+
return body.error ?? body;
|
|
477
|
+
}
|
|
478
|
+
return void 0;
|
|
479
|
+
}
|
|
480
|
+
function errorCode(err) {
|
|
481
|
+
return err?.code;
|
|
482
|
+
}
|
|
483
|
+
function normalize(err, opts) {
|
|
484
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
485
|
+
if (err instanceof CommanderError) {
|
|
486
|
+
return {
|
|
487
|
+
type: "usage",
|
|
488
|
+
message: (message || "Invalid command usage").replace(/^error:\s+/, ""),
|
|
489
|
+
recovery: "Check the command syntax and flags; run the command with --help.",
|
|
490
|
+
retryable: false
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
if (err instanceof SubcommandRequiredError) {
|
|
494
|
+
return {
|
|
495
|
+
type: "usage",
|
|
496
|
+
message,
|
|
497
|
+
recovery: `Re-run with one of these subcommands: ${err.subcommands.join(", ")}.`,
|
|
498
|
+
retryable: false,
|
|
499
|
+
detail: { subcommands: err.subcommands }
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (err instanceof UnknownSubcommandError) {
|
|
503
|
+
const didYouMean = err.suggestion === void 0 ? "" : `Did you mean '${err.suggestion}'? `;
|
|
504
|
+
return {
|
|
505
|
+
type: "usage",
|
|
506
|
+
message,
|
|
507
|
+
recovery: `${didYouMean}Valid subcommands: ${err.subcommands.join(", ")}.`,
|
|
508
|
+
retryable: false,
|
|
509
|
+
detail: { subcommands: err.subcommands }
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
if (err instanceof CliAuthError) {
|
|
513
|
+
return { type: "auth", message: message || "Missing credentials", recovery: opts.authRecovery, retryable: false };
|
|
514
|
+
}
|
|
515
|
+
if (err instanceof CliNotFoundError) {
|
|
516
|
+
return { type: "not_found", message, recovery: err.recovery, retryable: false };
|
|
517
|
+
}
|
|
518
|
+
if (err instanceof CliUsageError) {
|
|
519
|
+
return {
|
|
520
|
+
type: "usage",
|
|
521
|
+
message,
|
|
522
|
+
recovery: err.recovery,
|
|
523
|
+
retryable: false,
|
|
524
|
+
...err.detail !== void 0 ? { detail: err.detail } : {}
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
const status = httpStatus(err);
|
|
528
|
+
const detail = responseDetail(err);
|
|
529
|
+
if (status !== void 0) {
|
|
530
|
+
switch (status) {
|
|
531
|
+
case 400:
|
|
532
|
+
return {
|
|
533
|
+
type: "usage",
|
|
534
|
+
status,
|
|
535
|
+
message: "Bad request",
|
|
536
|
+
recovery: "Check parameter values (ids, keys, query syntax) against the API.",
|
|
537
|
+
retryable: false,
|
|
538
|
+
detail
|
|
539
|
+
};
|
|
540
|
+
case 401:
|
|
541
|
+
return {
|
|
542
|
+
type: "auth",
|
|
543
|
+
status,
|
|
544
|
+
message: "Authentication failed",
|
|
545
|
+
recovery: opts.authRecovery,
|
|
546
|
+
retryable: false
|
|
547
|
+
};
|
|
548
|
+
case 403:
|
|
549
|
+
return {
|
|
550
|
+
type: "forbidden",
|
|
551
|
+
status,
|
|
552
|
+
message: "Forbidden",
|
|
553
|
+
recovery: `Your ${opts.service} account lacks permission for this operation; check token scope and resource permissions.`,
|
|
554
|
+
retryable: false,
|
|
555
|
+
detail
|
|
556
|
+
};
|
|
557
|
+
case 404:
|
|
558
|
+
return {
|
|
559
|
+
type: "not_found",
|
|
560
|
+
status,
|
|
561
|
+
message: NOT_FOUND_MESSAGE,
|
|
562
|
+
recovery: NOT_FOUND_RECOVERY,
|
|
563
|
+
retryable: false,
|
|
564
|
+
detail
|
|
565
|
+
};
|
|
566
|
+
case 409:
|
|
567
|
+
return {
|
|
568
|
+
type: "conflict",
|
|
569
|
+
status,
|
|
570
|
+
message: "Conflict",
|
|
571
|
+
recovery: "The resource changed or already exists; re-fetch current state and retry.",
|
|
572
|
+
retryable: false,
|
|
573
|
+
detail
|
|
574
|
+
};
|
|
575
|
+
case 429: {
|
|
576
|
+
const wait = retryAfterSeconds(err);
|
|
577
|
+
return {
|
|
578
|
+
type: "rate_limited",
|
|
579
|
+
status,
|
|
580
|
+
message: `${opts.service} is rate limiting these requests`,
|
|
581
|
+
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.`,
|
|
582
|
+
retryable: true,
|
|
583
|
+
...wait === void 0 ? {} : { detail: { retryAfterSeconds: wait } }
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
if (status >= 500) {
|
|
588
|
+
return {
|
|
589
|
+
type: "server",
|
|
590
|
+
status,
|
|
591
|
+
message: status === 503 ? `${opts.service} returned 503 (service unavailable)` : `${opts.service} returned ${status}`,
|
|
592
|
+
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.`,
|
|
593
|
+
retryable: true,
|
|
594
|
+
detail
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
return {
|
|
598
|
+
type: "unknown",
|
|
599
|
+
status,
|
|
600
|
+
message: `${opts.service} error (HTTP ${status}): ${message}`,
|
|
601
|
+
recovery: "Inspect the detail field for the API response.",
|
|
602
|
+
retryable: false,
|
|
603
|
+
detail
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
const code = errorCode(err);
|
|
607
|
+
const transport = classifyTransport(err, code, message, opts);
|
|
608
|
+
if (transport)
|
|
609
|
+
return transport;
|
|
610
|
+
return { type: "unknown", message, recovery: "Unexpected error; inspect the message.", retryable: false };
|
|
611
|
+
}
|
|
612
|
+
function classifyTransport(err, code, message, opts) {
|
|
613
|
+
const matches = (codes) => code !== void 0 && codes.includes(code) || codes.some((c) => message.includes(c));
|
|
614
|
+
const host = targetHost(err);
|
|
615
|
+
const where = host ?? opts.service;
|
|
616
|
+
const urlVar = opts.urlEnvVar ?? "the base-URL environment variable";
|
|
617
|
+
if (matches(TRANSPORT_CODES.readTimeout)) {
|
|
618
|
+
const limit = configuredTimeoutMs(err);
|
|
619
|
+
return {
|
|
620
|
+
type: "timeout",
|
|
621
|
+
message: `${opts.service} did not respond${limit === void 0 ? " in time" : ` within ${humanMs(limit)}`}`,
|
|
622
|
+
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.`,
|
|
623
|
+
retryable: true
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
if (matches(TRANSPORT_CODES.connectTimeout)) {
|
|
627
|
+
const limit = configuredTimeoutMs(err);
|
|
628
|
+
return {
|
|
629
|
+
type: "timeout",
|
|
630
|
+
message: `Could not open a connection to ${where}${limit === void 0 ? "" : ` within ${humanMs(limit)}`}`,
|
|
631
|
+
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.`,
|
|
632
|
+
retryable: true
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
if (matches(TRANSPORT_CODES.unresolved)) {
|
|
636
|
+
return {
|
|
637
|
+
type: "network",
|
|
638
|
+
message: `The host ${where} does not resolve`,
|
|
639
|
+
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.`,
|
|
640
|
+
retryable: false
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
if (matches(TRANSPORT_CODES.refused)) {
|
|
644
|
+
return {
|
|
645
|
+
type: "network",
|
|
646
|
+
message: `Nothing accepted a connection at ${where}`,
|
|
647
|
+
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.`,
|
|
648
|
+
retryable: false
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
if (matches(TRANSPORT_CODES.dropped)) {
|
|
652
|
+
return {
|
|
653
|
+
type: "network",
|
|
654
|
+
message: `The connection to ${opts.service} closed before a response arrived`,
|
|
655
|
+
recovery: `The connection dropped mid-request. This is usually a blip, so retry.`,
|
|
656
|
+
retryable: true
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
return void 0;
|
|
660
|
+
}
|
|
661
|
+
function classifyError(err, opts) {
|
|
662
|
+
const base = normalize(err, opts);
|
|
663
|
+
const n = opts.adapt ? opts.adapt(base, err) : base;
|
|
664
|
+
if (n.type === "auth" && n.detail === void 0 && opts.credentialInfo) {
|
|
665
|
+
n.detail = opts.credentialInfo();
|
|
666
|
+
}
|
|
667
|
+
return {
|
|
668
|
+
envelope: {
|
|
669
|
+
error: {
|
|
670
|
+
type: n.type,
|
|
671
|
+
message: n.message,
|
|
672
|
+
recovery: n.recovery,
|
|
673
|
+
retryable: n.retryable,
|
|
674
|
+
...n.detail !== void 0 ? { detail: n.detail } : {}
|
|
675
|
+
}
|
|
676
|
+
},
|
|
677
|
+
exitCode: TYPE_EXIT[n.type]
|
|
678
|
+
};
|
|
679
|
+
}
|
|
680
|
+
function createErrorHandler(opts) {
|
|
681
|
+
return (err) => {
|
|
682
|
+
const { envelope, exitCode } = classifyError(err, opts);
|
|
683
|
+
process.stderr.write(`${JSON.stringify(envelope)}
|
|
684
|
+
`);
|
|
685
|
+
return process.exit(exitCode);
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
function isCleanCommanderExit(err) {
|
|
689
|
+
return err instanceof CommanderError && (err.exitCode === 0 || err.code === "commander.helpDisplayed" || err.code === "commander.help" || err.code === "commander.version");
|
|
690
|
+
}
|
|
691
|
+
function routeErrors(cmd) {
|
|
692
|
+
cmd.exitOverride();
|
|
693
|
+
cmd.configureOutput({ writeErr: () => void 0 });
|
|
694
|
+
cmd.commands.forEach(routeErrors);
|
|
695
|
+
}
|
|
696
|
+
function commandPath2(cmd) {
|
|
697
|
+
const parts = [];
|
|
698
|
+
let cur = cmd;
|
|
699
|
+
while (cur) {
|
|
700
|
+
parts.unshift(cur.name());
|
|
701
|
+
cur = cur.parent;
|
|
702
|
+
}
|
|
703
|
+
return parts.join(" ");
|
|
704
|
+
}
|
|
705
|
+
function attachSubcommandGuards(cmd) {
|
|
706
|
+
cmd.commands.forEach(attachSubcommandGuards);
|
|
707
|
+
if (cmd.commands.length === 0)
|
|
708
|
+
return;
|
|
709
|
+
const hasAction = Boolean(cmd._actionHandler);
|
|
710
|
+
if (hasAction)
|
|
711
|
+
return;
|
|
712
|
+
cmd.allowExcessArguments(true);
|
|
713
|
+
cmd.action((...params) => {
|
|
714
|
+
const invoked = params[params.length - 1];
|
|
715
|
+
const names = cmd.commands.map((c) => c.name()).filter((name) => name !== "help");
|
|
716
|
+
const [token] = invoked.args;
|
|
717
|
+
if (token === void 0)
|
|
718
|
+
throw new SubcommandRequiredError(commandPath2(cmd), names);
|
|
719
|
+
throw new UnknownSubcommandError(commandPath2(cmd), token, names, suggestSubcommand(token, names));
|
|
720
|
+
});
|
|
721
|
+
}
|
|
722
|
+
function editDistance(a, b) {
|
|
723
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
724
|
+
for (let i = 1; i <= a.length; i++) {
|
|
725
|
+
const row = [i];
|
|
726
|
+
for (let j = 1; j <= b.length; j++) {
|
|
727
|
+
const substitution = prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
728
|
+
row[j] = Math.min(row[j - 1] + 1, prev[j] + 1, substitution);
|
|
729
|
+
}
|
|
730
|
+
prev = row;
|
|
731
|
+
}
|
|
732
|
+
return prev[b.length];
|
|
733
|
+
}
|
|
734
|
+
function suggestSubcommand(token, names) {
|
|
735
|
+
const MAX_EDITS = 2;
|
|
736
|
+
let best;
|
|
737
|
+
for (const name of names) {
|
|
738
|
+
const distance = editDistance(token.toLowerCase(), name.toLowerCase());
|
|
739
|
+
if (distance <= MAX_EDITS && distance < token.length && (!best || distance < best.distance)) {
|
|
740
|
+
best = { name, distance };
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
return best?.name;
|
|
744
|
+
}
|
|
745
|
+
async function runCli(program, opts) {
|
|
746
|
+
routeErrors(program);
|
|
747
|
+
attachSubcommandGuards(program);
|
|
748
|
+
try {
|
|
749
|
+
await program.parseAsync();
|
|
750
|
+
} catch (err) {
|
|
751
|
+
if (isCleanCommanderExit(err)) {
|
|
752
|
+
process.exit(err.exitCode ?? 0);
|
|
753
|
+
}
|
|
754
|
+
createErrorHandler(opts)(err);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// ../../cli-utils/dist/conventions.js
|
|
759
|
+
var FORBIDDEN_SCOPE_POSITIONALS = /* @__PURE__ */ new Set([
|
|
760
|
+
"project",
|
|
761
|
+
"repo",
|
|
762
|
+
"space",
|
|
763
|
+
"drive",
|
|
764
|
+
"board",
|
|
765
|
+
"chat",
|
|
766
|
+
"page",
|
|
767
|
+
"structure"
|
|
768
|
+
]);
|
|
769
|
+
var FORBIDDEN_PROSE_POSITIONALS = /* @__PURE__ */ new Set(["body", "comment", "text"]);
|
|
770
|
+
var SAFE_ID_WORDS = /* @__PURE__ */ new Set([
|
|
771
|
+
"uuid",
|
|
772
|
+
"guid",
|
|
773
|
+
"grid",
|
|
774
|
+
"valid",
|
|
775
|
+
"void",
|
|
776
|
+
"paid",
|
|
777
|
+
"hybrid",
|
|
778
|
+
"android",
|
|
779
|
+
"rapid",
|
|
780
|
+
"solid",
|
|
781
|
+
"liquid"
|
|
782
|
+
]);
|
|
783
|
+
function optionConcept(option) {
|
|
784
|
+
if (!option.long)
|
|
785
|
+
return void 0;
|
|
786
|
+
return option.long.replace(/^--/, "").replace(/^no-/, "");
|
|
787
|
+
}
|
|
788
|
+
function optionTakesValue(option) {
|
|
789
|
+
return option.required || option.optional;
|
|
790
|
+
}
|
|
791
|
+
function hasValidator(o) {
|
|
792
|
+
return typeof o.parseArg === "function" || Array.isArray(o.argChoices) && o.argChoices.length > 0;
|
|
793
|
+
}
|
|
794
|
+
function checkVariadicAccumulation(o, label, path, violations) {
|
|
795
|
+
if (!o.variadic || typeof o.parseArg !== "function")
|
|
796
|
+
return;
|
|
797
|
+
const first = o.argChoices?.[0] ?? "PROJ-1";
|
|
798
|
+
const second = o.argChoices?.[1] ?? o.argChoices?.[0] ?? "PROJ-2";
|
|
799
|
+
let problem;
|
|
800
|
+
try {
|
|
801
|
+
const result = o.parseArg(second, o.parseArg(first, void 0));
|
|
802
|
+
if (!Array.isArray(result) || result.length !== 2) {
|
|
803
|
+
problem = `two tokens produced ${JSON.stringify(result)} instead of a two-element array`;
|
|
804
|
+
}
|
|
805
|
+
} catch (err) {
|
|
806
|
+
problem = `the parser threw on the probe tokens (${err instanceof Error ? err.message : String(err)})`;
|
|
807
|
+
}
|
|
808
|
+
if (problem) {
|
|
809
|
+
violations.push(`${path}: variadic ${label} has a parser that does not collect values \u2014 ${problem}. Wrap the item validator in \`listOf(...)\` (\xA74.10); a bare validator makes commander keep only the last token.`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function checkRejectsEmptyString(arg, path, violations) {
|
|
813
|
+
const parse = arg.parseArg;
|
|
814
|
+
if (!arg.required || arg.variadic || typeof parse !== "function")
|
|
815
|
+
return;
|
|
816
|
+
const accepted = (token) => {
|
|
817
|
+
try {
|
|
818
|
+
return { value: parse(token, void 0) };
|
|
819
|
+
} catch {
|
|
820
|
+
return void 0;
|
|
821
|
+
}
|
|
822
|
+
};
|
|
823
|
+
const empty = accepted("");
|
|
824
|
+
const blank = empty ? void 0 : accepted(" ");
|
|
825
|
+
const problem = empty ? `it parsed "" as ${JSON.stringify(empty.value)}` : blank ? `it parsed a whitespace-only value as ${JSON.stringify(blank.value)}` : void 0;
|
|
826
|
+
if (!problem)
|
|
827
|
+
return;
|
|
828
|
+
violations.push(`${path}: required positional \`<${arg.name()}>\` accepts an empty identifier \u2014 ${problem}. A required positional names one entity, and many APIs route \`/resource/\` to \`/resource\`, so an empty id asks for the whole collection and gets a confident 200 instead of an error. Use \`nonEmpty\` rather than \`text\` (or a stricter validator that already fits \u2014 positiveInt, an id/key shape); empty and whitespace-only must both throw (\xA74.10).`);
|
|
829
|
+
}
|
|
830
|
+
function checkOption(option, path, violations) {
|
|
831
|
+
const name = optionConcept(option);
|
|
832
|
+
if (!name)
|
|
833
|
+
return;
|
|
834
|
+
if (name === "id") {
|
|
835
|
+
violations.push(`${path}: flag \`--id\` is banned \u2014 use an entity-qualified id flag (e.g. --comment-id, --message-id) per \xA74.3.`);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
if (/[a-z]id$/.test(name) && !name.endsWith("-id") && !SAFE_ID_WORDS.has(name)) {
|
|
839
|
+
violations.push(`${path}: id flag \`--${name}\` must be hyphenated as \`--\u2026-id\` per \xA74.3.`);
|
|
840
|
+
}
|
|
841
|
+
const hint = BANNED_FLAGS[name];
|
|
842
|
+
if (hint) {
|
|
843
|
+
violations.push(`${path}: non-canonical flag \`--${name}\` \u2014 ${hint} (\xA74.6/\xA74.7).`);
|
|
844
|
+
}
|
|
845
|
+
if (optionTakesValue(option) && !hasValidator(option)) {
|
|
846
|
+
violations.push(`${path}: option \`--${name}\` takes a value but declares no validator \u2014 wire one (date/dateTime/jsonShape/integer/positiveInt/\u2026 or .choices()); for free text use \`text\` (empty allowed) or \`nonEmpty\` (\xA74.10).`);
|
|
847
|
+
}
|
|
848
|
+
checkVariadicAccumulation(option, `option \`--${name}\``, path, violations);
|
|
849
|
+
}
|
|
850
|
+
function checkArguments(args, path, violations) {
|
|
851
|
+
if (args.length > 1) {
|
|
852
|
+
const names = args.map((a) => a.name()).join(", ");
|
|
853
|
+
violations.push(`${path}: ${args.length} positionals (${names}) \u2014 at most one is allowed; extras must be flags. The only multi-positional form is a single trailing same-kind variadic (\xA74.2).`);
|
|
854
|
+
}
|
|
855
|
+
for (const arg of args) {
|
|
856
|
+
const name = arg.name();
|
|
857
|
+
if (FORBIDDEN_SCOPE_POSITIONALS.has(name)) {
|
|
858
|
+
violations.push(`${path}: scope \`${name}\` must be a flag (--${name} / --${name}-id), never a positional (\xA74.2/\xA74.4).`);
|
|
859
|
+
}
|
|
860
|
+
if (FORBIDDEN_PROSE_POSITIONALS.has(name)) {
|
|
861
|
+
violations.push(`${path}: prose \`${name}\` must be the --body flag, never a positional (\xA74.6).`);
|
|
862
|
+
}
|
|
863
|
+
if (!hasValidator(arg)) {
|
|
864
|
+
violations.push(`${path}: positional \`<${name}>\` declares no validator \u2014 pass a parser to .argument()/subjectArg (e.g. positiveInt, enumArg([...]), text) (\xA74.10).`);
|
|
865
|
+
}
|
|
866
|
+
checkVariadicAccumulation(arg, `positional \`<${name}>\``, path, violations);
|
|
867
|
+
checkRejectsEmptyString(arg, path, violations);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
function checkAllFlag(cmd, path, violations) {
|
|
871
|
+
const names = new Set(cmd.options.map((o) => o.attributeName()));
|
|
872
|
+
if (!names.has("all") || names.has("limit"))
|
|
873
|
+
return;
|
|
874
|
+
violations.push(`${path}: \`--all\` means "collect every match" and is only meaningful where there are pages to collect, but this command declares no \`--limit\`. Here it reads as a scope filter \u2014 name it for what it selects (e.g. \`--all-employees\`) so the two meanings cannot be confused (\xA74.7).`);
|
|
875
|
+
}
|
|
876
|
+
var NON_CANONICAL_VERBS = /* @__PURE__ */ new Map([
|
|
877
|
+
["new", "create"],
|
|
878
|
+
["rm", "delete"],
|
|
879
|
+
["edit", "update"],
|
|
880
|
+
["fetch", "get"],
|
|
881
|
+
["show", "get"],
|
|
882
|
+
["read", "get"]
|
|
883
|
+
]);
|
|
884
|
+
function checkVerb(cmd, parentPath, path, violations) {
|
|
885
|
+
if (!parentPath)
|
|
886
|
+
return;
|
|
887
|
+
const canonical = NON_CANONICAL_VERBS.get(cmd.name());
|
|
888
|
+
if (!canonical)
|
|
889
|
+
return;
|
|
890
|
+
violations.push(`${path}: \`${cmd.name()}\` is not a canonical verb \u2014 use \`${canonical}\` (rule 2). An agent that learned the verb on one noun applies it to every noun, so a synonym here costs a failed call everywhere else it guesses.`);
|
|
891
|
+
}
|
|
892
|
+
var WRITE_VERBS = ["create", "update", "delete"];
|
|
893
|
+
var WRITE_WITHOUT_READ_EXEMPT = /* @__PURE__ */ new Map([
|
|
894
|
+
[
|
|
895
|
+
"bamboohr timeoff",
|
|
896
|
+
"The read is `requests`, named for what the vendor API addresses \u2014 it filters requests rather than resolving one by id."
|
|
897
|
+
],
|
|
898
|
+
[
|
|
899
|
+
"msgraph calendar",
|
|
900
|
+
"The group is named for the calendar, but `create` and `update` act on events, and the read for those is `events`. A `calendar get` would name the wrong entity; the mismatch is real and wants renaming, not a new command."
|
|
901
|
+
]
|
|
902
|
+
]);
|
|
903
|
+
function checkWriteWithoutRead(cmd, path, violations) {
|
|
904
|
+
if (cmd.commands.length === 0)
|
|
905
|
+
return;
|
|
906
|
+
const subs = new Set(cmd.commands.map((c) => c.name()));
|
|
907
|
+
if (subs.has("get") || subs.has("list"))
|
|
908
|
+
return;
|
|
909
|
+
const writes = WRITE_VERBS.filter((v) => subs.has(v));
|
|
910
|
+
if (writes.length === 0 || WRITE_WITHOUT_READ_EXEMPT.has(path))
|
|
911
|
+
return;
|
|
912
|
+
violations.push(`${path}: offers ${writes.join(", ")} and no way to read anything back \u2014 no \`get\`, no \`list\`. Add one, or declare the group in WRITE_WITHOUT_READ_EXEMPT with the reason.`);
|
|
913
|
+
}
|
|
914
|
+
function walk(cmd, parentPath, violations) {
|
|
915
|
+
const path = parentPath ? `${parentPath} ${cmd.name()}` : cmd.name();
|
|
916
|
+
for (const option of cmd.options)
|
|
917
|
+
checkOption(option, path, violations);
|
|
918
|
+
checkAllFlag(cmd, path, violations);
|
|
919
|
+
checkVerb(cmd, parentPath, path, violations);
|
|
920
|
+
checkWriteWithoutRead(cmd, path, violations);
|
|
921
|
+
checkArguments(cmd.registeredArguments, path, violations);
|
|
922
|
+
if (cmd.commands.length === 0 && !hasExamples(cmd)) {
|
|
923
|
+
violations.push(`${path}: runnable command has no examples \u2014 declare 1\u20133 with the examples() builder (\xA7"--help is the agent's interface").`);
|
|
924
|
+
}
|
|
925
|
+
for (const sub of cmd.commands)
|
|
926
|
+
walk(sub, path, violations);
|
|
927
|
+
}
|
|
928
|
+
function assertConventions(program) {
|
|
929
|
+
const violations = [];
|
|
930
|
+
walk(program, "", violations);
|
|
931
|
+
if (violations.length > 0) {
|
|
932
|
+
throw new Error(`CLI convention violations (${violations.length}):
|
|
933
|
+
${violations.map((v) => ` - ${v}`).join("\n")}`);
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// ../../cli-utils/dist/snapshot.js
|
|
938
|
+
function renderArg(arg) {
|
|
939
|
+
const name = arg.variadic ? `${arg.name()}...` : arg.name();
|
|
940
|
+
return arg.required ? `<${name}>` : `[${name}]`;
|
|
941
|
+
}
|
|
942
|
+
function renderOption(option) {
|
|
943
|
+
const suffix = `${option.mandatory ? "!" : ""}${option.argChoices ? `=${option.argChoices.join("|")}` : ""}`;
|
|
944
|
+
return `${option.flags}${suffix}`;
|
|
945
|
+
}
|
|
946
|
+
function walk2(cmd, parentPath, lines) {
|
|
947
|
+
const path = parentPath ? `${parentPath} ${cmd.name()}` : cmd.name();
|
|
948
|
+
const args = cmd.registeredArguments.map(renderArg);
|
|
949
|
+
const options = cmd.options.map(renderOption).sort();
|
|
950
|
+
lines.push([path, ...args, ...options].join(" ").trimEnd());
|
|
951
|
+
[...cmd.commands].sort((a, b) => a.name().localeCompare(b.name())).forEach((sub) => {
|
|
952
|
+
walk2(sub, path, lines);
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
function serializeCommandTree(program) {
|
|
956
|
+
const lines = [];
|
|
957
|
+
walk2(program, "", lines);
|
|
958
|
+
return lines.join("\n");
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// ../../cli-utils/dist/readme.js
|
|
962
|
+
function tokenize(line) {
|
|
963
|
+
const tokens = [];
|
|
964
|
+
let current = "";
|
|
965
|
+
let quote;
|
|
966
|
+
let started = false;
|
|
967
|
+
for (const char of line) {
|
|
968
|
+
if (quote) {
|
|
969
|
+
if (char === quote)
|
|
970
|
+
quote = void 0;
|
|
971
|
+
else
|
|
972
|
+
current += char;
|
|
973
|
+
continue;
|
|
974
|
+
}
|
|
975
|
+
if (char === '"' || char === "'") {
|
|
976
|
+
quote = char;
|
|
977
|
+
started = true;
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
if (/\s/.test(char)) {
|
|
981
|
+
if (current || started)
|
|
982
|
+
tokens.push(current);
|
|
983
|
+
current = "";
|
|
984
|
+
started = false;
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
if (char === "#" && !current && !started)
|
|
988
|
+
break;
|
|
989
|
+
current += char;
|
|
990
|
+
}
|
|
991
|
+
if (current || started)
|
|
992
|
+
tokens.push(current);
|
|
993
|
+
return tokens;
|
|
994
|
+
}
|
|
995
|
+
function candidates(readme, programName) {
|
|
996
|
+
const found = [];
|
|
997
|
+
for (const match of readme.matchAll(/^\|\s*`([^`]+)`/gm))
|
|
998
|
+
found.push(match[1] ?? "");
|
|
999
|
+
for (const block of readme.matchAll(/^```[a-z]*\n([\s\S]*?)^```/gm)) {
|
|
1000
|
+
const body = (block[1] ?? "").replace(/\\\n\s*/g, " ");
|
|
1001
|
+
for (const line of body.split("\n"))
|
|
1002
|
+
found.push(line.replace(/^\s*\$\s+/, ""));
|
|
1003
|
+
}
|
|
1004
|
+
return found.filter((line) => tokenize(line)[0] === programName);
|
|
1005
|
+
}
|
|
1006
|
+
function findOption(cmd, flag) {
|
|
1007
|
+
return cmd.options.find((o) => o.long === flag);
|
|
1008
|
+
}
|
|
1009
|
+
function optionTakesValue2(option) {
|
|
1010
|
+
return option.required || option.optional;
|
|
1011
|
+
}
|
|
1012
|
+
function resolve(program, tokens) {
|
|
1013
|
+
let cmd = program;
|
|
1014
|
+
const path = [program.name()];
|
|
1015
|
+
let i = 1;
|
|
1016
|
+
for (; i < tokens.length; i++) {
|
|
1017
|
+
const next = cmd.commands.find((c) => c.name() === tokens[i]);
|
|
1018
|
+
if (!next)
|
|
1019
|
+
break;
|
|
1020
|
+
cmd = next;
|
|
1021
|
+
path.push(cmd.name());
|
|
1022
|
+
}
|
|
1023
|
+
return { cmd, path, rest: tokens.slice(i) };
|
|
1024
|
+
}
|
|
1025
|
+
function checkOne(program, line, documented, problems) {
|
|
1026
|
+
const tokens = tokenize(line);
|
|
1027
|
+
const { cmd, path, rest } = resolve(program, tokens);
|
|
1028
|
+
const where = path.join(" ");
|
|
1029
|
+
if (cmd.commands.length > 0) {
|
|
1030
|
+
const unknown = rest[0];
|
|
1031
|
+
problems.push(unknown && !unknown.startsWith("-") ? `\`${line.trim()}\`: \`${unknown}\` is not a subcommand of \`${where}\`` : `\`${line.trim()}\`: \`${where}\` is a group, not a runnable command`);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
let positionals = 0;
|
|
1035
|
+
for (let i = 0; i < rest.length; i++) {
|
|
1036
|
+
const token = rest[i] ?? "";
|
|
1037
|
+
if (!token.startsWith("--")) {
|
|
1038
|
+
if (!token.startsWith("-"))
|
|
1039
|
+
positionals++;
|
|
1040
|
+
continue;
|
|
1041
|
+
}
|
|
1042
|
+
const [flag, inlineValue] = token.split("=", 2);
|
|
1043
|
+
const option = findOption(cmd, flag ?? "");
|
|
1044
|
+
if (!option) {
|
|
1045
|
+
problems.push(`\`${line.trim()}\`: \`${where}\` has no flag \`${flag}\``);
|
|
1046
|
+
continue;
|
|
1047
|
+
}
|
|
1048
|
+
if (inlineValue !== void 0 || !optionTakesValue2(option))
|
|
1049
|
+
continue;
|
|
1050
|
+
while (rest[i + 1] !== void 0 && !(rest[i + 1] ?? "").startsWith("-")) {
|
|
1051
|
+
i++;
|
|
1052
|
+
if (!option.variadic)
|
|
1053
|
+
break;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
const args = cmd.registeredArguments;
|
|
1057
|
+
const variadic = args.some((a) => a.variadic);
|
|
1058
|
+
if (!variadic && positionals > args.length) {
|
|
1059
|
+
problems.push(`\`${line.trim()}\`: \`${where}\` takes ${args.length} positional(s), ${positionals} documented`);
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
documented.add(where);
|
|
1063
|
+
}
|
|
1064
|
+
function leaves(cmd, path, out) {
|
|
1065
|
+
const here = [...path, cmd.name()];
|
|
1066
|
+
if (cmd.commands.length === 0)
|
|
1067
|
+
out.push(here.join(" "));
|
|
1068
|
+
else
|
|
1069
|
+
for (const sub of cmd.commands)
|
|
1070
|
+
leaves(sub, here, out);
|
|
1071
|
+
}
|
|
1072
|
+
function checkReadme(program, readme) {
|
|
1073
|
+
const problems = [];
|
|
1074
|
+
const documented = /* @__PURE__ */ new Set();
|
|
1075
|
+
for (const line of candidates(readme, program.name()))
|
|
1076
|
+
checkOne(program, line, documented, problems);
|
|
1077
|
+
const all = [];
|
|
1078
|
+
for (const sub of program.commands)
|
|
1079
|
+
leaves(sub, [program.name()], all);
|
|
1080
|
+
return { problems, undocumented: all.filter((leaf) => !documented.has(leaf)) };
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
export {
|
|
1084
|
+
cacheGet,
|
|
1085
|
+
cacheSet,
|
|
1086
|
+
cacheGetOrFetch,
|
|
1087
|
+
readPackageVersion,
|
|
1088
|
+
intInRange,
|
|
1089
|
+
nonNegativeInt,
|
|
1090
|
+
positiveInt,
|
|
1091
|
+
enumArg,
|
|
1092
|
+
listOf,
|
|
1093
|
+
text,
|
|
1094
|
+
nonEmpty,
|
|
1095
|
+
integer,
|
|
1096
|
+
finiteNumber,
|
|
1097
|
+
date,
|
|
1098
|
+
dateTime,
|
|
1099
|
+
uuid,
|
|
1100
|
+
jsonShape,
|
|
1101
|
+
rejectStdinSentinel,
|
|
1102
|
+
textOrFileOption,
|
|
1103
|
+
resolveTextOrFile,
|
|
1104
|
+
SCOPE_SPECS,
|
|
1105
|
+
SUB_ENTITY_ID_NUMERIC,
|
|
1106
|
+
BANNED_FLAGS,
|
|
1107
|
+
subjectArg,
|
|
1108
|
+
projectOption,
|
|
1109
|
+
repoOption,
|
|
1110
|
+
spaceOption,
|
|
1111
|
+
scopeIdOption,
|
|
1112
|
+
subEntityOption,
|
|
1113
|
+
paginationOptions,
|
|
1114
|
+
bodyOption,
|
|
1115
|
+
commentOption,
|
|
1116
|
+
formatOption,
|
|
1117
|
+
fieldsOption,
|
|
1118
|
+
examples,
|
|
1119
|
+
EXIT,
|
|
1120
|
+
CliAuthError,
|
|
1121
|
+
CliNotFoundError,
|
|
1122
|
+
CliUsageError,
|
|
1123
|
+
SubcommandRequiredError,
|
|
1124
|
+
UnknownSubcommandError,
|
|
1125
|
+
classifyError,
|
|
1126
|
+
createErrorHandler,
|
|
1127
|
+
routeErrors,
|
|
1128
|
+
attachSubcommandGuards,
|
|
1129
|
+
runCli,
|
|
1130
|
+
assertConventions,
|
|
1131
|
+
serializeCommandTree,
|
|
1132
|
+
checkReadme
|
|
1133
|
+
};
|