@heyanon-arp/cli 0.0.20 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -729,12 +729,11 @@ var init_api = __esm({
729
729
  // src/cli.ts
730
730
  var import_shield3 = require("@heyanon-arp/shield");
731
731
  var import_commander = require("commander");
732
- var import_simple_update_notifier = __toESM(require("simple-update-notifier"));
733
732
 
734
733
  // package.json
735
734
  var package_default = {
736
735
  name: "@heyanon-arp/cli",
737
- version: "0.0.20",
736
+ version: "0.0.22",
738
737
  description: "Command-line client for the Agent Relationship Protocol \u2014 register agents, sign envelopes, run escrowed work cycles on Solana.",
739
738
  license: "MIT",
740
739
  keywords: ["arp", "agent-relationship-protocol", "did", "solana", "escrow", "ed25519", "agents", "a2a", "cli"],
@@ -763,8 +762,7 @@ var package_default = {
763
762
  "@solana/web3.js": "^1.98.4",
764
763
  chalk: "^4.1.2",
765
764
  commander: "^12.1.0",
766
- prompts: "^2.4.2",
767
- "simple-update-notifier": "^2.0.0"
765
+ prompts: "^2.4.2"
768
766
  },
769
767
  devDependencies: {
770
768
  "@types/jest": "^29.5.2",
@@ -832,6 +830,16 @@ function toCliErrorJson(err, includeStack = false) {
832
830
  if (details !== void 0) out2.details = details;
833
831
  return out2;
834
832
  }
833
+ if (err instanceof Error && err.code === "OUTBOUND_BLOCKED") {
834
+ const e = err;
835
+ const reasons = e.verdict?.reasons ?? e.reasons;
836
+ const out2 = { code: e.code, message: err.message };
837
+ const details = {};
838
+ if (Array.isArray(reasons) && reasons.length > 0) details.reasons = reasons;
839
+ if (includeStack && err.stack) details.stack = err.stack;
840
+ if (Object.keys(details).length > 0) out2.details = details;
841
+ return out2;
842
+ }
835
843
  const message = err instanceof Error ? err.message : String(err);
836
844
  const out = { code: "CLI_ERROR", message };
837
845
  if (includeStack && err instanceof Error && err.stack) {
@@ -1780,9 +1788,10 @@ var UNTIL_PHASES = [
1780
1788
  ];
1781
1789
  var WAIT_DEFAULT_INTERVAL_SEC = 3;
1782
1790
  var WAIT_DEFAULT_TIMEOUT_SEC = 300;
1783
- var WAIT_ABS_MAX_SEC = 86400;
1791
+ var WAIT_ABS_MAX_SEC = 7 * 86400;
1784
1792
  var WAIT_CONTRACT_LAG_MARGIN_SEC = 300;
1785
1793
  var WAIT_CONTRACT_CAP_FALLBACK_SEC = 3600;
1794
+ var WAIT_LOCK_FINALIZATION_DEFAULT_SEC = 1200;
1786
1795
  function registerStatusCommand(root) {
1787
1796
  root.command("status").description("Where am I in the work cycle? FSM state + next-action hint for ONE relationship (signed reads)").argument("<relationship-id>", "Relationship UUID").option("--server <url>", "Override ARP server base URL").option("--from-did <did>", "Signer DID \u2014 required only if multiple agents are registered against this server").option("--from <name>", "Signer agent NAME (handle) \u2014 alternative to --from-did, resolved against your local agents").option("--json", "Machine-readable: single JSON object with the composed summary. Pipe-safe.", false).option(
1788
1797
  "--wait",
@@ -1790,8 +1799,10 @@ function registerStatusCommand(root) {
1790
1799
  false
1791
1800
  ).option(
1792
1801
  "--wait-timeout <seconds>",
1793
- `Max wall-clock seconds to wait when --wait is set (default ${WAIT_DEFAULT_TIMEOUT_SEC}; with --until it defaults to the live on-chain budget = work+review+dispute windows + margin, read from the contract). Capped at that same on-chain budget. Process exits code 124 on timeout (unix \`timeout\` convention).`,
1794
- String(WAIT_DEFAULT_TIMEOUT_SEC)
1802
+ `Max wall-clock seconds to wait when --wait is set. Default ${WAIT_DEFAULT_TIMEOUT_SEC}s, with two phase-aware exceptions when --until is set: (1) delegation.locked uses a generous lock-finalization default (on-chain confirmation + indexer projection latency, not the lifecycle windows); (2) a lifecycle phase (work/review/dispute-governed, e.g. work.responded) auto-sizes to the live on-chain budget (work+review+dispute windows + margin, read from the contract) so it tracks the windows. An explicit value is always honored as-is (it may legitimately exceed the budget \u2014 a wait can span pre-lock acceptance/funding too); only a generous 7-day wait-loop safety cap is enforced. Exit code 124 on timeout (unix \`timeout\` convention).`
1803
+ // NB: no Commander default — an omitted value must reach resolveWaitTimeoutSec
1804
+ // as `undefined` so it can phase-size the default (a literal '300' here would
1805
+ // mask the contract-budget auto-sizing for --until waits).
1795
1806
  ).option(
1796
1807
  "--wait-interval <seconds>",
1797
1808
  `Seconds between polls when --wait is set (default ${WAIT_DEFAULT_INTERVAL_SEC}). Bound to [1, 60].`,
@@ -1828,7 +1839,7 @@ async function runStatus(relationshipId, opts) {
1828
1839
  }
1829
1840
  const until = parseUntilPhase(opts.until);
1830
1841
  const waitInterval = parseWaitInterval(opts.waitInterval);
1831
- const waitTimeout = await resolveWaitTimeoutSec(api, opts.waitTimeout, { hasUntil: !!until });
1842
+ const waitTimeout = await resolveWaitTimeoutSec(api, opts.waitTimeout, { untilPhase: until });
1832
1843
  const outcome = await runWaitLoop({
1833
1844
  fetchSummary: () => composeStatus(api, sender.did, relationshipId, signer),
1834
1845
  waitIntervalSec: waitInterval,
@@ -1990,7 +2001,7 @@ function parseWaitTimeout(raw) {
1990
2001
  throw new Error(`status: --wait-timeout must be a positive integer number of seconds (got '${raw}')`);
1991
2002
  }
1992
2003
  if (n > WAIT_ABS_MAX_SEC) {
1993
- throw new Error(`status: --wait-timeout must be <= ${WAIT_ABS_MAX_SEC} seconds (24h, the max envelope TTL). Got ${n}.`);
2004
+ throw new Error(`status: --wait-timeout must be <= ${WAIT_ABS_MAX_SEC} seconds (the 7-day wait-loop safety cap). Got ${n}.`);
1994
2005
  }
1995
2006
  return n;
1996
2007
  }
@@ -2001,21 +2012,25 @@ function contractWaitCapSec(cfg) {
2001
2012
  if (![w, r, d].every((n) => Number.isFinite(n) && n > 0)) return WAIT_CONTRACT_CAP_FALLBACK_SEC;
2002
2013
  return Math.min(WAIT_ABS_MAX_SEC, w + r + d + WAIT_CONTRACT_LAG_MARGIN_SEC);
2003
2014
  }
2015
+ var LOCK_FINALIZATION_PHASES = /* @__PURE__ */ new Set(["delegation.locked"]);
2016
+ var CONTRACT_BUDGETED_PHASES = /* @__PURE__ */ new Set([
2017
+ "delegation.disputing",
2018
+ "work.requested",
2019
+ "work.responded",
2020
+ "receipt.proposed",
2021
+ "cycle.released",
2022
+ "cycle.complete"
2023
+ ]);
2004
2024
  async function resolveWaitTimeoutSec(api, raw, opts) {
2005
- let cap;
2025
+ if (raw !== void 0) return parseWaitTimeout(raw);
2026
+ if (opts.untilPhase !== void 0 && LOCK_FINALIZATION_PHASES.has(opts.untilPhase)) return WAIT_LOCK_FINALIZATION_DEFAULT_SEC;
2027
+ const contractBudgeted = opts.untilPhase !== void 0 && CONTRACT_BUDGETED_PHASES.has(opts.untilPhase);
2028
+ if (!contractBudgeted) return WAIT_DEFAULT_TIMEOUT_SEC;
2006
2029
  try {
2007
- cap = contractWaitCapSec(await api.getEscrowConfig());
2030
+ return contractWaitCapSec(await api.getEscrowConfig());
2008
2031
  } catch {
2009
- cap = WAIT_CONTRACT_CAP_FALLBACK_SEC;
2032
+ return WAIT_CONTRACT_CAP_FALLBACK_SEC;
2010
2033
  }
2011
- if (raw === void 0) return opts.hasUntil ? cap : WAIT_DEFAULT_TIMEOUT_SEC;
2012
- const n = parseWaitTimeout(raw);
2013
- if (n > cap) {
2014
- throw new Error(
2015
- `status: --wait-timeout ${n}s exceeds the live on-chain budget of ${cap}s (work+review+dispute windows + ${WAIT_CONTRACT_LAG_MARGIN_SEC}s lag, read from the contract). Nothing stays pending longer; lower it or script a loop around \`heyarp status\`.`
2016
- );
2017
- }
2018
- return n;
2019
2034
  }
2020
2035
  function parseUntilPhase(raw) {
2021
2036
  if (raw === void 0 || raw === "") return void 0;
@@ -2334,9 +2349,8 @@ function nextAction(input) {
2334
2349
  }
2335
2350
  const iAmCaller = latestReceipt.callerDid === signerDid;
2336
2351
  return {
2337
- // There is no cosign step buyer consent is the on-chain
2338
- // claim_work_payment tx, worker recourse is the
2339
- // review-window self-claim.
2352
+ // Consent is the on-chain claim_work_payment tx (the buyer's), and
2353
+ // worker recourse is the review-window self-claim.
2340
2354
  hint: iAmCaller ? `Receipt PROPOSED \u2014 you (caller): review the deliverable and approve payment ON-CHAIN with \`heyarp escrow claim ${latestReceipt.delegationId}\` (irreversible release; \`heyarp escrow dispute open ${latestReceipt.delegationId}\` within the review window if the work is unacceptable)` : `Receipt PROPOSED \u2014 counterparty (the buyer) owes the on-chain approval (\`heyarp escrow claim\`); once the review window lapses you may self-claim with \`heyarp escrow claim ${latestReceipt.delegationId}\``,
2341
2355
  owner: iAmCaller ? "me" : "counterparty",
2342
2356
  complete: false
@@ -2977,7 +2991,7 @@ function registerOffer(parent) {
2977
2991
  'Block after delivery until the named FSM phase is reached (e.g. delegation.accepted). One of the UNTIL_PHASES from `heyarp status --help`. Exit code 124 on --wait-timeout. Recovers the "sub-agent exits before counterparty accepts" antipattern.'
2978
2992
  ).option(
2979
2993
  "--wait-timeout <seconds>",
2980
- "When --wait-until is set: max wall-clock wait. Omitted \u2192 auto-sized to the live on-chain budget (work+review+dispute windows + margin, read from the contract); explicit values are capped at that budget. Exit code 124 on timeout."
2994
+ "When --wait-until is set: max wall-clock wait. Omitted \u2192 delegation.locked uses a generous lock-finalization default (on-chain confirmation + indexer projection); later lifecycle phases (work/review/dispute-governed) auto-size to the live on-chain budget (read from the contract); other phases default to 300s. An explicit value is always honored as-is (a wait may span pre-lock acceptance/funding, so it can exceed the budget), bounded only by a generous 7-day wait-loop safety cap. Exit code 124 on timeout."
2981
2995
  ).option("--wait-interval <seconds>", "When --wait-until is set: poll cadence (default 3, bound [1, 60]).").option(
2982
2996
  "--wait-verbose",
2983
2997
  'When --wait-until is set: emit one dim line per poll tick showing the current FSM state. Useful for "is it alive or stuck?" diagnosis on long blocks.',
@@ -3204,7 +3218,7 @@ After the worker accepts, fund the escrow lock:`));
3204
3218
  untilPhase,
3205
3219
  waitIntervalSec: parseWaitInterval(opts.waitInterval),
3206
3220
  // Cap/default the wait against the live on-chain windows (work+review+dispute).
3207
- waitTimeoutSec: await resolveWaitTimeoutSec(api, opts.waitTimeout, { hasUntil: true }),
3221
+ waitTimeoutSec: await resolveWaitTimeoutSec(api, opts.waitTimeout, { untilPhase }),
3208
3222
  waitVerbose: !!opts.waitVerbose,
3209
3223
  json: false
3210
3224
  // delegation offer is a human-text command (printIngestResult is human-text); JSON mode would be a follow-up.
@@ -3230,7 +3244,7 @@ function registerFund(parent) {
3230
3244
  "Block after delivery until the named FSM phase is reached (typically delegation.locked \u2014 resolves once the escrow lock confirms on chain). One of the UNTIL_PHASES from `heyarp status --help`. Exit code 124 on --wait-timeout."
3231
3245
  ).option(
3232
3246
  "--wait-timeout <seconds>",
3233
- "When --wait-until is set: max wall-clock wait. Omitted \u2192 auto-sized to the live on-chain budget (work+review+dispute windows + margin, read from the contract); explicit values are capped at that budget. Exit code 124 on timeout."
3247
+ "When --wait-until is set: max wall-clock wait. Omitted \u2192 delegation.locked uses a generous lock-finalization default (on-chain confirmation + indexer projection); later lifecycle phases (work/review/dispute-governed) auto-size to the live on-chain budget (read from the contract); other phases default to 300s. An explicit value is always honored as-is (a wait may span pre-lock acceptance/funding, so it can exceed the budget), bounded only by a generous 7-day wait-loop safety cap. Exit code 124 on timeout."
3234
3248
  ).option("--wait-interval <seconds>", "When --wait-until is set: poll cadence (default 3, bound [1, 60]).").option("--wait-verbose", "When --wait-until is set: emit one dim line per poll tick showing the current FSM state.", false).action(async (delegationId, opts) => {
3235
3249
  await runFund(delegationId, opts);
3236
3250
  });
@@ -3333,7 +3347,7 @@ async function runFund(delegationId, opts) {
3333
3347
  // slow, so an omitted timeout auto-sizes to the live on-chain
3334
3348
  // budget (work+review+dispute + margin) — generous by design;
3335
3349
  // a timeout here is almost never a failure, just a slow chain.
3336
- waitTimeoutSec: await resolveWaitTimeoutSec(api, opts.waitTimeout, { hasUntil: true }),
3350
+ waitTimeoutSec: await resolveWaitTimeoutSec(api, opts.waitTimeout, { untilPhase }),
3337
3351
  waitVerbose: !!opts.waitVerbose,
3338
3352
  json: false
3339
3353
  });
@@ -6396,8 +6410,8 @@ async function runPropose(recipientDid, delegationId, requestHashArg, responseHa
6396
6410
  );
6397
6411
  }
6398
6412
  recipientDid = await resolveRecipient(opts.server, "receipt propose", recipientDid, { json: opts.json });
6399
- delegationId = requireUuidNormalised2("receipt propose", delegationId, "<delegation-id>");
6400
- if (opts.relId) opts.relId = requireUuidNormalised2("receipt propose", opts.relId, "--rel-id");
6413
+ delegationId = requireUuidNormalised("receipt propose", delegationId, "<delegation-id>");
6414
+ if (opts.relId) opts.relId = requireUuidNormalised("receipt propose", opts.relId, "--rel-id");
6401
6415
  const verdict = parseVerdict("receipt propose", opts.verdict);
6402
6416
  const ttlSeconds = parseTtl2("receipt propose", opts.ttl);
6403
6417
  if (opts.notesHash) requireSha256("receipt propose", opts.notesHash, "--notes-hash");
@@ -6658,10 +6672,6 @@ function parseInteger(cmdName, flag, raw) {
6658
6672
  }
6659
6673
  return n;
6660
6674
  }
6661
- function requireUuidNormalised2(cmdName, raw, label) {
6662
- requireUuid3(cmdName, raw, label);
6663
- return raw.toLowerCase();
6664
- }
6665
6675
  function requireUuid3(cmdName, raw, label) {
6666
6676
  requireUuid(cmdName, raw, label);
6667
6677
  }
@@ -7897,7 +7907,7 @@ async function runRequest(recipientDid, delegationId, opts) {
7897
7907
  );
7898
7908
  }
7899
7909
  recipientDid = await resolveRecipient(opts.server, "work request", recipientDid, { json: opts.json });
7900
- delegationId = requireUuidNormalised3("work request", delegationId, "<delegation-id>");
7910
+ delegationId = requireUuidNormalised("work request", delegationId, "<delegation-id>");
7901
7911
  const ttlSeconds = parseTtl5("work request", opts.ttl);
7902
7912
  const params = parseParamsInput("work request", opts);
7903
7913
  const requestId = parseRequestId("work request", opts.requestId);
@@ -7954,8 +7964,8 @@ async function runRespond(relationshipId, delegationId, requestId, opts) {
7954
7964
  "work respond: --verbose and --json are mutually exclusive. --json emits the structured server response; --verbose adds dumps that would break `--json | jq`."
7955
7965
  );
7956
7966
  }
7957
- relationshipId = requireUuidNormalised3("work respond", relationshipId, "<relationship-id>");
7958
- delegationId = requireUuidNormalised3("work respond", delegationId, "<delegation-id>");
7967
+ relationshipId = requireUuidNormalised("work respond", relationshipId, "<relationship-id>");
7968
+ delegationId = requireUuidNormalised("work respond", delegationId, "<delegation-id>");
7959
7969
  const ttlSeconds = parseTtl5("work respond", opts.ttl);
7960
7970
  const responsePayload = parseResponsePayload("work respond", opts);
7961
7971
  const api = new ArpApiClient(opts.server);
@@ -8098,13 +8108,13 @@ function parseParamsInput(cmdName, opts) {
8098
8108
  return parseJsonObject(cmdName, "--params", opts.params ?? "{}");
8099
8109
  }
8100
8110
  function readJsonObjectFile(cmdName, flagName, path) {
8101
- const { existsSync: existsSync7, readFileSync: readFileSync9 } = require("fs");
8111
+ const { existsSync: existsSync7, readFileSync: readFileSync10 } = require("fs");
8102
8112
  if (!existsSync7(path)) {
8103
8113
  throw new Error(`${cmdName}: ${flagName} file not found at ${path}`);
8104
8114
  }
8105
8115
  let raw;
8106
8116
  try {
8107
- raw = readFileSync9(path, "utf8");
8117
+ raw = readFileSync10(path, "utf8");
8108
8118
  } catch (err) {
8109
8119
  const detail = err instanceof Error ? err.message : String(err);
8110
8120
  throw new Error(`${cmdName}: failed to read ${flagName} (${path}): ${detail}`);
@@ -8159,13 +8169,6 @@ function parseRequestId(cmdName, raw) {
8159
8169
  }
8160
8170
  return raw;
8161
8171
  }
8162
- function requireUuidNormalised3(cmdName, raw, label) {
8163
- requireUuid4(cmdName, raw, label);
8164
- return raw.toLowerCase();
8165
- }
8166
- function requireUuid4(cmdName, raw, label) {
8167
- requireUuid(cmdName, raw, label);
8168
- }
8169
8172
 
8170
8173
  // src/commands/work-list.ts
8171
8174
  var import_sdk32 = require("@heyanon-arp/sdk");
@@ -8324,19 +8327,104 @@ function unknownOptionHint(program, err) {
8324
8327
  }
8325
8328
  }
8326
8329
 
8327
- // src/cli.ts
8328
- async function checkForUpdates() {
8329
- if (package_default.private === true) return;
8330
+ // src/version-gate.ts
8331
+ var import_node_fs10 = require("fs");
8332
+ var import_node_path7 = require("path");
8333
+ init_paths();
8334
+ var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
8335
+ var REGISTRY_TIMEOUT_MS = 1500;
8336
+ var VERSION_GATE_BYPASS_ENV = "HEYARP_IGNORE_VERSION_GATE";
8337
+ var cacheFile = () => (0, import_node_path7.join)(arpHomeDir(), "update-check.json");
8338
+ function compareVersions(a, b) {
8339
+ const seg = (v, i) => Number.parseInt(v.split(".")[i] ?? "0", 10) || 0;
8340
+ for (let i = 0; i < 3; i++) {
8341
+ const d = seg(a, i) - seg(b, i);
8342
+ if (d !== 0) return d > 0 ? 1 : -1;
8343
+ }
8344
+ return 0;
8345
+ }
8346
+ function isClientBlocked(current, latest) {
8347
+ if (!latest) return false;
8348
+ return compareVersions(current, latest) < 0;
8349
+ }
8350
+ function isVersionGateExempt(argv) {
8351
+ if (argv.length === 0) return true;
8352
+ if (argv[0] === "help") return true;
8353
+ return argv.includes("--version") || argv.includes("-V") || argv.includes("--help") || argv.includes("-h");
8354
+ }
8355
+ async function fetchLatest(pkgName) {
8356
+ const ctrl = new AbortController();
8357
+ const timer = setTimeout(() => ctrl.abort(), REGISTRY_TIMEOUT_MS);
8330
8358
  try {
8331
- await (0, import_simple_update_notifier.default)({
8332
- pkg: { name: package_default.name, version: package_default.version },
8333
- updateCheckInterval: 1e3 * 60 * 60 * 24
8359
+ const res = await fetch(`https://registry.npmjs.org/-/package/${pkgName.replace("/", "%2F")}/dist-tags`, {
8360
+ signal: ctrl.signal,
8361
+ headers: { accept: "application/json" }
8334
8362
  });
8363
+ if (!res.ok) return null;
8364
+ const body = await res.json();
8365
+ return typeof body.latest === "string" ? body.latest : null;
8335
8366
  } catch {
8367
+ return null;
8368
+ } finally {
8369
+ clearTimeout(timer);
8336
8370
  }
8337
8371
  }
8372
+ async function resolveLatest(pkgName) {
8373
+ try {
8374
+ const cached = JSON.parse((0, import_node_fs10.readFileSync)(cacheFile(), "utf8"));
8375
+ if (typeof cached.latest === "string" && typeof cached.checkedAt === "number" && Date.now() - cached.checkedAt < CHECK_INTERVAL_MS) {
8376
+ return cached.latest;
8377
+ }
8378
+ } catch {
8379
+ }
8380
+ const fresh = await fetchLatest(pkgName);
8381
+ if (!fresh) return null;
8382
+ try {
8383
+ const file = cacheFile();
8384
+ (0, import_node_fs10.mkdirSync)((0, import_node_path7.dirname)(file), { recursive: true });
8385
+ (0, import_node_fs10.writeFileSync)(file, JSON.stringify({ checkedAt: Date.now(), latest: fresh }));
8386
+ } catch {
8387
+ }
8388
+ return fresh;
8389
+ }
8390
+ async function enforceMinClientVersion(pkg, argv) {
8391
+ if (process.env[VERSION_GATE_BYPASS_ENV]) return;
8392
+ if (isVersionGateExempt(argv)) return;
8393
+ let latest;
8394
+ try {
8395
+ latest = await resolveLatest(pkg.name);
8396
+ } catch {
8397
+ return;
8398
+ }
8399
+ if (!isClientBlocked(pkg.version, latest)) return;
8400
+ const update = `npm i -g ${pkg.name}@latest`;
8401
+ if (argv.includes("--json")) {
8402
+ console.error(
8403
+ JSON.stringify({
8404
+ code: "CLI_VERSION_TOO_OLD",
8405
+ message: `heyarp ${pkg.version} is out of date and blocked; the current release is ${latest}. Update with \`${update}\`, then re-run.`,
8406
+ details: { current: pkg.version, latest, update, bypassEnv: VERSION_GATE_BYPASS_ENV }
8407
+ })
8408
+ );
8409
+ } else {
8410
+ process.stderr.write(
8411
+ `
8412
+ heyarp ${pkg.version} is out of date and is blocked.
8413
+ The current release is ${latest}. Update, then re-run:
8414
+
8415
+ ${update}
8416
+
8417
+ (emergency bypass: set ${VERSION_GATE_BYPASS_ENV}=1)
8418
+
8419
+ `
8420
+ );
8421
+ }
8422
+ process.exit(1);
8423
+ }
8424
+
8425
+ // src/cli.ts
8338
8426
  async function main() {
8339
- void checkForUpdates();
8427
+ await enforceMinClientVersion(package_default, process.argv.slice(2));
8340
8428
  const program = new import_commander.Command();
8341
8429
  program.name("heyarp").description("ARP \u2014 Agent Relationship Protocol CLI (talks to apps/arp-server)").version(package_default.version).usage("[global-options] <command> [options]").option("--trace", "Surface stack traces and error details on failure.", false);
8342
8430
  program.exitOverride();