@unbrained/pm-cli 2026.5.10 → 2026.5.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/.pi/README.md +10 -1
  3. package/.pi/agents/pm-triage-agent.md +19 -0
  4. package/.pi/agents/pm-verification-agent.md +21 -0
  5. package/.pi/chains/pm-native-delivery.chain.md +11 -0
  6. package/.pi/extensions/pm-cli/index.js +276 -36
  7. package/.pi/skills/pm-native/SKILL.md +6 -2
  8. package/CHANGELOG.md +7 -0
  9. package/README.md +9 -1
  10. package/dist/cli/argv-utils.d.ts +5 -0
  11. package/dist/cli/argv-utils.js +34 -0
  12. package/dist/cli/argv-utils.js.map +1 -0
  13. package/dist/cli/bootstrap-args.d.ts +15 -0
  14. package/dist/cli/bootstrap-args.js +211 -0
  15. package/dist/cli/bootstrap-args.js.map +1 -1
  16. package/dist/cli/commander-usage.js +109 -3
  17. package/dist/cli/commander-usage.js.map +1 -1
  18. package/dist/cli/commands/completion.js +7 -3
  19. package/dist/cli/commands/completion.js.map +1 -1
  20. package/dist/cli/commands/contracts.d.ts +19 -0
  21. package/dist/cli/commands/contracts.js +33 -1
  22. package/dist/cli/commands/contracts.js.map +1 -1
  23. package/dist/cli/commands/create.js +112 -51
  24. package/dist/cli/commands/create.js.map +1 -1
  25. package/dist/cli/commands/docs.js +9 -2
  26. package/dist/cli/commands/docs.js.map +1 -1
  27. package/dist/cli/commands/extension.d.ts +3 -1
  28. package/dist/cli/commands/extension.js +174 -2
  29. package/dist/cli/commands/extension.js.map +1 -1
  30. package/dist/cli/commands/files.js +9 -2
  31. package/dist/cli/commands/files.js.map +1 -1
  32. package/dist/cli/commands/init.d.ts +2 -0
  33. package/dist/cli/commands/init.js +21 -1
  34. package/dist/cli/commands/init.js.map +1 -1
  35. package/dist/cli/commands/metadata-normalizers.d.ts +4 -0
  36. package/dist/cli/commands/metadata-normalizers.js +37 -0
  37. package/dist/cli/commands/metadata-normalizers.js.map +1 -0
  38. package/dist/cli/commands/reindex.js +173 -135
  39. package/dist/cli/commands/reindex.js.map +1 -1
  40. package/dist/cli/commands/search.js +16 -6
  41. package/dist/cli/commands/search.js.map +1 -1
  42. package/dist/cli/commands/test.js +9 -2
  43. package/dist/cli/commands/test.js.map +1 -1
  44. package/dist/cli/commands/update.js +70 -39
  45. package/dist/cli/commands/update.js.map +1 -1
  46. package/dist/cli/error-guidance.d.ts +9 -1
  47. package/dist/cli/error-guidance.js +147 -6
  48. package/dist/cli/error-guidance.js.map +1 -1
  49. package/dist/cli/help-json-payload.js +11 -1
  50. package/dist/cli/help-json-payload.js.map +1 -1
  51. package/dist/cli/main.js +69 -6
  52. package/dist/cli/main.js.map +1 -1
  53. package/dist/cli/register-setup.js +14 -0
  54. package/dist/cli/register-setup.js.map +1 -1
  55. package/dist/cli/telemetry-flush.d.ts +2 -0
  56. package/dist/cli/telemetry-flush.js +4 -0
  57. package/dist/cli/telemetry-flush.js.map +1 -0
  58. package/dist/cli.js +1 -2
  59. package/dist/cli.js.map +1 -1
  60. package/dist/core/extensions/extension-types.d.ts +72 -0
  61. package/dist/core/extensions/extension-types.js +24 -0
  62. package/dist/core/extensions/extension-types.js.map +1 -1
  63. package/dist/core/extensions/loader.d.ts +1 -0
  64. package/dist/core/extensions/loader.js +766 -7
  65. package/dist/core/extensions/loader.js.map +1 -1
  66. package/dist/core/lock/lock.js +2 -0
  67. package/dist/core/lock/lock.js.map +1 -1
  68. package/dist/core/sentry/instrument.d.ts +15 -0
  69. package/dist/core/sentry/instrument.js +35 -3
  70. package/dist/core/sentry/instrument.js.map +1 -1
  71. package/dist/core/shared/constants.js +20 -0
  72. package/dist/core/shared/constants.js.map +1 -1
  73. package/dist/core/shared/errors.d.ts +8 -0
  74. package/dist/core/shared/errors.js.map +1 -1
  75. package/dist/core/shared/levenshtein.d.ts +1 -0
  76. package/dist/core/shared/levenshtein.js +37 -0
  77. package/dist/core/shared/levenshtein.js.map +1 -0
  78. package/dist/core/store/paths.js +34 -1
  79. package/dist/core/store/paths.js.map +1 -1
  80. package/dist/core/store/settings.js +210 -1
  81. package/dist/core/store/settings.js.map +1 -1
  82. package/dist/core/telemetry/runtime.d.ts +1 -0
  83. package/dist/core/telemetry/runtime.js +102 -3
  84. package/dist/core/telemetry/runtime.js.map +1 -1
  85. package/dist/mcp/server.js +3 -1
  86. package/dist/mcp/server.js.map +1 -1
  87. package/dist/pi/native.js +57 -4
  88. package/dist/pi/native.js.map +1 -1
  89. package/dist/sdk/cli-contracts.d.ts +21 -1
  90. package/dist/sdk/cli-contracts.js +250 -0
  91. package/dist/sdk/cli-contracts.js.map +1 -1
  92. package/dist/sdk/index.d.ts +12 -1
  93. package/dist/sdk/index.js +8 -1
  94. package/dist/sdk/index.js.map +1 -1
  95. package/dist/types.d.ts +41 -0
  96. package/dist/types.js.map +1 -1
  97. package/docs/CLAUDE_CODE_PLUGIN.md +39 -0
  98. package/docs/EXTENSIONS.md +687 -0
  99. package/docs/MIGRATION_CLI_SIMPLIFICATION.md +64 -0
  100. package/docs/PI_PACKAGE.md +95 -10
  101. package/docs/SDK.md +441 -0
  102. package/docs/examples/ci/github-actions-pm-extension-gate.yml +53 -0
  103. package/docs/examples/ci/gitlab-ci-pm-extension-gate.yml +41 -0
  104. package/docs/examples/ci/jenkins-pm-extension-gate.Jenkinsfile +45 -0
  105. package/docs/examples/policy-restricted-extension/README.md +74 -0
  106. package/docs/examples/policy-restricted-extension/index.js +21 -0
  107. package/docs/examples/policy-restricted-extension/manifest.json +21 -0
  108. package/docs/examples/policy-restricted-extension/package.json +8 -0
  109. package/docs/examples/sdk-app-embedding/README.md +39 -0
  110. package/docs/examples/sdk-app-embedding/package.json +9 -0
  111. package/docs/examples/sdk-app-embedding/run-embedded-pm.mjs +61 -0
  112. package/docs/examples/sdk-contract-consumer/README.md +57 -0
  113. package/docs/examples/sdk-contract-consumer/inspect-contracts.mjs +47 -0
  114. package/docs/examples/sdk-contract-consumer/package.json +10 -0
  115. package/docs/examples/starter-extension/README.md +57 -42
  116. package/docs/examples/starter-extension/manifest.json +15 -0
  117. package/marketplace.json +3 -3
  118. package/package.json +1 -1
  119. package/plugins/pm-cli-claude/.claude-plugin/plugin.json +2 -2
  120. package/plugins/pm-cli-claude/README.md +55 -14
  121. package/plugins/pm-cli-claude/agents/pm-delivery-chain.md +88 -0
  122. package/plugins/pm-cli-claude/agents/pm-triage-agent.md +83 -0
  123. package/plugins/pm-cli-claude/agents/pm-verification-agent.md +88 -0
  124. package/plugins/pm-cli-claude/hooks/session-start.mjs +87 -22
@@ -19,7 +19,8 @@ import { locateItem } from "../../core/store/item-store.js";
19
19
  import { getHistoryPath, getItemPath, getSettingsPath, resolvePmRoot } from "../../core/store/paths.js";
20
20
  import { readSettings } from "../../core/store/settings.js";
21
21
  import { loadCreateTemplateOptions } from "./templates.js";
22
- import { CONFIDENCE_TEXT_VALUES, DEPENDENCY_KIND_VALUES, ISSUE_SEVERITY_VALUES, RECURRENCE_FREQUENCY_VALUES, RECURRENCE_WEEKDAY_VALUES, RISK_VALUES, SCOPE_VALUES, } from "../../types/index.js";
22
+ import { normalizeRiskInput, normalizeSeverityInput, parseConfidenceInput, parseRegressionInput, } from "./metadata-normalizers.js";
23
+ import { DEPENDENCY_KIND_VALUES, ISSUE_SEVERITY_VALUES, RECURRENCE_FREQUENCY_VALUES, RECURRENCE_WEEKDAY_VALUES, RISK_VALUES, SCOPE_VALUES, } from "../../types/index.js";
23
24
  const CREATE_MODE_VALUES = ["strict", "progressive"];
24
25
  const SCHEDULE_CREATE_PRESET_VALUES = ["lightweight"];
25
26
  const SCHEDULE_CREATE_PRESET_TYPES = new Set(["Reminder", "Meeting", "Event"]);
@@ -171,38 +172,6 @@ function parseStatusValue(value, statusRegistry) {
171
172
  }
172
173
  return normalized;
173
174
  }
174
- function normalizeRiskInput(value) {
175
- const trimmed = value.trim();
176
- return trimmed.toLowerCase() === "med" ? "medium" : trimmed;
177
- }
178
- function normalizeSeverityInput(value) {
179
- const trimmed = value.trim();
180
- return trimmed.toLowerCase() === "med" ? "medium" : trimmed;
181
- }
182
- function parseConfidenceInput(value) {
183
- const trimmed = value.trim().toLowerCase();
184
- if (trimmed === "med") {
185
- return "medium";
186
- }
187
- if (CONFIDENCE_TEXT_VALUES.includes(trimmed)) {
188
- return trimmed;
189
- }
190
- const parsed = parseOptionalNumber(value, "confidence");
191
- if (!Number.isInteger(parsed) || parsed < 0 || parsed > 100) {
192
- throw new PmCliError("Confidence must be an integer 0..100 or one of low|med|medium|high", EXIT_CODE.USAGE);
193
- }
194
- return parsed;
195
- }
196
- function parseRegressionInput(value) {
197
- const normalized = value.trim().toLowerCase();
198
- if (normalized === "true" || normalized === "1") {
199
- return true;
200
- }
201
- if (normalized === "false" || normalized === "0") {
202
- return false;
203
- }
204
- throw new PmCliError("Regression must be one of true|false|1|0", EXIT_CODE.USAGE);
205
- }
206
175
  function parseCreatedAt(value, currentIso) {
207
176
  if (!value || value.trim() === "" || value.trim().toLowerCase() === "now") {
208
177
  return currentIso;
@@ -237,6 +206,61 @@ function assertNoLegacyNoneTokens(values, flag, replacementHint) {
237
206
  const suffix = replacementHint ? ` ${replacementHint}` : "";
238
207
  throw new PmCliError(`${flag} no longer accepts "none" or "null".${suffix}`.trim(), EXIT_CODE.USAGE);
239
208
  }
209
+ const CREATE_LEGACY_NONE_COLLECTION_NORMALIZERS = [
210
+ { optionKey: "dep", clearFlagKey: "clearDeps", valueFlag: "--dep", clearFlag: "--clear-deps" },
211
+ { optionKey: "comment", clearFlagKey: "clearComments", valueFlag: "--comment", clearFlag: "--clear-comments" },
212
+ { optionKey: "note", clearFlagKey: "clearNotes", valueFlag: "--note", clearFlag: "--clear-notes" },
213
+ { optionKey: "learning", clearFlagKey: "clearLearnings", valueFlag: "--learning", clearFlag: "--clear-learnings" },
214
+ { optionKey: "file", clearFlagKey: "clearFiles", valueFlag: "--file", clearFlag: "--clear-files" },
215
+ { optionKey: "test", clearFlagKey: "clearTests", valueFlag: "--test", clearFlag: "--clear-tests" },
216
+ { optionKey: "doc", clearFlagKey: "clearDocs", valueFlag: "--doc", clearFlag: "--clear-docs" },
217
+ { optionKey: "reminder", clearFlagKey: "clearReminders", valueFlag: "--reminder", clearFlag: "--clear-reminders" },
218
+ { optionKey: "event", clearFlagKey: "clearEvents", valueFlag: "--event", clearFlag: "--clear-events" },
219
+ { optionKey: "typeOption", clearFlagKey: "clearTypeOptions", valueFlag: "--type-option", clearFlag: "--clear-type-options" },
220
+ ];
221
+ function normalizeLegacyNoneCreateOptions(options) {
222
+ const normalized = {
223
+ ...options,
224
+ unset: options.unset ? [...options.unset] : undefined,
225
+ };
226
+ const appendUnsetTarget = (value) => {
227
+ const current = normalized.unset ? [...normalized.unset] : [];
228
+ if (!current.includes(value)) {
229
+ current.push(value);
230
+ }
231
+ normalized.unset = current;
232
+ };
233
+ if (isLegacyNoneToken(normalized.template)) {
234
+ normalized.template = undefined;
235
+ }
236
+ const scalarOptionKeys = new Set([...CREATE_OPTION_KEY_TO_UNSET_CANONICAL.keys(), "rank"]);
237
+ for (const optionKey of scalarOptionKeys) {
238
+ const candidate = normalized[optionKey];
239
+ if (typeof candidate !== "string" || !isLegacyNoneToken(candidate)) {
240
+ continue;
241
+ }
242
+ const canonicalUnset = optionKey === "rank" ? "order" : (CREATE_OPTION_KEY_TO_UNSET_CANONICAL.get(optionKey) ?? optionKey);
243
+ appendUnsetTarget(canonicalUnset);
244
+ normalized[optionKey] = undefined;
245
+ }
246
+ for (const definition of CREATE_LEGACY_NONE_COLLECTION_NORMALIZERS) {
247
+ const entries = normalized[definition.optionKey];
248
+ if (!Array.isArray(entries) || entries.length === 0) {
249
+ continue;
250
+ }
251
+ const hasLegacy = entries.some((entry) => isLegacyNoneToken(entry));
252
+ if (!hasLegacy) {
253
+ continue;
254
+ }
255
+ const concreteEntries = entries.filter((entry) => !isLegacyNoneToken(entry));
256
+ if (concreteEntries.length > 0) {
257
+ throw new PmCliError(`Cannot mix legacy clear token "none"/"null" with concrete ${definition.valueFlag} entries. Use ${definition.clearFlag} to clear or provide explicit entries.`, EXIT_CODE.USAGE);
258
+ }
259
+ normalized[definition.optionKey] = undefined;
260
+ normalized[definition.clearFlagKey] = true;
261
+ }
262
+ return normalized;
263
+ }
240
264
  function parseOptionalString(value) {
241
265
  if (value === undefined)
242
266
  return undefined;
@@ -299,11 +323,14 @@ function parseDependencies(raw, nowValue, prefix) {
299
323
  return { values: undefined, explicitEmpty: false };
300
324
  assertNoLegacyNoneTokens(raw, "--dep", "Use --clear-deps to clear dependencies.");
301
325
  const values = raw.map((entry) => {
302
- const kv = parseCsvKv(entry, "--dep");
326
+ const trimmedEntry = entry.trim();
327
+ const kv = looksLikeStructuredEntry(trimmedEntry, ["id", "kind", "author", "created_at"])
328
+ ? parseCsvKv(entry, "--dep")
329
+ : { id: trimmedEntry, kind: "related" };
303
330
  const id = parseOptionalString(kv.id);
304
331
  const kind = parseOptionalString(kv.kind);
305
332
  if (!id || !kind) {
306
- throw new PmCliError("--dep requires id and kind", EXIT_CODE.USAGE);
333
+ throw new PmCliError("--dep requires id and kind, or a bare item id to create a related dependency", EXIT_CODE.USAGE);
307
334
  }
308
335
  if (id.trim().toLowerCase() === "undefined") {
309
336
  throw new PmCliError(`--dep id must not use placeholder token "${id}". Use --clear-deps to clear dependencies.`, EXIT_CODE.USAGE);
@@ -317,6 +344,13 @@ function parseDependencies(raw, nowValue, prefix) {
317
344
  });
318
345
  return { values, explicitEmpty: false };
319
346
  }
347
+ function looksLikeStructuredEntry(raw, keys) {
348
+ if (raw.startsWith("```") || raw.includes("\n")) {
349
+ return true;
350
+ }
351
+ const keyPattern = keys.map((key) => key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
352
+ return new RegExp(`^(?:[-*+]\\s+)?(?:${keyPattern})\\s*[:=]`, "i").test(raw);
353
+ }
320
354
  export function parseLogSeed(optionName, raw, nowValue, fallbackAuthor) {
321
355
  if (!raw || raw.length === 0)
322
356
  return { values: undefined, explicitEmpty: false };
@@ -343,17 +377,18 @@ export function parseLogSeed(optionName, raw, nowValue, fallbackAuthor) {
343
377
  kv = parseCsvKv(entry, optionName);
344
378
  }
345
379
  catch (error) {
346
- if (optionName === "--comment") {
380
+ if (optionName === "--comment" || optionName === "--note" || optionName === "--learning") {
347
381
  return buildPlainTextCommentSeed();
348
382
  }
349
383
  throw error;
350
384
  }
351
385
  const unsupportedKeys = Object.keys(kv).filter((key) => !LOG_SEED_ALLOWED_KEYS.has(key));
352
386
  if (unsupportedKeys.length > 0) {
353
- if (optionName === "--comment" && !trimmedEntry.includes(",") && !trimmedEntry.includes("\n")) {
354
- return buildPlainTextCommentSeed();
355
- }
356
- throw new PmCliError(buildInvalidLogSeedKeysMessage(optionName, unsupportedKeys), EXIT_CODE.USAGE);
387
+ return {
388
+ created_at: parseCreatedAt(kv.created_at, nowValue),
389
+ author: parseOptionalString(kv.author) ?? fallbackAuthor,
390
+ text: trimmedEntry,
391
+ };
357
392
  }
358
393
  const text = kv.text ?? "";
359
394
  if (text === "") {
@@ -372,9 +407,12 @@ export function parseFiles(raw) {
372
407
  return { values: undefined, explicitEmpty: false };
373
408
  assertNoLegacyNoneTokens(raw, "--file", "Use --clear-files to clear linked files.");
374
409
  const values = raw.map((entry) => {
375
- const kv = parseCsvKv(entry, "--file");
410
+ const trimmedEntry = entry.trim();
411
+ const kv = looksLikeStructuredEntry(trimmedEntry, ["path", "scope", "note"])
412
+ ? parseCsvKv(entry, "--file")
413
+ : { path: trimmedEntry };
376
414
  if (!kv.path) {
377
- throw new PmCliError("--file requires path=<value>", EXIT_CODE.USAGE);
415
+ throw new PmCliError("--file requires path=<value> or a bare file path", EXIT_CODE.USAGE);
378
416
  }
379
417
  return {
380
418
  path: kv.path,
@@ -559,11 +597,32 @@ export function parseTests(raw) {
559
597
  return { values: undefined, explicitEmpty: false };
560
598
  assertNoLegacyNoneTokens(raw, "--test", "Use --clear-tests to clear linked tests.");
561
599
  const values = raw.map((entry) => {
562
- const kv = parseCsvKv(entry, "--test");
600
+ const trimmedEntry = entry.trim();
601
+ const kv = looksLikeStructuredEntry(trimmedEntry, [
602
+ "command",
603
+ "path",
604
+ "scope",
605
+ "timeout",
606
+ "timeout_seconds",
607
+ "pm_context_mode",
608
+ "env_set",
609
+ "env_clear",
610
+ "shared_host_safe",
611
+ "assert_stdout_contains",
612
+ "assert_stdout_regex",
613
+ "assert_stderr_contains",
614
+ "assert_stderr_regex",
615
+ "assert_stdout_min_lines",
616
+ "assert_json_field_equals",
617
+ "assert_json_field_gte",
618
+ "note",
619
+ ])
620
+ ? parseCsvKv(entry, "--test")
621
+ : { command: trimmedEntry };
563
622
  const command = parseOptionalString(kv.command);
564
623
  const filePath = parseOptionalString(kv.path);
565
624
  if (!command) {
566
- throw new PmCliError("--test requires command=<value> (path=<value> is optional metadata)", EXIT_CODE.USAGE);
625
+ throw new PmCliError("--test requires command=<value> or a bare command (path=<value> is optional metadata)", EXIT_CODE.USAGE);
567
626
  }
568
627
  const timeoutSecondsRaw = parseOptionalString(kv.timeout_seconds);
569
628
  const timeoutAliasRaw = parseOptionalString(kv.timeout);
@@ -597,9 +656,12 @@ export function parseDocs(raw) {
597
656
  return { values: undefined, explicitEmpty: false };
598
657
  assertNoLegacyNoneTokens(raw, "--doc", "Use --clear-docs to clear linked docs.");
599
658
  const values = raw.map((entry) => {
600
- const kv = parseCsvKv(entry, "--doc");
659
+ const trimmedEntry = entry.trim();
660
+ const kv = looksLikeStructuredEntry(trimmedEntry, ["path", "scope", "note"])
661
+ ? parseCsvKv(entry, "--doc")
662
+ : { path: trimmedEntry };
601
663
  if (!kv.path) {
602
- throw new PmCliError("--doc requires path=<value>", EXIT_CODE.USAGE);
664
+ throw new PmCliError("--doc requires path=<value> or a bare doc path", EXIT_CODE.USAGE);
603
665
  }
604
666
  return {
605
667
  path: kv.path,
@@ -705,9 +767,9 @@ function parseEvents(raw, nowValue) {
705
767
  const referenceDate = new Date(nowValue);
706
768
  const values = raw.map((entry) => {
707
769
  const kv = parseCsvKv(entry, "--event");
708
- const startRaw = parseOptionalString(kv.start)?.trim();
770
+ const startRaw = parseOptionalString(kv.start ?? kv.date)?.trim();
709
771
  if (!startRaw) {
710
- throw new PmCliError("--event requires start=<iso|relative>", EXIT_CODE.USAGE);
772
+ throw new PmCliError("--event requires start=<iso|relative> or date=<iso|relative>", EXIT_CODE.USAGE);
711
773
  }
712
774
  const startAt = resolveIsoOrRelative(startRaw, referenceDate, "event.start");
713
775
  const endRaw = parseOptionalString(kv.end)?.trim();
@@ -1087,21 +1149,20 @@ function ensureInitHasRun(pmRoot) {
1087
1149
  });
1088
1150
  }
1089
1151
  export async function runCreate(options, global) {
1090
- let resolvedOptions = await resolveCreateStdinInputs(options);
1152
+ let resolvedOptions = normalizeLegacyNoneCreateOptions(await resolveCreateStdinInputs(options));
1091
1153
  const pmRoot = resolvePmRoot(process.cwd(), global.path);
1092
1154
  await ensureInitHasRun(pmRoot);
1093
1155
  const settings = await readSettings(pmRoot);
1094
1156
  const statusRegistry = resolveRuntimeStatusRegistry(settings.schema);
1095
1157
  const runtimeFieldRegistry = resolveRuntimeFieldRegistry(settings.schema);
1096
1158
  const typeRegistry = resolveItemTypeRegistry(settings, getActiveExtensionRegistrations());
1097
- assertNoLegacyNoneToken(resolvedOptions.template, "--template", "Omit --template to skip template usage.");
1098
1159
  if (resolvedOptions.template !== undefined) {
1099
1160
  const templateName = resolvedOptions.template.trim();
1100
1161
  if (templateName.length === 0) {
1101
1162
  throw new PmCliError("--template must not be empty. Omit --template to disable template usage.", EXIT_CODE.USAGE);
1102
1163
  }
1103
1164
  const templateOptions = await loadCreateTemplateOptions(pmRoot, templateName);
1104
- resolvedOptions = mergeCreateOptionsWithTemplate(templateOptions, resolvedOptions);
1165
+ resolvedOptions = normalizeLegacyNoneCreateOptions(mergeCreateOptionsWithTemplate(templateOptions, resolvedOptions));
1105
1166
  }
1106
1167
  if (resolvedOptions.type === undefined) {
1107
1168
  throw new PmCliError("Missing required option --type <value>", EXIT_CODE.USAGE);