fluncle 0.275.0 → 0.277.0

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 (2) hide show
  1. package/bin/fluncle.mjs +572 -337
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -60,6 +60,50 @@ var __esm = (fn, res, err) => () => {
60
60
  };
61
61
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
62
62
 
63
+ // src/output.ts
64
+ import { writeSync } from "node:fs";
65
+ function writeFdSync(fd, text) {
66
+ const buf = Buffer.from(text, "utf8");
67
+ let offset = 0;
68
+ while (offset < buf.length) {
69
+ try {
70
+ offset += writeSync(fd, buf, offset);
71
+ } catch (error) {
72
+ const code = error.code;
73
+ if (code === "EAGAIN") {
74
+ if (stopWaitingForReader()) {
75
+ return;
76
+ }
77
+ Atomics.wait(EAGAIN_WAIT, 0, 0, 1);
78
+ continue;
79
+ }
80
+ if (code === "EPIPE") {
81
+ return;
82
+ }
83
+ throw error;
84
+ }
85
+ }
86
+ }
87
+ function isJsonFailure(value) {
88
+ return typeof value === "object" && value !== null && typeof value.code === "string" && typeof value.message === "string";
89
+ }
90
+ function printJson(value) {
91
+ writeFdSync(1, JSON.stringify(value, null, 2) + `
92
+ `);
93
+ }
94
+ var EAGAIN_WAIT, stopWaitingForReader = () => false, CliError;
95
+ var init_output = __esm(() => {
96
+ EAGAIN_WAIT = new Int32Array(new SharedArrayBuffer(4));
97
+ CliError = class CliError extends Error {
98
+ code;
99
+ constructor(code, message) {
100
+ super(message);
101
+ this.name = "CliError";
102
+ this.code = code;
103
+ }
104
+ };
105
+ });
106
+
63
107
  // ../../node_modules/dotenv/lib/main.js
64
108
  var require_main = __commonJS(function(exports, module) {
65
109
  var fs = __require("fs");
@@ -379,6 +423,42 @@ var require_main = __commonJS(function(exports, module) {
379
423
  module.exports = DotenvModule;
380
424
  });
381
425
 
426
+ // src/deadline.ts
427
+ function fireDeadlineNow2() {
428
+ state2.fire?.();
429
+ }
430
+ function deadlineAt() {
431
+ return state2.at;
432
+ }
433
+ function deadlineSignal() {
434
+ return state2.controller.signal;
435
+ }
436
+ function remainingDeadlineMs2(now = Date.now) {
437
+ return state2.at === null ? null : state2.at - now();
438
+ }
439
+ function assertBeforeDeadline(doing) {
440
+ const remaining = remainingDeadlineMs2();
441
+ if (remaining !== null && remaining <= 0) {
442
+ fireDeadlineNow2();
443
+ throw new CliError("timeout", `the deadline passed while ${doing}`);
444
+ }
445
+ }
446
+ function noteProgress2(note) {
447
+ state2.note = note;
448
+ }
449
+ var state2;
450
+ var init_deadline = __esm(() => {
451
+ init_output();
452
+ state2 = {
453
+ at: null,
454
+ cancel: null,
455
+ controller: new AbortController,
456
+ fire: null,
457
+ fired: false,
458
+ note: null
459
+ };
460
+ });
461
+
382
462
  // src/env.ts
383
463
  var exports_env = {};
384
464
  __export(exports_env, {
@@ -422,15 +502,22 @@ function resolveTokenRef() {
422
502
  if (process.env.FLUNCLE_API_TOKEN !== undefined || !ref || false) {
423
503
  return;
424
504
  }
505
+ assertBeforeDeadline("reading FLUNCLE_API_TOKEN_REF with op");
506
+ const remaining = remainingDeadlineMs2();
507
+ const timeout = remaining === null ? OP_READ_TIMEOUT_MS : Math.max(1, Math.min(OP_READ_TIMEOUT_MS, remaining));
425
508
  try {
426
509
  const secretRef = ref.startsWith("op://") ? ref : `op://${ref}`;
427
510
  process.env.FLUNCLE_API_TOKEN = execFileSync("op", ["read", "--no-newline", secretRef], {
428
511
  encoding: "utf8",
429
- stdio: ["ignore", "pipe", "pipe"]
512
+ stdio: ["ignore", "pipe", "pipe"],
513
+ timeout
430
514
  });
431
- } catch {
432
- throw new Error(`Could not read FLUNCLE_API_TOKEN_REF (${ref}) with op.`);
515
+ } catch (error) {
516
+ assertBeforeDeadline("reading FLUNCLE_API_TOKEN_REF with op");
517
+ const timedOut = error.code === "ETIMEDOUT";
518
+ throw new Error(timedOut ? `op did not return FLUNCLE_API_TOKEN_REF (${ref}) within ${Math.ceil(timeout / 1000)} s; unlock 1Password or set FLUNCLE_API_TOKEN.` : `Could not read FLUNCLE_API_TOKEN_REF (${ref}) with op.`);
433
519
  }
520
+ assertBeforeDeadline("reading FLUNCLE_API_TOKEN_REF with op");
434
521
  }
435
522
  function loadConfig() {
436
523
  const profile = getEnvProfile();
@@ -451,8 +538,9 @@ function getEnvProfile() {
451
538
  function isEnvProfile2(value) {
452
539
  return envProfiles2.includes(value);
453
540
  }
454
- var import_dotenv2, envProfiles2, defaultEnvProfile = "production", loadedProfile;
541
+ var import_dotenv2, envProfiles2, defaultEnvProfile = "production", OP_READ_TIMEOUT_MS = 20000, loadedProfile;
455
542
  var init_env = __esm(() => {
543
+ init_deadline();
456
544
  import_dotenv2 = __toESM(require_main(), 1);
457
545
  envProfiles2 = ["local", "production"];
458
546
  });
@@ -591,43 +679,6 @@ var init_util = __esm(() => {
591
679
  TIKTOK_DRAFT_STALE_MS = 24 * 60 * 60 * 1000;
592
680
  });
593
681
 
594
- // src/output.ts
595
- import { writeSync as writeSync2 } from "node:fs";
596
- function writeStdoutSync2(text) {
597
- const buf = Buffer.from(text, "utf8");
598
- let offset = 0;
599
- while (offset < buf.length) {
600
- try {
601
- offset += writeSync2(1, buf, offset);
602
- } catch (error) {
603
- if (error.code === "EAGAIN") {
604
- Atomics.wait(EAGAIN_WAIT2, 0, 0, 1);
605
- continue;
606
- }
607
- throw error;
608
- }
609
- }
610
- }
611
- function isJsonFailure(value) {
612
- return typeof value === "object" && value !== null && typeof value.code === "string" && typeof value.message === "string";
613
- }
614
- function printJson2(value) {
615
- writeStdoutSync2(JSON.stringify(value, null, 2) + `
616
- `);
617
- }
618
- var EAGAIN_WAIT2, CliError2;
619
- var init_output = __esm(() => {
620
- EAGAIN_WAIT2 = new Int32Array(new SharedArrayBuffer(4));
621
- CliError2 = class CliError2 extends Error {
622
- code;
623
- constructor(code, message) {
624
- super(message);
625
- this.name = "CliError";
626
- this.code = code;
627
- }
628
- };
629
- });
630
-
631
682
  // src/version.ts
632
683
  function normalizeVersion(version) {
633
684
  return version?.trim().replace(/^v/i, "");
@@ -651,7 +702,7 @@ function parseVersion(version) {
651
702
  var currentVersion;
652
703
  var init_version = __esm(() => {
653
704
  init_output();
654
- currentVersion = "0.275.0".trim() ? "0.275.0".trim() : "0.1.0";
705
+ currentVersion = "0.277.0".trim() ? "0.277.0".trim() : "0.1.0";
655
706
  });
656
707
 
657
708
  // src/update-notifier.ts
@@ -1332,7 +1383,7 @@ var init_Redactable = __esm(() => {
1332
1383
  });
1333
1384
 
1334
1385
  // ../../node_modules/effect/dist/Formatter.js
1335
- function format(input, options) {
1386
+ function format2(input, options) {
1336
1387
  const space = options?.space ?? 0;
1337
1388
  const ancestors = new WeakSet;
1338
1389
  const gap = !space ? "" : typeof space === "number" ? " ".repeat(space) : space;
@@ -1461,7 +1512,7 @@ var init_Inspectable = __esm(() => {
1461
1512
  return this.toJSON();
1462
1513
  },
1463
1514
  toString() {
1464
- return format(this.toJSON());
1515
+ return format2(this.toJSON());
1465
1516
  }
1466
1517
  };
1467
1518
  });
@@ -1560,7 +1611,7 @@ var EffectTypeId = `~effect/Effect`, ExitTypeId = `~effect/Exit`, effectVariance
1560
1611
  },
1561
1612
  ...makePrimitiveProto(options),
1562
1613
  toString() {
1563
- return `${options.op}(${format(this[args])})`;
1614
+ return `${options.op}(${format2(this[args])})`;
1564
1615
  },
1565
1616
  toJSON() {
1566
1617
  return {
@@ -1634,7 +1685,7 @@ var init_core = __esm(() => {
1634
1685
  };
1635
1686
  },
1636
1687
  toString() {
1637
- return format(this.toJSON(), {
1688
+ return format2(this.toJSON(), {
1638
1689
  ignoreToString: true,
1639
1690
  space: 2
1640
1691
  });
@@ -1691,7 +1742,7 @@ var init_core = __esm(() => {
1691
1742
  };
1692
1743
  }
1693
1744
  toString() {
1694
- return `Cause(${format(this.reasons)})`;
1745
+ return `Cause(${format2(this.reasons)})`;
1695
1746
  }
1696
1747
  [NodeInspectSymbol]() {
1697
1748
  return this.toJSON();
@@ -1737,7 +1788,7 @@ var init_core = __esm(() => {
1737
1788
  return pipeArguments(this, arguments);
1738
1789
  }
1739
1790
  toString() {
1740
- return format(this);
1791
+ return format2(this);
1741
1792
  }
1742
1793
  [NodeInspectSymbol]() {
1743
1794
  return this.toString();
@@ -1750,7 +1801,7 @@ var init_core = __esm(() => {
1750
1801
  this.error = error;
1751
1802
  }
1752
1803
  toString() {
1753
- return `Fail(${format(this.error)})`;
1804
+ return `Fail(${format2(this.error)})`;
1754
1805
  }
1755
1806
  toJSON() {
1756
1807
  return {
@@ -1771,7 +1822,7 @@ var init_core = __esm(() => {
1771
1822
  this.defect = defect;
1772
1823
  }
1773
1824
  toString() {
1774
- return `Die(${format(this.defect)})`;
1825
+ return `Die(${format2(this.defect)})`;
1775
1826
  }
1776
1827
  toJSON() {
1777
1828
  return {
@@ -1938,7 +1989,7 @@ var init_option = __esm(() => {
1938
1989
  return combine(hash(this._tag))(hash(this.value));
1939
1990
  },
1940
1991
  toString() {
1941
- return `some(${format(this.value)})`;
1992
+ return `some(${format2(this.value)})`;
1942
1993
  },
1943
1994
  toJSON() {
1944
1995
  return {
@@ -2011,7 +2062,7 @@ var init_result = __esm(() => {
2011
2062
  return combine(hash(this._tag))(hash(this.success));
2012
2063
  },
2013
2064
  toString() {
2014
- return `success(${format(this.success)})`;
2065
+ return `success(${format2(this.success)})`;
2015
2066
  },
2016
2067
  toJSON() {
2017
2068
  return {
@@ -2031,7 +2082,7 @@ var init_result = __esm(() => {
2031
2082
  return combine(hash(this._tag))(hash(this.failure));
2032
2083
  },
2033
2084
  toString() {
2034
- return `failure(${format(this.failure)})`;
2085
+ return `failure(${format2(this.failure)})`;
2035
2086
  },
2036
2087
  toJSON() {
2037
2088
  return {
@@ -4208,7 +4259,7 @@ var init_Cron = __esm(() => {
4208
4259
  return toPojo(this);
4209
4260
  },
4210
4261
  toString() {
4211
- return `Cron(${format(toPojo(this))})`;
4262
+ return `Cron(${format2(toPojo(this))})`;
4212
4263
  },
4213
4264
  toJSON() {
4214
4265
  const out = toPojo(this);
@@ -4518,7 +4569,7 @@ var TypeId11 = "~effect/BigDecimal", BigDecimalProto, isBigDecimal = (u) => hasP
4518
4569
  }
4519
4570
  const powerOfTen = comparisonPowersOfTen[absoluteScaleDifference] ??= bigint102 ** BigInt(absoluteScaleDifference);
4520
4571
  return scaleDifference > 0 ? compareBigInt(self.value, that.value * powerOfTen) : compareBigInt(self.value * powerOfTen, that.value);
4521
- }, sign = (n) => n.value === bigint02 ? 0 : n.value < bigint02 ? -1 : 1, format3 = (n) => {
4572
+ }, sign = (n) => n.value === bigint02 ? 0 : n.value < bigint02 ? -1 : 1, format4 = (n) => {
4522
4573
  const normalized = normalize(n);
4523
4574
  if (Math.abs(normalized.scale) >= 16) {
4524
4575
  return toExponential(normalized);
@@ -4558,7 +4609,7 @@ var init_BigDecimal = __esm(() => {
4558
4609
  return isBigDecimal(that) && compare(this, that) === 0;
4559
4610
  },
4560
4611
  toString() {
4561
- return `BigDecimal(${format3(this)})`;
4612
+ return `BigDecimal(${format4(this)})`;
4562
4613
  },
4563
4614
  toJSON() {
4564
4615
  return {
@@ -4791,7 +4842,7 @@ var init_Chunk = __esm(() => {
4791
4842
  _A: (_) => _
4792
4843
  },
4793
4844
  toString() {
4794
- return `Chunk(${format(toReadonlyArray(this))})`;
4845
+ return `Chunk(${format2(toReadonlyArray(this))})`;
4795
4846
  },
4796
4847
  toJSON() {
4797
4848
  return {
@@ -6229,7 +6280,7 @@ var init_SchemaAST = __esm(() => {
6229
6280
  constructor(literal, annotations, checks, encoding, context) {
6230
6281
  super(annotations, checks, encoding, context);
6231
6282
  if (typeof literal === "number" && !globalThis.Number.isFinite(literal)) {
6232
- throw new Error(`A numeric literal must be finite, got ${format(literal)}`);
6283
+ throw new Error(`A numeric literal must be finite, got ${format2(literal)}`);
6233
6284
  }
6234
6285
  this.literal = literal;
6235
6286
  }
@@ -7606,7 +7657,7 @@ function isPattern2(regExp, annotations) {
7606
7657
  const source = regExp.source;
7607
7658
  const flags = regExp.flags;
7608
7659
  const canExport = /^[dg]*uy?$/.test(flags);
7609
- const runtimeRegExp = flags === "" ? `new RegExp(${format(source)})` : `new RegExp(${format(source)}, ${format(flags)})`;
7660
+ const runtimeRegExp = flags === "" ? `new RegExp(${format2(source)})` : `new RegExp(${format2(source)}, ${format2(flags)})`;
7610
7661
  return isPattern(regExp, {
7611
7662
  toJsonSchema: () => canExport ? {
7612
7663
  pattern: flags.endsWith("y") ? `^(?:${source})` : source
@@ -7925,7 +7976,7 @@ var init_MutableHashMap = __esm(() => {
7925
7976
  return this.backing[Symbol.iterator]();
7926
7977
  },
7927
7978
  toString() {
7928
- return `MutableHashMap(${format(Array.from(this))})`;
7979
+ return `MutableHashMap(${format2(Array.from(this))})`;
7929
7980
  },
7930
7981
  toJSON() {
7931
7982
  return {
@@ -8835,7 +8886,7 @@ var init_MutableHashSet = __esm(() => {
8835
8886
  return Array.from(this.keyMap).map(([_]) => _)[Symbol.iterator]();
8836
8887
  },
8837
8888
  toString() {
8838
- return `MutableHashSet(${format(Array.from(this))})`;
8889
+ return `MutableHashSet(${format2(Array.from(this))})`;
8839
8890
  },
8840
8891
  toJSON() {
8841
8892
  return {
@@ -9162,7 +9213,7 @@ var init_TxChunk = __esm(() => {
9162
9213
  return this.toJSON();
9163
9214
  },
9164
9215
  toString() {
9165
- return `TxChunk(${format(toJson(this.ref))})`;
9216
+ return `TxChunk(${format2(toJson(this.ref))})`;
9166
9217
  },
9167
9218
  toJSON() {
9168
9219
  return {
@@ -9208,7 +9259,7 @@ var init_TxHashMap = __esm(() => {
9208
9259
  return toJson(this);
9209
9260
  },
9210
9261
  toString() {
9211
- return `TxHashMap(${format(toJson(this.ref))})`;
9262
+ return `TxHashMap(${format2(toJson(this.ref))})`;
9212
9263
  },
9213
9264
  toJSON() {
9214
9265
  return {
@@ -9234,7 +9285,7 @@ var init_TxHashSet = __esm(() => {
9234
9285
  return toJson(this);
9235
9286
  },
9236
9287
  toString() {
9237
- return `TxHashSet(${format(toJson(this.ref))})`;
9288
+ return `TxHashSet(${format2(toJson(this.ref))})`;
9238
9289
  },
9239
9290
  toJSON() {
9240
9291
  return {
@@ -9598,7 +9649,7 @@ async function userApiGet(path) {
9598
9649
  function userHeaders() {
9599
9650
  const stored = readUserToken();
9600
9651
  if (!stored) {
9601
- throw new CliError2("not_logged_in", "You're not signed in. Run `fluncle login` to link this device to your account.");
9652
+ throw new CliError("not_logged_in", "You're not signed in. Run `fluncle login` to link this device to your account.");
9602
9653
  }
9603
9654
  return {
9604
9655
  Authorization: `Bearer ${stored.token}`
@@ -9672,11 +9723,15 @@ function adminHeaders() {
9672
9723
  }
9673
9724
  async function apiRequest(path, init = {}) {
9674
9725
  const url = `${getApiBaseUrl()}${path}`;
9726
+ noteProgress2(`waiting for the Fluncle API: ${init.method ?? "GET"} ${path}`);
9675
9727
  return runPromise2(gen2(function* () {
9676
9728
  const { response, text } = yield* tryPromise2({
9677
9729
  catch: (cause) => new ApiTransportError({ cause }),
9678
9730
  try: async (signal) => {
9679
- const response = await fetch(url, { ...init, signal });
9731
+ const response = await fetch(url, {
9732
+ ...init,
9733
+ signal: AbortSignal.any([signal, deadlineSignal()])
9734
+ });
9680
9735
  return { response, text: await response.text() };
9681
9736
  }
9682
9737
  });
@@ -9689,7 +9744,7 @@ async function apiRequest(path, init = {}) {
9689
9744
  });
9690
9745
  }
9691
9746
  return data;
9692
- }).pipe(mapError2((error) => error._tag === "ApiTransportError" ? error.cause : new CliError2(error.code, error.message))));
9747
+ }).pipe(mapError2((error) => error._tag === "ApiTransportError" ? error.cause : new CliError(error.code, error.message))));
9693
9748
  }
9694
9749
  function parseJson2(text) {
9695
9750
  if (!text) {
@@ -9703,6 +9758,7 @@ function parseJson2(text) {
9703
9758
  var ApiTransportError, ApiResponseError;
9704
9759
  var init_api = __esm(() => {
9705
9760
  init_dist();
9761
+ init_deadline();
9706
9762
  init_env();
9707
9763
  init_output();
9708
9764
  init_user_token();
@@ -9788,7 +9844,7 @@ async function freshCommand({
9788
9844
  if (showTracks) {
9789
9845
  payload.tracks = response.tracks;
9790
9846
  }
9791
- printJson2(payload);
9847
+ printJson(payload);
9792
9848
  return;
9793
9849
  }
9794
9850
  const blocks = [];
@@ -9855,7 +9911,7 @@ async function albumsCommand({
9855
9911
  if (slug) {
9856
9912
  const response = await publicApiGet(`/api/v1/albums/${encodeURIComponent(slug)}`);
9857
9913
  if (json) {
9858
- printJson2(response);
9914
+ printJson(response);
9859
9915
  return;
9860
9916
  }
9861
9917
  const { album } = response;
@@ -9869,7 +9925,7 @@ async function albumsCommand({
9869
9925
  }
9870
9926
  const response = await publicApiGet(`/api/v1/albums?page=${page}`);
9871
9927
  if (json) {
9872
- printJson2(response);
9928
+ printJson(response);
9873
9929
  return;
9874
9930
  }
9875
9931
  printEntityIndex(response.albums, response, { plural: "albums", singular: "album" }, "albums");
@@ -9888,7 +9944,7 @@ async function artistsCommand({
9888
9944
  if (slug) {
9889
9945
  const response = await publicApiGet(`/api/v1/artists/${encodeURIComponent(slug)}`);
9890
9946
  if (json) {
9891
- printJson2(response);
9947
+ printJson(response);
9892
9948
  return;
9893
9949
  }
9894
9950
  const { artist } = response;
@@ -9902,7 +9958,7 @@ async function artistsCommand({
9902
9958
  }
9903
9959
  const response = await publicApiGet(`/api/v1/artists?page=${page}`);
9904
9960
  if (json) {
9905
- printJson2(response);
9961
+ printJson(response);
9906
9962
  return;
9907
9963
  }
9908
9964
  printEntityIndex(response.artists, response, { plural: "artists", singular: "artist" }, "artists");
@@ -9921,7 +9977,7 @@ async function labelsCommand({
9921
9977
  if (slug) {
9922
9978
  const response = await publicApiGet(`/api/v1/labels/${encodeURIComponent(slug)}`);
9923
9979
  if (json) {
9924
- printJson2(response);
9980
+ printJson(response);
9925
9981
  return;
9926
9982
  }
9927
9983
  const { label } = response;
@@ -9938,7 +9994,7 @@ async function labelsCommand({
9938
9994
  }
9939
9995
  const response = await publicApiGet(`/api/v1/labels?page=${page}`);
9940
9996
  if (json) {
9941
- printJson2(response);
9997
+ printJson(response);
9942
9998
  return;
9943
9999
  }
9944
10000
  printEntityIndex(response.labels, response, { plural: "labels", singular: "label" }, "labels");
@@ -9978,7 +10034,7 @@ async function searchCommand({
9978
10034
  }
9979
10035
  const response = await publicApiGet(`/api/v1/search/archive?${params.toString()}`);
9980
10036
  if (json) {
9981
- printJson2(response);
10037
+ printJson(response);
9982
10038
  return;
9983
10039
  }
9984
10040
  const blocks = [];
@@ -10049,7 +10105,7 @@ async function mixtapeGetCommand(idOrLogId) {
10049
10105
  const mixtapes = await mixtapeListCommand();
10050
10106
  const match = mixtapes.find((mixtape) => mixtape.id === idOrLogId || mixtape.logId === idOrLogId);
10051
10107
  if (!match) {
10052
- throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10108
+ throw new CliError("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10053
10109
  }
10054
10110
  return match;
10055
10111
  }
@@ -10071,7 +10127,7 @@ async function distributeYoutube(mixtapeId, videoPath, onProgress) {
10071
10127
  while (videoId === undefined) {
10072
10128
  attempts += 1;
10073
10129
  if (attempts > MAX_UPLOAD_ATTEMPTS) {
10074
- throw new CliError2("youtube_upload_failed", `YouTube upload did not complete after ${MAX_UPLOAD_ATTEMPTS} attempts`);
10130
+ throw new CliError("youtube_upload_failed", `YouTube upload did not complete after ${MAX_UPLOAD_ATTEMPTS} attempts`);
10075
10131
  }
10076
10132
  onProgress?.(offset > 0 ? `Resuming upload at ${formatBytes(offset)} / ${formatBytes(contentLength)}` : `Uploading video → YouTube`);
10077
10133
  const result = await putChunk({
@@ -10130,7 +10186,7 @@ async function putChunk(args) {
10130
10186
  if (response.status === 200 || response.status === 201) {
10131
10187
  const video = await response.json().catch(() => ({}));
10132
10188
  if (!video.id) {
10133
- throw new CliError2("youtube_no_video_id", "YouTube returned no video id on upload completion");
10189
+ throw new CliError("youtube_no_video_id", "YouTube returned no video id on upload completion");
10134
10190
  }
10135
10191
  return { kind: "done", videoId: video.id };
10136
10192
  }
@@ -10144,7 +10200,7 @@ async function putChunk(args) {
10144
10200
  return { kind: "reinit" };
10145
10201
  }
10146
10202
  const detail = (await response.text().catch(() => "")).slice(0, 400);
10147
- throw new CliError2("youtube_put_failed", `YouTube upload PUT failed (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`);
10203
+ throw new CliError("youtube_put_failed", `YouTube upload PUT failed (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`);
10148
10204
  }
10149
10205
  async function queryOffset(sessionUri, accessToken, contentLength) {
10150
10206
  const response = await fetch(sessionUri, {
@@ -10176,7 +10232,7 @@ function fileSize(path) {
10176
10232
  try {
10177
10233
  return statSync(path).size;
10178
10234
  } catch {
10179
- throw new CliError2("video_not_found", `Cannot read video file at ${path}`);
10235
+ throw new CliError("video_not_found", `Cannot read video file at ${path}`);
10180
10236
  }
10181
10237
  }
10182
10238
  function formatBytes(bytes) {
@@ -10200,11 +10256,11 @@ async function distributeMixcloud(mixtapeId, audioPath, onProgress, unlisted = f
10200
10256
  const mixtape = await mixtapeGetCommand(mixtapeId);
10201
10257
  const logId = mixtape.logId;
10202
10258
  if (!logId) {
10203
- throw new CliError2("mixtape_no_log_id", "The mixtape has no Log ID; mint it before distributing");
10259
+ throw new CliError("mixtape_no_log_id", "The mixtape has no Log ID; mint it before distributing");
10204
10260
  }
10205
10261
  const audio = Bun.file(audioPath);
10206
10262
  if (!await audio.exists()) {
10207
- throw new CliError2("audio_not_found", `Audio master not found: ${audioPath}`);
10263
+ throw new CliError("audio_not_found", `Audio master not found: ${audioPath}`);
10208
10264
  }
10209
10265
  const form = new FormData;
10210
10266
  form.append("mp3", audio);
@@ -10241,7 +10297,7 @@ async function distributeMixcloud(mixtapeId, audioPath, onProgress, unlisted = f
10241
10297
  }
10242
10298
  const result = parseUploadResult(uploadText);
10243
10299
  if (!result.success || !result.key) {
10244
- throw new CliError2("mixcloud_upload_rejected", `Mixcloud rejected the upload: ${result.message ?? uploadText.slice(0, 300)}`);
10300
+ throw new CliError("mixcloud_upload_rejected", `Mixcloud rejected the upload: ${result.message ?? uploadText.slice(0, 300)}`);
10245
10301
  }
10246
10302
  const externalId = result.key;
10247
10303
  const url = `https://www.mixcloud.com${result.key}`;
@@ -10279,7 +10335,7 @@ async function fetchMixcloudToken() {
10279
10335
  const response = await adminApiPost("/api/v1/admin/mixcloud/token");
10280
10336
  return response.accessToken;
10281
10337
  } catch {
10282
- throw new CliError2("mixcloud_not_connected", "Mixcloud is not connected. Run `fluncle admin auth mixcloud` to authorize it.");
10338
+ throw new CliError("mixcloud_not_connected", "Mixcloud is not connected. Run `fluncle admin auth mixcloud` to authorize it.");
10283
10339
  }
10284
10340
  }
10285
10341
  async function fetchCover(logId) {
@@ -10309,9 +10365,9 @@ function parseUploadResult(body) {
10309
10365
  }
10310
10366
  function throwMixcloudError(status, body) {
10311
10367
  if (body.includes("An invalid access token was provided")) {
10312
- throw new CliError2("mixcloud_invalid_token", "Mixcloud rejected the access token. Re-auth with `fluncle admin auth mixcloud`.");
10368
+ throw new CliError("mixcloud_invalid_token", "Mixcloud rejected the access token. Re-auth with `fluncle admin auth mixcloud`.");
10313
10369
  }
10314
- throw new CliError2("mixcloud_request_failed", `Mixcloud responded ${status}${body ? `: ${body.slice(0, 300)}` : ""}`);
10370
+ throw new CliError("mixcloud_request_failed", `Mixcloud responded ${status}${body ? `: ${body.slice(0, 300)}` : ""}`);
10315
10371
  }
10316
10372
  var MIXCLOUD_API = "https://api.mixcloud.com", DESCRIPTION_MAX = 1000, PICTURE_MAX_BYTES, COVER_BASE = "https://www.fluncle.com/api/mixtape-cover";
10317
10373
  var init_mixtape_mixcloud = __esm(() => {
@@ -10333,21 +10389,21 @@ async function mixtapeUpdateCommand(id, options) {
10333
10389
  async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {}) {
10334
10390
  const mixtape = await mixtapeGetCommand(idOrLogId);
10335
10391
  if (!mixtape.id) {
10336
- throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10392
+ throw new CliError("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10337
10393
  }
10338
10394
  const both = !options.youtube && !options.mixcloud;
10339
10395
  const doYoutube = both || Boolean(options.youtube);
10340
10396
  const doMixcloud = both || Boolean(options.mixcloud);
10341
10397
  if (doYoutube && !options.video) {
10342
- throw new CliError2("missing_video", "YouTube distribution needs --video <mp4>");
10398
+ throw new CliError("missing_video", "YouTube distribution needs --video <mp4>");
10343
10399
  }
10344
10400
  if (doMixcloud && !options.audio) {
10345
- throw new CliError2("missing_audio", "Mixcloud distribution needs --audio <file>");
10401
+ throw new CliError("missing_audio", "Mixcloud distribution needs --audio <file>");
10346
10402
  }
10347
10403
  const mixtapeId = mixtape.id;
10348
10404
  const logId = mixtape.logId;
10349
10405
  if (!logId) {
10350
- throw new CliError2("mixtape_not_promoted", `${mixtapeId} has no coordinate yet. Promote its recording first:
10406
+ throw new CliError("mixtape_not_promoted", `${mixtapeId} has no coordinate yet. Promote its recording first:
10351
10407
  ` + " fluncle admin recordings promote <recordingId>");
10352
10408
  }
10353
10409
  if (!mixtape.durationMs) {
@@ -10366,7 +10422,7 @@ async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {
10366
10422
  const results = [];
10367
10423
  if (doYoutube) {
10368
10424
  if (!options.video) {
10369
- throw new CliError2("missing_video", "YouTube distribution needs --video <mp4>");
10425
+ throw new CliError("missing_video", "YouTube distribution needs --video <mp4>");
10370
10426
  }
10371
10427
  onProgress("YouTube: uploading video…");
10372
10428
  await Promise.resolve().then(() => init_mixtape_youtube());
@@ -10376,7 +10432,7 @@ async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {
10376
10432
  }
10377
10433
  if (doMixcloud) {
10378
10434
  if (!options.audio) {
10379
- throw new CliError2("missing_audio", "Mixcloud distribution needs --audio <file>");
10435
+ throw new CliError("missing_audio", "Mixcloud distribution needs --audio <file>");
10380
10436
  }
10381
10437
  onProgress("Mixcloud: uploading audio…");
10382
10438
  await Promise.resolve().then(() => init_mixtape_mixcloud());
@@ -10389,10 +10445,10 @@ async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {
10389
10445
  async function mixtapeResyncCommand(idOrLogId, options, onProgress = () => {}) {
10390
10446
  const mixtape = await mixtapeGetCommand(idOrLogId);
10391
10447
  if (!mixtape.id) {
10392
- throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10448
+ throw new CliError("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10393
10449
  }
10394
10450
  if (!mixtape.logId) {
10395
- throw new CliError2("mixtape_no_log_id", "The mixtape isn't published yet. Distribute it before re-syncing.");
10451
+ throw new CliError("mixtape_no_log_id", "The mixtape isn't published yet. Distribute it before re-syncing.");
10396
10452
  }
10397
10453
  const mixtapeId = mixtape.id;
10398
10454
  const explicit = Boolean(options.youtube) || Boolean(options.mixcloud);
@@ -10404,7 +10460,7 @@ async function mixtapeResyncCommand(idOrLogId, options, onProgress = () => {}) {
10404
10460
  doYoutube = platforms.has("youtube");
10405
10461
  doMixcloud = platforms.has("mixcloud");
10406
10462
  if (!doYoutube && !doMixcloud) {
10407
- throw new CliError2("mixtape_not_distributed", "The mixtape has no YouTube or Mixcloud link to re-sync.");
10463
+ throw new CliError("mixtape_not_distributed", "The mixtape has no YouTube or Mixcloud link to re-sync.");
10408
10464
  }
10409
10465
  }
10410
10466
  const results = [];
@@ -10449,7 +10505,7 @@ function buildBody(options) {
10449
10505
  if (options.durationMs !== undefined) {
10450
10506
  const parsed = parseDuration(options.durationMs);
10451
10507
  if (parsed === null) {
10452
- throw new CliError2("invalid_duration", "Duration must be mm:ss or h:mm:ss, or a millisecond count");
10508
+ throw new CliError("invalid_duration", "Duration must be mm:ss or h:mm:ss, or a millisecond count");
10453
10509
  }
10454
10510
  body.durationMs = parsed;
10455
10511
  }
@@ -10478,7 +10534,7 @@ var init_format = __esm(() => {
10478
10534
  import { createInterface } from "node:readline/promises";
10479
10535
  async function promptLine(label, nonInteractiveMessage) {
10480
10536
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
10481
- throw new CliError2("not_interactive", nonInteractiveMessage);
10537
+ throw new CliError("not_interactive", nonInteractiveMessage);
10482
10538
  }
10483
10539
  const rl = createInterface({
10484
10540
  input: process.stdin,
@@ -10494,7 +10550,7 @@ async function selectWithKeyboard(items, options) {
10494
10550
  const stdin = options.input ?? process.stdin;
10495
10551
  const stdout = options.output ?? process.stdout;
10496
10552
  if (!stdin.isTTY || !stdout.isTTY) {
10497
- throw new CliError2("not_interactive", options.nonInteractiveMessage);
10553
+ throw new CliError("not_interactive", options.nonInteractiveMessage);
10498
10554
  }
10499
10555
  let selectedIndex = 0;
10500
10556
  let renderedLines = 0;
@@ -10581,7 +10637,7 @@ async function openExternal(target) {
10581
10637
  const command = platformOpenCommand();
10582
10638
  if (!command) {
10583
10639
  console.log(target);
10584
- throw new CliError2("unsupported_platform", `Automatic opening is only supported on macOS and Linux. Open this manually: ${target}`);
10640
+ throw new CliError("unsupported_platform", `Automatic opening is only supported on macOS and Linux. Open this manually: ${target}`);
10585
10641
  }
10586
10642
  const child = Bun.spawn([command, target], {
10587
10643
  stderr: "pipe",
@@ -10590,7 +10646,7 @@ async function openExternal(target) {
10590
10646
  const exitCode = await child.exited;
10591
10647
  if (exitCode !== 0) {
10592
10648
  const stderr = await new Response(child.stderr).text();
10593
- throw new CliError2("open_failed", stderr.trim() || `Could not open ${target} with ${command}`);
10649
+ throw new CliError("open_failed", stderr.trim() || `Could not open ${target} with ${command}`);
10594
10650
  }
10595
10651
  }
10596
10652
  function platformOpenCommand() {
@@ -10665,7 +10721,7 @@ async function openRecentCommand(options) {
10665
10721
  function spotifyPlaylistUrlToAppUri(playlistUrl) {
10666
10722
  const playlistId = playlistUrl.match(/open\.spotify\.com\/playlist\/([^/?#]+)/)?.[1];
10667
10723
  if (!playlistId) {
10668
- throw new CliError2("invalid_playlist_url", `Could not parse Spotify playlist URL: ${playlistUrl}`);
10724
+ throw new CliError("invalid_playlist_url", `Could not parse Spotify playlist URL: ${playlistUrl}`);
10669
10725
  }
10670
10726
  return `spotify:playlist:${playlistId}`;
10671
10727
  }
@@ -10714,13 +10770,13 @@ var init_random = __esm(() => {
10714
10770
  async function subscribeCommand(email, json) {
10715
10771
  const address = email?.trim() || await promptLine("Email: ", PROMPT_NON_INTERACTIVE_MESSAGE);
10716
10772
  if (!address) {
10717
- throw new CliError2("invalid_email", "Enter a valid email address.");
10773
+ throw new CliError("invalid_email", "Enter a valid email address.");
10718
10774
  }
10719
10775
  await publicApiPost("/api/v1/newsletter", {
10720
10776
  email: address
10721
10777
  });
10722
10778
  if (json) {
10723
- printJson2({
10779
+ printJson({
10724
10780
  ok: true
10725
10781
  });
10726
10782
  return;
@@ -10738,11 +10794,11 @@ var init_subscribe = __esm(() => {
10738
10794
  async function submitCommand(input) {
10739
10795
  const query = input?.trim() || await promptLine("Track or Spotify link: ", PROMPT_NON_INTERACTIVE_MESSAGE2);
10740
10796
  if (!query) {
10741
- throw new CliError2("missing_query", "Missing search input");
10797
+ throw new CliError("missing_query", "Missing search input");
10742
10798
  }
10743
10799
  const response = await publicApiGet(`/api/v1/search?q=${encodeURIComponent(query)}`);
10744
10800
  if (response.results.length === 0) {
10745
- throw new CliError2("no_results", "Nothing came back for that search. Try the artist and the title.");
10801
+ throw new CliError("no_results", "Nothing came back for that search. Try the artist and the title.");
10746
10802
  }
10747
10803
  const selected = await selectCandidate(response.results);
10748
10804
  if (!selected) {
@@ -10844,7 +10900,7 @@ async function requestDeviceCode(baseUrl) {
10844
10900
  return;
10845
10901
  });
10846
10902
  if (!response.ok || !data?.device_code || !data.user_code) {
10847
- throw new CliError2("device_code_failed", `Couldn't start the login flow (${response.status}). Try again in a moment.`);
10903
+ throw new CliError("device_code_failed", `Couldn't start the login flow (${response.status}). Try again in a moment.`);
10848
10904
  }
10849
10905
  return data;
10850
10906
  }
@@ -10876,15 +10932,15 @@ async function pollForToken(baseUrl, device) {
10876
10932
  intervalMs += 5000;
10877
10933
  break;
10878
10934
  case "access_denied":
10879
- throw new CliError2("access_denied", "Sign-in was denied in the browser. Nothing linked.");
10935
+ throw new CliError("access_denied", "Sign-in was denied in the browser. Nothing linked.");
10880
10936
  case "expired_token":
10881
- throw new CliError2("expired_token", "The login request timed out. Run `fluncle login` again.");
10937
+ throw new CliError("expired_token", "The login request timed out. Run `fluncle login` again.");
10882
10938
  default:
10883
- throw new CliError2("device_token_failed", pending?.error_description ?? `Login failed (${response.status}).`);
10939
+ throw new CliError("device_token_failed", pending?.error_description ?? `Login failed (${response.status}).`);
10884
10940
  }
10885
10941
  await sleep3(intervalMs);
10886
10942
  }
10887
- throw new CliError2("expired_token", "The login request timed out. Run `fluncle login` again.");
10943
+ throw new CliError("expired_token", "The login request timed out. Run `fluncle login` again.");
10888
10944
  }
10889
10945
  async function fetchSessionUser(baseUrl, token) {
10890
10946
  const response = await fetch(`${baseUrl}/api/v1/me`, {
@@ -11020,7 +11076,7 @@ var init_about = __esm(() => {
11020
11076
  async function versionCommand(options) {
11021
11077
  const result = options.check ? await buildVersionCheck() : buildCurrentVersion();
11022
11078
  if (options.json) {
11023
- printJson2({
11079
+ printJson({
11024
11080
  ok: true,
11025
11081
  ...result
11026
11082
  });
@@ -11086,7 +11142,7 @@ function parseVersion2(version) {
11086
11142
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
11087
11143
  var init_version2 = __esm(() => {
11088
11144
  init_output();
11089
- currentVersion2 = "0.275.0".trim() ? "0.275.0".trim() : "0.1.0";
11145
+ currentVersion2 = "0.277.0".trim() ? "0.277.0".trim() : "0.1.0";
11090
11146
  });
11091
11147
 
11092
11148
  // ../../packages/registry/src/index.ts
@@ -12605,7 +12661,7 @@ var init_src = __esm(() => {
12605
12661
  ],
12606
12662
  kind: "cron",
12607
12663
  name: "cron.publish-advance",
12608
- operatorNotes: "every 30m. Pure HTTP trigger, zero LLM tokens. The last autonomy gap: render finishes → this pushes, with no operator beat between. Admin tier (needs the Worker's Postiz key, which the box never sees — the box only triggers; the `drip_clips` / `capture_post_urls` precedent). SHIPS DARK: the Worker's kill switch is DEFAULT-DENY (only an explicit `false` in the `publish_advance_paused` setting runs it), so the timer ticks and posts nothing until `fluncle admin publish resume`. The Worker reads the switch FIRST, then advances at most ONE ready finding — both masters finalized, 15m settled, the whole bundle served on R2, the (track, platform) row CLAIMED atomically before any Postiz call, a rolling-24h cap of 6 pushes. A failed push is left `failed` for the operator and never auto-retried. Source: docs/agents/hermes/scripts/publish-advance-sweep.sh. Probed on /status as cron.publish-advance.",
12664
+ operatorNotes: "every 30m. Pure HTTP trigger, zero LLM tokens. The last autonomy gap: render finishes → this pushes, with no operator beat between. Admin tier (needs the Worker's Postiz key, which the box never sees — the box only triggers; the `drip_clips` / `capture_post_urls` precedent). SHIPS DARK: the Worker's kill switch is DEFAULT-DENY (only an explicit `false` in the `publish_advance_paused` setting runs it), so the timer ticks and posts nothing until `fluncle admin publish resume`. The Worker reads the switch FIRST, then advances at most ONE ready finding — both masters finalized, settled six hours, the whole bundle served on R2, the (track, platform) row CLAIMED atomically before any Postiz call, a rolling-24h cap of 4 pushes. A failed push is left `failed` for the operator and never auto-retried. Source: docs/agents/hermes/scripts/publish-advance-sweep.sh. Probed on /status as cron.publish-advance.",
12609
12665
  probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-publish-advance", kind: "cron" },
12610
12666
  statusDescription: "advances the publish queue on his own clock",
12611
12667
  title: "Publish advance",
@@ -12712,7 +12768,7 @@ var init_src = __esm(() => {
12712
12768
  ],
12713
12769
  kind: "cron",
12714
12770
  name: "cron.indexnow",
12715
- operatorNotes: "06:00 Europe/Amsterdam, after nightly hub reconciliation, with a 07:15 retry. Calls the agent-tier submit_indexnow walk, claim, and ack phases in bounded admitted windows. The box posts paced batches of 1,000, up to 10,000 URLs per daily run, with Yandex fallback on 429 and bounded Retry-After backoff. A complete walk within 12 hours skips the retry walk. HTTP 200/202 allow matching-version stamps; unaccepted versions stay due, partial rate limits remain resumable, and zero-acceptance rate limits or other rejections fail the run summary. Source: docs/agents/hermes/scripts/indexnow.* and docs/agents/hermes/indexnow-timer/.",
12771
+ operatorNotes: "06:00 Europe/Amsterdam, after nightly hub reconciliation, with a 07:15 retry. Calls the agent-tier submit_indexnow walk, claim, and ack phases in bounded admitted windows. The box posts paced batches of 1,000, up to 2,000 URLs per daily run, with Yandex fallback on 429 and bounded Retry-After backoff. A complete walk within 12 hours skips the retry walk. HTTP 200/202 allow matching-version stamps; unaccepted versions stay due, partial rate limits remain resumable, and zero-acceptance rate limits or other rejections fail the run summary. Source: docs/agents/hermes/scripts/indexnow.* and docs/agents/hermes/indexnow-timer/.",
12716
12772
  operatorOnly: "Search-engine notification is plumbing, not a listener-facing automation.",
12717
12773
  probeConfig: {
12718
12774
  cadenceMs: 24 * 60 * MINUTE_MS,
@@ -12978,7 +13034,7 @@ function checkBundleCompleteness(files) {
12978
13034
  function validateBundleCompleteness(files, onProgress, options) {
12979
13035
  const completeness = checkBundleCompleteness(files);
12980
13036
  if (completeness.uploadingFootage && completeness.missingContract.length > 0 && !options.allowPartial) {
12981
- throw new CliError2("bundle_incomplete", `Refusing to upload a PARTIAL bundle: footage is being uploaded but the re-render contract is missing ${completeness.missingContract.join(", ")}. ` + `A footage-only upload leaves composition.tsx/props.json/render.json stale on R2 and desyncs the render.json from the DB ledger. ` + `Ship the complete bundle (re-run \`ship\` and upload with --dir), or pass --allow-partial for a deliberate partial refresh (e.g. poster-only).`);
13037
+ throw new CliError("bundle_incomplete", `Refusing to upload a PARTIAL bundle: footage is being uploaded but the re-render contract is missing ${completeness.missingContract.join(", ")}. ` + `A footage-only upload leaves composition.tsx/props.json/render.json stale on R2 and desyncs the render.json from the DB ledger. ` + `Ship the complete bundle (re-run \`ship\` and upload with --dir), or pass --allow-partial for a deliberate partial refresh (e.g. poster-only).`);
12982
13038
  }
12983
13039
  if (completeness.uploadingFootage) {
12984
13040
  if (completeness.missingContract.length > 0) {
@@ -12999,7 +13055,7 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
12999
13055
  path: files[spec.option]
13000
13056
  })).filter((spec) => Boolean(spec.path));
13001
13057
  if (present.length === 0) {
13002
- throw new CliError2("nothing_to_upload", "No bundle files resolved to upload (pass --dir <bundle> or explicit file flags).");
13058
+ throw new CliError("nothing_to_upload", "No bundle files resolved to upload (pass --dir <bundle> or explicit file flags).");
13003
13059
  }
13004
13060
  const platesOnly = isPlatesOnlyUpload(files);
13005
13061
  const presign = await adminApiPost(`/api/v1/admin/tracks/${encodeURIComponent(idOrLogId)}/video/uploads`, { fields: present.map((spec) => spec.field) });
@@ -13009,7 +13065,7 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
13009
13065
  for (const spec of present) {
13010
13066
  const upload = byField.get(spec.field);
13011
13067
  if (!upload) {
13012
- throw new CliError2("presign_missing", `Worker did not sign an upload for ${spec.field}`);
13068
+ throw new CliError("presign_missing", `Worker did not sign an upload for ${spec.field}`);
13013
13069
  }
13014
13070
  onProgress?.(`Uploading ${spec.field} → ${upload.key}`);
13015
13071
  const file = Bun.file(spec.path);
@@ -13021,7 +13077,7 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
13021
13077
  });
13022
13078
  if (!response.ok) {
13023
13079
  const detail = (await response.text().catch(() => "")).slice(0, 300);
13024
- throw new CliError2("r2_put_failed", `R2 rejected ${spec.field} with ${response.status} ${response.statusText}${detail ? `: ${detail}` : ""}`);
13080
+ throw new CliError("r2_put_failed", `R2 rejected ${spec.field} with ${response.status} ${response.statusText}${detail ? `: ${detail}` : ""}`);
13025
13081
  }
13026
13082
  urls[spec.field] = `${FOUND_BASE}/${upload.key}`;
13027
13083
  }
@@ -26162,6 +26218,10 @@ var init_admin_tracks = __esm(() => {
26162
26218
  scope: TrackWorkScopeSchema.default("all")
26163
26219
  })).output(object({
26164
26220
  capabilities: TrackWorkCapabilitiesSchema.optional(),
26221
+ catalogueCapture: object({
26222
+ closedReason: _enum(["paused", "bytes_spent", "tracks_spent"]),
26223
+ open: literal(false)
26224
+ }).optional(),
26165
26225
  debtPending: boolean3().optional(),
26166
26226
  ok: literal(true),
26167
26227
  oldestQueuedCaptureOver24h: boolean3().optional(),
@@ -28970,7 +29030,7 @@ async function resolveMixtapeId(idOrLogId) {
28970
29030
  const response = await adminApiGet("/api/v1/admin/mixtapes");
28971
29031
  const match = response.mixtapes.find((mixtape) => mixtape.id === idOrLogId || mixtape.logId === idOrLogId);
28972
29032
  if (!match?.id) {
28973
- throw new CliError2("mixtape_not_found", `No mixtape with id or Log ID ${idOrLogId}`);
29033
+ throw new CliError("mixtape_not_found", `No mixtape with id or Log ID ${idOrLogId}`);
28974
29034
  }
28975
29035
  return match.id;
28976
29036
  }
@@ -29085,7 +29145,7 @@ function renditionFfmpegArgs(inputPath, outputPath) {
29085
29145
  }
29086
29146
  async function uploadRenditionMultipart(masterPath, presignPath, onProgress = () => {}) {
29087
29147
  if (!existsSync(masterPath)) {
29088
- throw new CliError2("file_not_found", `Set-video master not found: ${masterPath}`);
29148
+ throw new CliError("file_not_found", `Set-video master not found: ${masterPath}`);
29089
29149
  }
29090
29150
  await assertFfmpeg();
29091
29151
  const renditionPath = join4(tmpdir(), `fluncle-set-${randomUUID()}.mp4`);
@@ -29104,7 +29164,7 @@ async function uploadRenditionMultipart(masterPath, presignPath, onProgress = ()
29104
29164
  for (const part of plan.parts) {
29105
29165
  const url = urlByPart.get(part.partNumber);
29106
29166
  if (!url) {
29107
- throw new CliError2("presign_missing", `Worker did not sign part ${part.partNumber} of ${plan.partCount}`);
29167
+ throw new CliError("presign_missing", `Worker did not sign part ${part.partNumber} of ${plan.partCount}`);
29108
29168
  }
29109
29169
  onProgress(`Set video: part ${part.partNumber}/${plan.partCount}`);
29110
29170
  const etag = await putPart(url, renditionPath, part, onProgress);
@@ -29126,11 +29186,11 @@ async function putPart(url, path, part, onProgress = () => {}) {
29126
29186
  try {
29127
29187
  return await putPartOnce(url, body, part);
29128
29188
  } catch (error) {
29129
- if (error instanceof CliError2) {
29189
+ if (error instanceof CliError || deadlineSignal().aborted) {
29130
29190
  throw error;
29131
29191
  }
29132
29192
  if (attempt >= MAX_PART_ATTEMPTS) {
29133
- throw new CliError2("r2_part_failed", `Part ${part.partNumber} failed after ${MAX_PART_ATTEMPTS} attempts: ${error instanceof Error ? error.message : String(error)}`);
29193
+ throw new CliError("r2_part_failed", `Part ${part.partNumber} failed after ${MAX_PART_ATTEMPTS} attempts: ${error instanceof Error ? error.message : String(error)}`);
29134
29194
  }
29135
29195
  const backoffMs = 500 * 2 ** (attempt - 1);
29136
29196
  onProgress(`Set video: part ${part.partNumber} dropped, retry ${attempt}/${MAX_PART_ATTEMPTS - 1} in ${backoffMs}ms…`);
@@ -29139,17 +29199,17 @@ async function putPart(url, path, part, onProgress = () => {}) {
29139
29199
  }
29140
29200
  }
29141
29201
  async function putPartOnce(url, body, part) {
29142
- const response = await fetch(url, { body, method: "PUT" });
29202
+ const response = await fetch(url, { body, method: "PUT", signal: deadlineSignal() });
29143
29203
  if (!response.ok) {
29144
29204
  const detail = (await response.text().catch(() => "")).slice(0, 300);
29145
29205
  if (response.status < 500) {
29146
- throw new CliError2("r2_part_failed", `R2 rejected part ${part.partNumber} (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`);
29206
+ throw new CliError("r2_part_failed", `R2 rejected part ${part.partNumber} (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`);
29147
29207
  }
29148
29208
  throw new Error(`R2 ${response.status} ${response.statusText} on part ${part.partNumber}${detail ? `: ${detail}` : ""}`);
29149
29209
  }
29150
29210
  const etag = response.headers.get("etag");
29151
29211
  if (!etag) {
29152
- throw new CliError2("r2_no_etag", `R2 returned no ETag for part ${part.partNumber}`);
29212
+ throw new CliError("r2_no_etag", `R2 returned no ETag for part ${part.partNumber}`);
29153
29213
  }
29154
29214
  return etag;
29155
29215
  }
@@ -29157,15 +29217,16 @@ async function completeUpload(url, parts) {
29157
29217
  const response = await fetch(url, {
29158
29218
  body: buildCompleteXml(parts),
29159
29219
  headers: { "content-type": "application/xml" },
29160
- method: "POST"
29220
+ method: "POST",
29221
+ signal: deadlineSignal()
29161
29222
  });
29162
29223
  const text = await response.text().catch(() => "");
29163
29224
  if (!response.ok || text.includes("<Error>")) {
29164
- throw new CliError2("r2_complete_failed", `R2 CompleteMultipartUpload failed (${response.status} ${response.statusText})${text ? `: ${text.slice(0, 300)}` : ""}`);
29225
+ throw new CliError("r2_complete_failed", `R2 CompleteMultipartUpload failed (${response.status} ${response.statusText})${text ? `: ${text.slice(0, 300)}` : ""}`);
29165
29226
  }
29166
29227
  }
29167
29228
  async function abortUpload(url) {
29168
- await fetch(url, { method: "DELETE" });
29229
+ await fetch(url, { method: "DELETE", signal: AbortSignal.timeout(UPLOAD_ABORT_TIMEOUT_MS) });
29169
29230
  }
29170
29231
  async function assertFfmpeg() {
29171
29232
  try {
@@ -29175,7 +29236,7 @@ async function assertFfmpeg() {
29175
29236
  throw new Error("ffmpeg -version exited non-zero");
29176
29237
  }
29177
29238
  } catch {
29178
- throw new CliError2("ffmpeg_missing", "--set-video needs ffmpeg to derive the 1080p rendition. Install it (brew install ffmpeg).");
29239
+ throw new CliError("ffmpeg_missing", "--set-video needs ffmpeg to derive the 1080p rendition. Install it (brew install ffmpeg).");
29179
29240
  }
29180
29241
  }
29181
29242
  async function deriveRendition(inputPath, outputPath) {
@@ -29186,13 +29247,14 @@ async function deriveRendition(inputPath, outputPath) {
29186
29247
  await proc.exited;
29187
29248
  if (proc.exitCode !== 0) {
29188
29249
  const detail = (await new Response(proc.stderr).text().catch(() => "")).slice(-400);
29189
- throw new CliError2("ffmpeg_failed", `ffmpeg failed to derive the set-video rendition${detail ? `: ${detail}` : ""}`);
29250
+ throw new CliError("ffmpeg_failed", `ffmpeg failed to derive the set-video rendition${detail ? `: ${detail}` : ""}`);
29190
29251
  }
29191
29252
  }
29192
- var FOUND_BASE2 = "https://found.fluncle.com", SET_VIDEO_RENDITION, MAX_PART_ATTEMPTS = 5;
29253
+ var FOUND_BASE2 = "https://found.fluncle.com", SET_VIDEO_RENDITION, MAX_PART_ATTEMPTS = 5, UPLOAD_ABORT_TIMEOUT_MS = 5000;
29193
29254
  var init_mixtape_set_video = __esm(() => {
29194
29255
  init_multipart();
29195
29256
  init_api();
29257
+ init_deadline();
29196
29258
  init_output();
29197
29259
  SET_VIDEO_RENDITION = {
29198
29260
  audioBitrate: "192k",
@@ -29289,12 +29351,12 @@ async function clipDripPauseCommand(paused) {
29289
29351
  }
29290
29352
  async function resolveClipSource(clip) {
29291
29353
  if (!clip.recordingId) {
29292
- throw new CliError2("clip_unlinked", `Clip ${clip.id} is linked to no recording`);
29354
+ throw new CliError("clip_unlinked", `Clip ${clip.id} is linked to no recording`);
29293
29355
  }
29294
29356
  await Promise.resolve().then(() => init_recordings());
29295
29357
  const recording = await recordingGet(clip.recordingId);
29296
29358
  if (!recording.r2Key) {
29297
- throw new CliError2("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
29359
+ throw new CliError("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
29298
29360
  }
29299
29361
  return {
29300
29362
  setUrl: r2PublicUrl(FOUND_BASE3, recording.r2Key)
@@ -29304,7 +29366,7 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
29304
29366
  const clips = await clipsListCommand();
29305
29367
  const clip = clips.find((candidate) => candidate.id === clipId);
29306
29368
  if (!clip) {
29307
- throw new CliError2("clip_not_found", `No clip with id ${clipId}`);
29369
+ throw new CliError("clip_not_found", `No clip with id ${clipId}`);
29308
29370
  }
29309
29371
  const source = await resolveClipSource(clip);
29310
29372
  await assertFfmpeg2();
@@ -29320,7 +29382,7 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
29320
29382
  });
29321
29383
  const sizeBytes = statSync3(outputPath).size;
29322
29384
  if (sizeBytes > MAX_CLIP_BYTES) {
29323
- throw new CliError2("clip_too_large", `The cut is ${(sizeBytes / 1e6).toFixed(1)} MB (> 100 MB Cloudflare MT ceiling). Shorten the window or lower the bitrate cap`);
29385
+ throw new CliError("clip_too_large", `The cut is ${(sizeBytes / 1e6).toFixed(1)} MB (> 100 MB Cloudflare MT ceiling). Shorten the window or lower the bitrate cap`);
29324
29386
  }
29325
29387
  onProgress(`Clip ${clipId}: uploading ${(sizeBytes / 1e6).toFixed(1)} MB…`);
29326
29388
  const presign = await adminApiPost(`/api/v1/admin/clips/${encodeURIComponent(clipId)}/cut/presign`, { contentType: "video/mp4" });
@@ -29345,7 +29407,7 @@ async function putClip(url, contentType, path) {
29345
29407
  });
29346
29408
  if (!response.ok) {
29347
29409
  const detail = (await response.text().catch(() => "")).slice(0, 300);
29348
- throw new CliError2("r2_put_failed", `R2 rejected the clip upload (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`);
29410
+ throw new CliError("r2_put_failed", `R2 rejected the clip upload (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`);
29349
29411
  }
29350
29412
  }
29351
29413
  async function assertFfmpeg2() {
@@ -29356,12 +29418,12 @@ async function assertFfmpeg2() {
29356
29418
  throw new Error("ffmpeg -version exited non-zero");
29357
29419
  }
29358
29420
  } catch {
29359
- throw new CliError2("ffmpeg_missing", "The clip cut needs ffmpeg. Install it on the box (apt-get install -y ffmpeg).");
29421
+ throw new CliError("ffmpeg_missing", "The clip cut needs ffmpeg. Install it on the box (apt-get install -y ffmpeg).");
29360
29422
  }
29361
29423
  }
29362
29424
  async function runClipCut(options) {
29363
29425
  if (!options.setUrl) {
29364
- throw new CliError2("missing_set_url", "The clip cut needs the set rendition URL");
29426
+ throw new CliError("missing_set_url", "The clip cut needs the set rendition URL");
29365
29427
  }
29366
29428
  const proc = Bun.spawn(["ffmpeg", ...clipCutFfmpegArgs(options)], {
29367
29429
  stderr: "pipe",
@@ -29370,7 +29432,7 @@ async function runClipCut(options) {
29370
29432
  await proc.exited;
29371
29433
  if (proc.exitCode !== 0) {
29372
29434
  const detail = (await new Response(proc.stderr).text().catch(() => "")).slice(-400);
29373
- throw new CliError2("ffmpeg_failed", `ffmpeg failed to cut the clip${detail ? `: ${detail}` : ""}`);
29435
+ throw new CliError("ffmpeg_failed", `ffmpeg failed to cut the clip${detail ? `: ${detail}` : ""}`);
29374
29436
  }
29375
29437
  }
29376
29438
  var FOUND_BASE3 = "https://found.fluncle.com", CLIP_WIDTH = 1080, CLIP_HEIGHT = 1920, CLIP_CRF = 21, CLIP_MAXRATE = "10M", CLIP_BUFSIZE = "20M", CLIP_AUDIO_BITRATE = "192k", MAX_CLIP_BYTES;
@@ -29404,7 +29466,7 @@ async function recordingsListCommand(options = {}) {
29404
29466
  const suffix = query.toString() ? `?${query.toString()}` : "";
29405
29467
  const response = await adminApiGet(`/api/v1/admin/recordings${suffix}`);
29406
29468
  if (options.json) {
29407
- printJson2({ ok: true, recordings: response.recordings });
29469
+ printJson({ ok: true, recordings: response.recordings });
29408
29470
  return;
29409
29471
  }
29410
29472
  if (response.recordings.length === 0) {
@@ -29416,11 +29478,11 @@ async function recordingsListCommand(options = {}) {
29416
29478
  }
29417
29479
  async function recordingGetCommand(id, options = {}) {
29418
29480
  if (!id) {
29419
- throw new CliError2("missing_id", "Missing recording id for: get");
29481
+ throw new CliError("missing_id", "Missing recording id for: get");
29420
29482
  }
29421
29483
  const recording = await recordingGet2(id);
29422
29484
  if (options.json) {
29423
- printJson2({ ok: true, recording });
29485
+ printJson({ ok: true, recording });
29424
29486
  return;
29425
29487
  }
29426
29488
  console.log(formatRecordingSummary(recording));
@@ -29438,7 +29500,7 @@ async function recordingCreateCommand(options = {}) {
29438
29500
  });
29439
29501
  const recording = created.recording;
29440
29502
  if (options.json) {
29441
- printJson2({ ok: true, recording });
29503
+ printJson({ ok: true, recording });
29442
29504
  return;
29443
29505
  }
29444
29506
  console.log(`Plan ${recording.id} created, handle "${recording.title}"`);
@@ -29446,13 +29508,13 @@ async function recordingCreateCommand(options = {}) {
29446
29508
  }
29447
29509
  const title = options.title?.trim();
29448
29510
  if (!title) {
29449
- throw new CliError2("missing_title", "A recording needs a --title (or --plan for a videoless plan)");
29511
+ throw new CliError("missing_title", "A recording needs a --title (or --plan for a videoless plan)");
29450
29512
  }
29451
29513
  if (!options.video) {
29452
- throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
29514
+ throw new CliError("missing_video", "A recording needs a --video <file> to stage");
29453
29515
  }
29454
29516
  if (!existsSync2(options.video)) {
29455
- throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
29517
+ throw new CliError("file_not_found", `Set-video master not found: ${options.video}`);
29456
29518
  }
29457
29519
  const created = await adminApiPost("/api/v1/admin/recordings", {
29458
29520
  recordedAt: options.recordedAt,
@@ -29467,14 +29529,14 @@ async function recordingCreateCommand(options = {}) {
29467
29529
  log(`Recording ${recording.id} created, staging the set video…`);
29468
29530
  const upload = await uploadRenditionMultipart(options.video, `/api/v1/admin/recordings/${encodeURIComponent(recording.id)}/set-video/presign`, log);
29469
29531
  if (options.json) {
29470
- printJson2({ key: upload.key, ok: true, recording, url: upload.url });
29532
+ printJson({ key: upload.key, ok: true, recording, url: upload.url });
29471
29533
  return;
29472
29534
  }
29473
29535
  console.log(`Recording ${recording.id} staged → ${upload.url}`);
29474
29536
  }
29475
29537
  async function recordingUpdateCommand(id, options = {}) {
29476
29538
  if (!id) {
29477
- throw new CliError2("missing_id", "Missing recording id for: update");
29539
+ throw new CliError("missing_id", "Missing recording id for: update");
29478
29540
  }
29479
29541
  const body = {};
29480
29542
  if (options.title !== undefined) {
@@ -29488,66 +29550,66 @@ async function recordingUpdateCommand(id, options = {}) {
29488
29550
  }
29489
29551
  if (options.tracklistFile !== undefined) {
29490
29552
  if (!existsSync2(options.tracklistFile)) {
29491
- throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
29553
+ throw new CliError("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
29492
29554
  }
29493
29555
  try {
29494
29556
  body.tracklistJson = JSON.parse(readFileSync2(options.tracklistFile, "utf8"));
29495
29557
  } catch {
29496
- throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
29558
+ throw new CliError("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
29497
29559
  }
29498
29560
  }
29499
29561
  if (Object.keys(body).length === 0) {
29500
- throw new CliError2("no_fields", "Nothing to update. Pass --title, --recorded-at, --parent-id, or --tracklist-file");
29562
+ throw new CliError("no_fields", "Nothing to update. Pass --title, --recorded-at, --parent-id, or --tracklist-file");
29501
29563
  }
29502
29564
  const response = await adminApiPatch(`/api/v1/admin/recordings/${encodeURIComponent(id)}`, body);
29503
29565
  if (options.json) {
29504
- printJson2({ ok: true, recording: response.recording });
29566
+ printJson({ ok: true, recording: response.recording });
29505
29567
  return;
29506
29568
  }
29507
29569
  console.log(`Updated ${formatRecordingSummary(response.recording)}`);
29508
29570
  }
29509
29571
  async function recordingDeleteCommand(id, options = {}) {
29510
29572
  if (!id) {
29511
- throw new CliError2("missing_id", "Missing recording id for: delete");
29573
+ throw new CliError("missing_id", "Missing recording id for: delete");
29512
29574
  }
29513
29575
  await adminApiDelete(`/api/v1/admin/recordings/${encodeURIComponent(id)}`);
29514
29576
  if (options.json) {
29515
- printJson2({ ok: true });
29577
+ printJson({ ok: true });
29516
29578
  return;
29517
29579
  }
29518
29580
  console.log(`Deleted recording ${id} (and its clips).`);
29519
29581
  }
29520
29582
  async function recordingPromoteCommand(id, options = {}) {
29521
29583
  if (!id) {
29522
- throw new CliError2("missing_id", "Missing recording id for: promote");
29584
+ throw new CliError("missing_id", "Missing recording id for: promote");
29523
29585
  }
29524
29586
  const response = await adminApiPost(`/api/v1/admin/recordings/${encodeURIComponent(id)}/promote`);
29525
29587
  const recording = response.recording;
29526
29588
  if (options.json) {
29527
- printJson2({ ok: true, recording });
29589
+ printJson({ ok: true, recording });
29528
29590
  return;
29529
29591
  }
29530
29592
  console.log(recording.logId ? `Promoted recording ${id} → mixtape fluncle://${recording.logId}` : `Promoted recording ${id}`);
29531
29593
  }
29532
29594
  async function recordingReplaceCuesCommand(id, options = {}) {
29533
29595
  if (!id) {
29534
- throw new CliError2("missing_id", "Missing recording id for: replace-cues");
29596
+ throw new CliError("missing_id", "Missing recording id for: replace-cues");
29535
29597
  }
29536
29598
  if (!options.cuesFile) {
29537
- throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
29599
+ throw new CliError("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
29538
29600
  }
29539
29601
  if (!existsSync2(options.cuesFile)) {
29540
- throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
29602
+ throw new CliError("file_not_found", `Cues file not found: ${options.cuesFile}`);
29541
29603
  }
29542
29604
  let cues;
29543
29605
  try {
29544
29606
  cues = JSON.parse(readFileSync2(options.cuesFile, "utf8"));
29545
29607
  } catch {
29546
- throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
29608
+ throw new CliError("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
29547
29609
  }
29548
29610
  const response = await adminApiPut(`/api/v1/admin/recordings/${encodeURIComponent(id)}/cues`, { cues });
29549
29611
  if (options.json) {
29550
- printJson2({ ok: true, recording: response.recording });
29612
+ printJson({ ok: true, recording: response.recording });
29551
29613
  return;
29552
29614
  }
29553
29615
  console.log(`Replaced cues for ${formatRecordingSummary(response.recording)}`);
@@ -29565,7 +29627,7 @@ function buildBody2(options, { requireContent }) {
29565
29627
  if (options.contentFile !== undefined) {
29566
29628
  body.contentJson = readContentFile(options.contentFile);
29567
29629
  } else if (requireContent) {
29568
- throw new CliError2("missing_content", "A draft needs the structured content payload via --content-file <edition.json>");
29630
+ throw new CliError("missing_content", "A draft needs the structured content payload via --content-file <edition.json>");
29569
29631
  }
29570
29632
  if (requireContent && typeof options.promptVersion === "number") {
29571
29633
  body.promptVersion = options.promptVersion;
@@ -29583,13 +29645,13 @@ function buildBody2(options, { requireContent }) {
29583
29645
  }
29584
29646
  function readContentFile(filePath) {
29585
29647
  if (!existsSync3(filePath)) {
29586
- throw new CliError2("file_not_found", `Edition payload file not found: ${filePath}`);
29648
+ throw new CliError("file_not_found", `Edition payload file not found: ${filePath}`);
29587
29649
  }
29588
29650
  const text = readFileSync3(filePath, "utf-8");
29589
29651
  try {
29590
29652
  return JSON.parse(text);
29591
29653
  } catch (error) {
29592
- throw new CliError2("invalid_content_json", `Edition payload JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
29654
+ throw new CliError("invalid_content_json", `Edition payload JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
29593
29655
  }
29594
29656
  }
29595
29657
  async function newsletterDraftCommand(options) {
@@ -29626,15 +29688,15 @@ function resolveBody(options) {
29626
29688
  }
29627
29689
  if (options.bodyFile !== undefined) {
29628
29690
  if (!existsSync4(options.bodyFile)) {
29629
- throw new CliError2("file_not_found", `Body file not found: ${options.bodyFile}`);
29691
+ throw new CliError("file_not_found", `Body file not found: ${options.bodyFile}`);
29630
29692
  }
29631
29693
  return readFileSync4(options.bodyFile, "utf-8");
29632
29694
  }
29633
- throw new CliError2("missing_body", "An entry needs a body via --body <text> or --body-file <entry.md>");
29695
+ throw new CliError("missing_body", "An entry needs a body via --body <text> or --body-file <entry.md>");
29634
29696
  }
29635
29697
  function requireTitle(options) {
29636
29698
  if (options.title === undefined || !options.title.trim()) {
29637
- throw new CliError2("missing_title", "An entry needs a title via --title <text>");
29699
+ throw new CliError("missing_title", "An entry needs a title via --title <text>");
29638
29700
  }
29639
29701
  return options.title;
29640
29702
  }
@@ -29669,20 +29731,20 @@ async function promptDetailCommand(slug) {
29669
29731
  const prompts = await promptsListCommand();
29670
29732
  const detail = prompts.find((prompt) => prompt.slug === slug);
29671
29733
  if (!detail) {
29672
- throw new CliError2("unknown_prompt", `No prompt goes by "${slug}". The registered ones: ${prompts.map((prompt) => prompt.slug).join(", ")}`);
29734
+ throw new CliError("unknown_prompt", `No prompt goes by "${slug}". The registered ones: ${prompts.map((prompt) => prompt.slug).join(", ")}`);
29673
29735
  }
29674
29736
  return detail;
29675
29737
  }
29676
29738
  function resolveBody2(options) {
29677
29739
  if (options.bodyFile === undefined) {
29678
- throw new CliError2("missing_body", "An edit needs a body via --body-file <prompt.txt>. Start from `fluncle admin prompts get <slug> --json | jq -r .body`.");
29740
+ throw new CliError("missing_body", "An edit needs a body via --body-file <prompt.txt>. Start from `fluncle admin prompts get <slug> --json | jq -r .body`.");
29679
29741
  }
29680
29742
  if (!existsSync5(options.bodyFile)) {
29681
- throw new CliError2("file_not_found", `Body file not found: ${options.bodyFile}`);
29743
+ throw new CliError("file_not_found", `Body file not found: ${options.bodyFile}`);
29682
29744
  }
29683
29745
  const body = readFileSync5(options.bodyFile, "utf-8");
29684
29746
  if (body.trim().length === 0) {
29685
- throw new CliError2("empty_body", "A prompt body cannot be empty. Nothing was appended.");
29747
+ throw new CliError("empty_body", "A prompt body cannot be empty. Nothing was appended.");
29686
29748
  }
29687
29749
  return body;
29688
29750
  }
@@ -29697,7 +29759,7 @@ async function promptRollbackCommand(slug, version) {
29697
29759
  const target = detail.versions.find((candidate) => candidate.version === version);
29698
29760
  if (!target) {
29699
29761
  const known = detail.versions.map((candidate) => `v${candidate.version}`).join(", ");
29700
- throw new CliError2("unknown_version", known.length > 0 ? `${slug} has no v${version}. On file: ${known}. For the repo's baked default, run \`fluncle admin prompts reset ${slug}\`.` : `${slug} has no history yet: the repo's baked default is what runs. There is nothing to roll back to.`);
29762
+ throw new CliError("unknown_version", known.length > 0 ? `${slug} has no v${version}. On file: ${known}. For the repo's baked default, run \`fluncle admin prompts reset ${slug}\`.` : `${slug} has no history yet: the repo's baked default is what runs. There is nothing to roll back to.`);
29701
29763
  }
29702
29764
  return restore(detail, { body: target.body, from: version, note: `rolled back to v${version}` });
29703
29765
  }
@@ -29722,7 +29784,7 @@ function parseAgainst(value) {
29722
29784
  }
29723
29785
  const version = parseVersion3(value);
29724
29786
  if (version === undefined) {
29725
- throw new CliError2("invalid_against", `--against takes a version (3, or v3) or the word default. Got "${value}".`);
29787
+ throw new CliError("invalid_against", `--against takes a version (3, or v3) or the word default. Got "${value}".`);
29726
29788
  }
29727
29789
  return { kind: "version", version };
29728
29790
  }
@@ -29802,7 +29864,7 @@ function resolveAgainstVersion(detail, version) {
29802
29864
  const target = detail.versions.find((candidate) => candidate.version === version);
29803
29865
  if (!target) {
29804
29866
  const known = detail.versions.map((candidate) => `v${candidate.version}`).join(", ");
29805
- throw new CliError2("unknown_version", known.length > 0 ? `${detail.slug} has no v${version}. On file: ${known}.` : `${detail.slug} has no history yet. Diff against the repo's default instead: drop --against.`);
29867
+ throw new CliError("unknown_version", known.length > 0 ? `${detail.slug} has no v${version}. On file: ${known}.` : `${detail.slug} has no history yet. Diff against the repo's default instead: drop --against.`);
29806
29868
  }
29807
29869
  return { body: target.body, label: `v${version}`, version };
29808
29870
  }
@@ -29853,7 +29915,7 @@ import { createInterface as createInterface2 } from "node:readline/promises";
29853
29915
  async function listSubmissionsCommand(options = {}) {
29854
29916
  const response = await adminApiGet("/api/v1/admin/submissions");
29855
29917
  if (options.json) {
29856
- printJson2({ ok: true, submissions: response.submissions });
29918
+ printJson({ ok: true, submissions: response.submissions });
29857
29919
  return;
29858
29920
  }
29859
29921
  if (response.submissions.length === 0) {
@@ -29867,7 +29929,7 @@ async function listSubmissionsCommand(options = {}) {
29867
29929
  async function reviewSubmissionCommand(submissionId, options = {}) {
29868
29930
  const submission = await fetchSubmission(submissionId);
29869
29931
  if (options.json) {
29870
- printJson2({ ok: true, submission });
29932
+ printJson({ ok: true, submission });
29871
29933
  return;
29872
29934
  }
29873
29935
  console.log(formatSubmissionDetail(submission));
@@ -29875,7 +29937,7 @@ async function reviewSubmissionCommand(submissionId, options = {}) {
29875
29937
  async function rejectSubmissionCommand(submissionId, options = {}) {
29876
29938
  const response = await adminApiPost(`/api/v1/admin/submissions/${encodeURIComponent(submissionId)}/reject`);
29877
29939
  if (options.json) {
29878
- printJson2({ ok: true, submission: response.submission });
29940
+ printJson({ ok: true, submission: response.submission });
29879
29941
  return;
29880
29942
  }
29881
29943
  console.log(`Rejected ${formatTrackLine(response.submission)}.`);
@@ -29900,7 +29962,7 @@ async function approveSubmissionCommand(submissionId, options = {}) {
29900
29962
  });
29901
29963
  const response = await adminApiPost(`/api/v1/admin/submissions/${encodeURIComponent(submission.id)}/approve`);
29902
29964
  if (options.json) {
29903
- printJson2({ ok: true, submission: response.submission });
29965
+ printJson({ ok: true, submission: response.submission });
29904
29966
  return;
29905
29967
  }
29906
29968
  console.log(`Approved ${formatTrackLine(submission)}.`);
@@ -29911,7 +29973,7 @@ async function triageSubmissionCommand(submissionId, verdict, options = {}) {
29911
29973
  verdict
29912
29974
  });
29913
29975
  if (options.json) {
29914
- printJson2({ ok: true, submission: response.submission });
29976
+ printJson({ ok: true, submission: response.submission });
29915
29977
  return;
29916
29978
  }
29917
29979
  console.log(`Triaged ${formatTrackLine(response.submission)}: ${response.submission.triageVerdict ?? ""}`);
@@ -29922,7 +29984,7 @@ async function fetchSubmission(submissionId) {
29922
29984
  }
29923
29985
  async function confirm(label) {
29924
29986
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
29925
- throw new CliError2("not_interactive", "Approving publishes a submission and needs a yes. Run this in a terminal, or pass --json to approve without the prompt.");
29987
+ throw new CliError("not_interactive", "Approving publishes a submission and needs a yes. Run this in a terminal, or pass --json to approve without the prompt.");
29926
29988
  }
29927
29989
  const rl = createInterface2({
29928
29990
  input: process.stdin,
@@ -30508,9 +30570,11 @@ function createEvidenceHttp(overrides = {}) {
30508
30570
  return {
30509
30571
  cacheDir: defaultCacheDir(),
30510
30572
  cacheTtlMs: 7 * 24 * 60 * 60 * 1000,
30573
+ deadline: null,
30511
30574
  fetch: (url, init) => globalThis.fetch(url, init),
30512
30575
  now: () => Date.now(),
30513
30576
  policies: SOURCE_POLICIES,
30577
+ progress: noteProgress2,
30514
30578
  random: Math.random,
30515
30579
  refresh: false,
30516
30580
  sleep: (ms) => runPromise2(sleep2(ms)),
@@ -30584,16 +30648,26 @@ async function readNextSlot(slotFile) {
30584
30648
  const value = Number(raw);
30585
30649
  return Number.isFinite(value) ? value : 0;
30586
30650
  }
30651
+ function seconds2(ms) {
30652
+ return Math.ceil(ms / 1000);
30653
+ }
30587
30654
  async function reserveSlot(http, source) {
30588
30655
  const interval = http.policies[source].intervalMs;
30589
30656
  const slotAt = await withSlotLock(http, source, async (slotFile) => {
30590
30657
  const now = http.now();
30591
30658
  const slot = Math.max(now, await readNextSlot(slotFile));
30659
+ if (http.deadline !== null && slot >= http.deadline) {
30660
+ return new EvidenceFetchError("timeout", `${source} queue is ${seconds2(slot - now)} s long, past the deadline ${seconds2(http.deadline - now)} s away; fetched responses are cached, so a later run resumes`, 0);
30661
+ }
30592
30662
  await writeFile2(slotFile, String(slot + interval));
30593
30663
  return slot;
30594
30664
  });
30665
+ if (slotAt instanceof EvidenceFetchError) {
30666
+ throw slotAt;
30667
+ }
30595
30668
  const wait = slotAt - http.now();
30596
30669
  if (wait > 0) {
30670
+ http.progress(`waiting ${seconds2(wait)} s for a ${source} slot behind other callers on this machine (${http.stats.requests} requests sent so far; fetched responses are cached, so a later run resumes)`);
30597
30671
  await http.sleep(wait);
30598
30672
  }
30599
30673
  }
@@ -30634,8 +30708,12 @@ async function fetchEvidenceText(http, source, cacheKey, perform, accept = () =>
30634
30708
  let attempt = 0;
30635
30709
  const request = gen2(function* () {
30636
30710
  attempt += 1;
30637
- yield* promise2(() => reserveSlot(http, source));
30711
+ yield* tryPromise2({
30712
+ catch: (error) => error instanceof EvidenceFetchError ? error : new EvidencePerformError({ cause: error }),
30713
+ try: () => reserveSlot(http, source)
30714
+ });
30638
30715
  http.stats.requests += 1;
30716
+ http.progress(`${source} request ${http.stats.requests} in flight`);
30639
30717
  let returned = false;
30640
30718
  const response = yield* tryPromise2({
30641
30719
  catch: (error) => {
@@ -30705,7 +30783,7 @@ async function fetchEvidenceJson(http, source, url, headers = {}) {
30705
30783
  const { cached, text } = await fetchEvidenceText(http, source, `GET ${url}`, async (signal) => {
30706
30784
  const response = await http.fetch(url, {
30707
30785
  headers: { Accept: "application/json", "User-Agent": EVIDENCE_USER_AGENT, ...headers },
30708
- signal
30786
+ signal: AbortSignal.any([signal, deadlineSignal()])
30709
30787
  });
30710
30788
  return { headers: response.headers, status: response.status, text: await response.text() };
30711
30789
  }, isJson2);
@@ -30714,6 +30792,7 @@ async function fetchEvidenceJson(http, source, url, headers = {}) {
30714
30792
  var SOURCE_POLICIES, EVIDENCE_USER_AGENT = "FluncleLabelTriage/1.0 ( https://www.fluncle.com )", EvidenceFetchError, EvidenceRetryable, EvidencePerformError, LOCK_STALE_MS = 1e4;
30715
30793
  var init_evidence_http = __esm(() => {
30716
30794
  init_dist();
30795
+ init_deadline();
30717
30796
  SOURCE_POLICIES = {
30718
30797
  apple: { attempts: 3, intervalMs: 3000, maxBackoffMs: 30000, timeoutMs: 1e4 },
30719
30798
  beatport: { attempts: 2, intervalMs: 1000, maxBackoffMs: 30000, timeoutMs: 75000 },
@@ -31407,7 +31486,7 @@ async function labelEvidenceCommand(input, options) {
31407
31486
  Promise.resolve().then(() => (init_env(), exports_env)),
31408
31487
  Promise.resolve().then(() => (init_admin_labels3(), exports_admin_labels))
31409
31488
  ]);
31410
- const http = createEvidenceHttp({ refresh: options.refresh });
31489
+ const http = createEvidenceHttp({ deadline: deadlineAt(), refresh: options.refresh });
31411
31490
  const discogsToken = readOptionalEnv("DISCOGS_USER_TOKEN");
31412
31491
  if (discogsToken) {
31413
31492
  http.policies = { ...http.policies, discogs: { ...http.policies.discogs, intervalMs: 1100 } };
@@ -31458,6 +31537,7 @@ function labelEvidenceLines(evidence) {
31458
31537
  }
31459
31538
  var MBID_PATTERN3, MB_ROOT = "https://musicbrainz.org/ws/2", DISCOGS_ROOT = "https://api.discogs.com", FIRECRAWL_SCRAPE_URL = "https://api.firecrawl.dev/v2/scrape", VARIOUS_ARTISTS_MBID = "89ad4ac3-39f7-470e-963a-56509c546377", RELEASE_SAMPLE = 12, FIRST_CREDIT_ARTISTS = 25, APPLE_LOOKUPS = 5, DISCOGS_RELEASE_SAMPLE = 8, DISCOGS_STYLE_TARGET = 10, DISCOGS_RELEASE_READS = 8, PROFILE_CHARS = 800, EVIDENCE_SOURCES, MAX_CENSUS_PAGES = 5;
31460
31539
  var init_admin_label_evidence = __esm(() => {
31540
+ init_deadline();
31461
31541
  init_evidence_http();
31462
31542
  MBID_PATTERN3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
31463
31543
  EVIDENCE_SOURCES = ["musicbrainz", "discogs", "beatport", "apple"];
@@ -31500,7 +31580,7 @@ async function paginateWithKeyboard(options) {
31500
31580
  const stdin = options.input ?? process.stdin;
31501
31581
  const stdout = options.output ?? process.stdout;
31502
31582
  if (!stdin.isTTY || !stdout.isTTY) {
31503
- throw new CliError2("not_interactive", options.nonInteractiveMessage);
31583
+ throw new CliError("not_interactive", options.nonInteractiveMessage);
31504
31584
  }
31505
31585
  const first = await options.fetchPage(undefined);
31506
31586
  if (first.lines.length === 0) {
@@ -33728,8 +33808,114 @@ var fluncleAsciiLogo = `███████╗██╗ ██╗ ██
33728
33808
  ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝╚══════╝╚══════╝`;
33729
33809
  var fluncleTagline = "Drum & bass bangers from another dimension";
33730
33810
 
33811
+ // src/deadline.ts
33812
+ init_output();
33813
+ var DEFAULT_NON_INTERACTIVE_TIMEOUT_SECONDS = 240;
33814
+ var TIMEOUT_EXIT_CODE = 124;
33815
+ var TIMEOUT_ENV = "FLUNCLE_TIMEOUT";
33816
+ var CLEANUP_GRACE_MS = 1e4;
33817
+ var state = {
33818
+ at: null,
33819
+ cancel: null,
33820
+ controller: new AbortController,
33821
+ fire: null,
33822
+ fired: false,
33823
+ note: null
33824
+ };
33825
+ function parseTimeoutSeconds(value, source) {
33826
+ if (value === undefined || value.trim() === "") {
33827
+ return null;
33828
+ }
33829
+ const trimmed = value.trim();
33830
+ if (["0", "off", "none"].includes(trimmed.toLowerCase())) {
33831
+ return 0;
33832
+ }
33833
+ const seconds = Number(trimmed);
33834
+ if (!Number.isFinite(seconds) || seconds <= 0) {
33835
+ throw new CliError("invalid_timeout", `${source} must be a number of seconds (0 disables the deadline), not "${value}"`);
33836
+ }
33837
+ return seconds;
33838
+ }
33839
+ function resolveTimeoutSeconds(input) {
33840
+ const flag = parseTimeoutSeconds(input.flag, "--timeout");
33841
+ if (flag !== null) {
33842
+ return flag;
33843
+ }
33844
+ const env = parseTimeoutSeconds(input.env, TIMEOUT_ENV);
33845
+ if (env !== null) {
33846
+ return env;
33847
+ }
33848
+ return input.interactive ? 0 : DEFAULT_NON_INTERACTIVE_TIMEOUT_SECONDS;
33849
+ }
33850
+ function timeoutMessage(command, seconds, note) {
33851
+ const waiting = note ? `: ${note}` : "";
33852
+ return `fluncle ${command} stopped after ${seconds} s${waiting}. Set --timeout or ${TIMEOUT_ENV} in seconds (0 disables the deadline).`;
33853
+ }
33854
+ function defaultSchedule(fire, ms) {
33855
+ const timer = setTimeout(fire, ms);
33856
+ timer.unref();
33857
+ return () => clearTimeout(timer);
33858
+ }
33859
+ function armDeadline(seconds, options) {
33860
+ disarmDeadline();
33861
+ if (seconds <= 0) {
33862
+ state.at = null;
33863
+ return;
33864
+ }
33865
+ const now = options.now ?? Date.now;
33866
+ const exit = options.exit ?? ((code) => process.exit(code));
33867
+ const schedule = options.schedule ?? defaultSchedule;
33868
+ const report = options.report ?? ((message) => {
33869
+ if (options.json) {
33870
+ printJson({ code: "timeout", message, ok: false });
33871
+ return;
33872
+ }
33873
+ console.error(message);
33874
+ });
33875
+ state.at = now() + seconds * 1000;
33876
+ state.fire = () => {
33877
+ if (state.fired) {
33878
+ return;
33879
+ }
33880
+ const message = timeoutMessage(options.command, seconds, state.note);
33881
+ state.fired = true;
33882
+ disarmDeadline();
33883
+ report(message);
33884
+ process.exitCode = TIMEOUT_EXIT_CODE;
33885
+ state.controller.abort(new CliError("timeout", message));
33886
+ schedule(() => exit(TIMEOUT_EXIT_CODE), options.graceMs ?? CLEANUP_GRACE_MS);
33887
+ };
33888
+ state.cancel = schedule(state.fire, seconds * 1000);
33889
+ }
33890
+ function disarmDeadline() {
33891
+ state.cancel?.();
33892
+ state.cancel = null;
33893
+ }
33894
+ function fireDeadlineNow() {
33895
+ state.fire?.();
33896
+ }
33897
+ function deadlineFired() {
33898
+ return state.fired;
33899
+ }
33900
+ function remainingDeadlineMs(now = Date.now) {
33901
+ return state.at === null ? null : state.at - now();
33902
+ }
33903
+ function fireIfPastDeadline(waitingOn) {
33904
+ const remaining = remainingDeadlineMs();
33905
+ if (remaining === null || remaining > 0) {
33906
+ return false;
33907
+ }
33908
+ noteProgress(waitingOn);
33909
+ fireDeadlineNow();
33910
+ return true;
33911
+ }
33912
+ function noteProgress(note) {
33913
+ state.note = note;
33914
+ }
33915
+
33731
33916
  // src/env.ts
33732
33917
  var import_dotenv = __toESM(require_main(), 1);
33918
+ init_deadline();
33733
33919
  var envProfiles = ["local", "production"];
33734
33920
  function setEnvProfile(profile) {
33735
33921
  if (!profile) {
@@ -33803,24 +33989,45 @@ async function maybePrintLiveCallout(args) {
33803
33989
  }
33804
33990
 
33805
33991
  // src/output.ts
33806
- import { writeSync } from "node:fs";
33807
- var EAGAIN_WAIT = new Int32Array(new SharedArrayBuffer(4));
33808
- function writeStdoutSync(text) {
33992
+ import { writeSync as writeSync2 } from "node:fs";
33993
+ import { format } from "node:util";
33994
+ var EAGAIN_WAIT2 = new Int32Array(new SharedArrayBuffer(4));
33995
+ var stopWaitingForReader2 = () => false;
33996
+ function writeFdSync2(fd, text) {
33809
33997
  const buf = Buffer.from(text, "utf8");
33810
33998
  let offset = 0;
33811
33999
  while (offset < buf.length) {
33812
34000
  try {
33813
- offset += writeSync(1, buf, offset);
34001
+ offset += writeSync2(fd, buf, offset);
33814
34002
  } catch (error) {
33815
- if (error.code === "EAGAIN") {
33816
- Atomics.wait(EAGAIN_WAIT, 0, 0, 1);
34003
+ const code = error.code;
34004
+ if (code === "EAGAIN") {
34005
+ if (stopWaitingForReader2()) {
34006
+ return;
34007
+ }
34008
+ Atomics.wait(EAGAIN_WAIT2, 0, 0, 1);
33817
34009
  continue;
33818
34010
  }
34011
+ if (code === "EPIPE") {
34012
+ return;
34013
+ }
33819
34014
  throw error;
33820
34015
  }
33821
34016
  }
33822
34017
  }
33823
- class CliError extends Error {
34018
+ function routeConsoleThroughBlockingWrites(pastDeadline) {
34019
+ stopWaitingForReader2 = pastDeadline;
34020
+ const toStdout = (...args) => writeFdSync2(1, `${format(...args)}
34021
+ `);
34022
+ const toStderr = (...args) => writeFdSync2(2, `${format(...args)}
34023
+ `);
34024
+ console.debug = toStdout;
34025
+ console.error = toStderr;
34026
+ console.info = toStdout;
34027
+ console.log = toStdout;
34028
+ console.warn = toStderr;
34029
+ }
34030
+ class CliError2 extends Error {
33824
34031
  code;
33825
34032
  constructor(code, message) {
33826
34033
  super(message);
@@ -33828,12 +34035,12 @@ class CliError extends Error {
33828
34035
  this.code = code;
33829
34036
  }
33830
34037
  }
33831
- function printJson(value) {
33832
- writeStdoutSync(JSON.stringify(value, null, 2) + `
34038
+ function printJson2(value) {
34039
+ writeFdSync2(1, JSON.stringify(value, null, 2) + `
33833
34040
  `);
33834
34041
  }
33835
34042
  function toJsonFailure(error) {
33836
- if (error instanceof CliError) {
34043
+ if (error instanceof CliError2) {
33837
34044
  return {
33838
34045
  code: error.code,
33839
34046
  message: error.message,
@@ -33915,9 +34122,17 @@ function artistRuleLines(rules) {
33915
34122
  }
33916
34123
  function createProgram() {
33917
34124
  const program = configureCommand(new Command);
33918
- program.name("fluncle").description(fluncleTagline).option("--env <local|production>", "Config profile to load (default: production)").showSuggestionAfterError(false).addHelpCommand("help [command]", "Display help for command").hook("preAction", (_thisCommand, actionCommand) => {
34125
+ program.name("fluncle").description(fluncleTagline).option("--env <local|production>", "Config profile to load (default: production)").option("--timeout <seconds>", `Stop the command after this many seconds with exit code 124 (0 disables; default 240 when stdout is not a terminal, or ${TIMEOUT_ENV})`).showSuggestionAfterError(false).addHelpCommand("help [command]", "Display help for command").hook("preAction", (_thisCommand, actionCommand) => {
33919
34126
  const options = actionCommand.optsWithGlobals();
33920
34127
  setEnvProfile(options.env);
34128
+ armDeadline(resolveTimeoutSeconds({
34129
+ env: process.env[TIMEOUT_ENV],
34130
+ flag: options.timeout,
34131
+ interactive: Boolean(process.stdout.isTTY)
34132
+ }), {
34133
+ command: commandPath(actionCommand),
34134
+ json: process.argv.includes("--json")
34135
+ });
33921
34136
  }).addHelpText("before", `
33922
34137
  ${fluncleAsciiLogo}
33923
34138
  `).addHelpText("after", rootHelpSections);
@@ -33930,6 +34145,7 @@ ${fluncleAsciiLogo}
33930
34145
  return program;
33931
34146
  }
33932
34147
  async function main(args = process.argv.slice(2)) {
34148
+ routeConsoleThroughBlockingWrites(() => fireIfPastDeadline("waiting for the stdout reader to take the output"));
33933
34149
  const program = createProgram();
33934
34150
  if (args.length === 0) {
33935
34151
  program.outputHelp();
@@ -33944,17 +34160,31 @@ async function main(args = process.argv.slice(2)) {
33944
34160
  if (isHelpDisplayed(error)) {
33945
34161
  return;
33946
34162
  }
34163
+ if (deadlineFired()) {
34164
+ process.exit(TIMEOUT_EXIT_CODE);
34165
+ }
33947
34166
  const normalized = normalizeCommanderError(error);
33948
34167
  if (process.argv.includes("--json") || args.includes("--json")) {
33949
- printJson(toJsonFailure(normalized));
34168
+ printJson2(toJsonFailure(normalized));
33950
34169
  } else {
33951
34170
  console.error(formatError(normalized));
33952
34171
  }
33953
34172
  process.exit(1);
33954
34173
  }
34174
+ disarmDeadline();
34175
+ if (deadlineFired()) {
34176
+ process.exit(TIMEOUT_EXIT_CODE);
34177
+ }
33955
34178
  await Promise.resolve().then(() => init_update_notifier());
33956
34179
  await notifyIfUpdateAvailable(args);
33957
34180
  }
34181
+ function commandPath(command) {
34182
+ const names = [];
34183
+ for (let current = command;current?.parent; current = current.parent) {
34184
+ names.unshift(current.name());
34185
+ }
34186
+ return [...names, ...command.args.slice(0, 1)].join(" ");
34187
+ }
33958
34188
  function configureCommand(command) {
33959
34189
  return command.exitOverride().helpOption("-h, --help", "Display help for command").addHelpCommand(false).configureOutput({
33960
34190
  writeErr: () => {}
@@ -34109,7 +34339,7 @@ JSON field reference:
34109
34339
  const receipts = await Promise.resolve().then(() => (init_admin_operation_receipts2(), {}));
34110
34340
  const result = await getOperationReceiptCommand(operationKey);
34111
34341
  if (options.json) {
34112
- printJson(result);
34342
+ printJson2(result);
34113
34343
  return;
34114
34344
  }
34115
34345
  console.log(operationReceiptLines(result.receipt).join(`
@@ -34123,7 +34353,7 @@ JSON field reference:
34123
34353
  requestDigest
34124
34354
  });
34125
34355
  if (options.json) {
34126
- printJson(result);
34356
+ printJson2(result);
34127
34357
  return;
34128
34358
  }
34129
34359
  console.log(operationReceiptLines(result.receipt).join(`
@@ -34136,7 +34366,7 @@ JSON field reference:
34136
34366
  staleBefore: parseOperationReceiptFence(options.staleBefore)
34137
34367
  });
34138
34368
  if (options.json) {
34139
- printJson(result);
34369
+ printJson2(result);
34140
34370
  return;
34141
34371
  }
34142
34372
  const noun = result.repaired === 1 ? "receipt" : "receipts";
@@ -34150,7 +34380,7 @@ JSON field reference:
34150
34380
  const projections = await Promise.resolve().then(() => (init_admin_projections2(), {}));
34151
34381
  const result = await getProjectionStatusCommand();
34152
34382
  if (options.json) {
34153
- printJson(result);
34383
+ printJson2(result);
34154
34384
  return;
34155
34385
  }
34156
34386
  console.log(projectionStatusLines(result.status).join(`
@@ -34171,7 +34401,7 @@ JSON field reference:
34171
34401
  wallMs: options.wallMs === undefined ? undefined : parseProjectionWallMs(options.wallMs)
34172
34402
  });
34173
34403
  if (options.json) {
34174
- printJson(result);
34404
+ printJson2(result);
34175
34405
  return;
34176
34406
  }
34177
34407
  console.log(`${result.target} ${result.action}: steps ${result.steps}, processed ${result.processed}, scheduled ${result.scheduled}, ${result.complete ? "complete" : result.wallStopped ? "wall budget spent, more work remains" : "more work remains"}.`);
@@ -34186,7 +34416,7 @@ JSON field reference:
34186
34416
  workKind: options.workKind
34187
34417
  });
34188
34418
  if (options.json) {
34189
- printJson(result);
34419
+ printJson2(result);
34190
34420
  return;
34191
34421
  }
34192
34422
  console.log(dueWorkRekeyLine(result));
@@ -34198,7 +34428,7 @@ JSON field reference:
34198
34428
  target: parseProjectionCutover(options.target)
34199
34429
  });
34200
34430
  if (options.json) {
34201
- printJson(result);
34431
+ printJson2(result);
34202
34432
  return;
34203
34433
  }
34204
34434
  console.log(`${result.target}: cutover ${result.enabled ? "open" : "dark"}.`);
@@ -34211,7 +34441,7 @@ JSON field reference:
34211
34441
  const vectors = await Promise.resolve().then(() => (init_admin_vectors2(), {}));
34212
34442
  const result = await getVectorServingCommand(parseVectorTarget(targetValue));
34213
34443
  if (options.json) {
34214
- printJson(result);
34444
+ printJson2(result);
34215
34445
  return;
34216
34446
  }
34217
34447
  console.log(vectorServingStatusLines(result.status).join(`
@@ -34224,7 +34454,7 @@ JSON field reference:
34224
34454
  target: parseVectorTarget(targetValue)
34225
34455
  });
34226
34456
  if (options.json) {
34227
- printJson(result);
34457
+ printJson2(result);
34228
34458
  return;
34229
34459
  }
34230
34460
  console.log(vectorServingStatusLines(result.status).join(`
@@ -34241,7 +34471,7 @@ JSON field reference:
34241
34471
  contracts: parseArtifactContracts(options.contract)
34242
34472
  });
34243
34473
  if (options.json) {
34244
- printJson(result);
34474
+ printJson2(result);
34245
34475
  return;
34246
34476
  }
34247
34477
  console.log(`${result.consumer.consumerId}: rebuilding at snapshot seq ${result.consumer.snapshotSeq ?? 0}.`);
@@ -34250,7 +34480,7 @@ JSON field reference:
34250
34480
  await Promise.resolve().then(() => init_admin_artifacts2());
34251
34481
  const result = await getArtifactConsumerCommand(consumerId);
34252
34482
  if (options.json) {
34253
- printJson(result);
34483
+ printJson2(result);
34254
34484
  return;
34255
34485
  }
34256
34486
  console.log(`${result.consumer.consumerId}: ${result.consumer.state}, head ${result.consumer.headSeq}, applied ${result.consumer.appliedThroughSeq ?? "none"}.`);
@@ -34266,7 +34496,7 @@ JSON field reference:
34266
34496
  streamVersion: parseArtifactInteger(options.streamVersion, "--stream-version")
34267
34497
  });
34268
34498
  if (options.json) {
34269
- printJson(result);
34499
+ printJson2(result);
34270
34500
  return;
34271
34501
  }
34272
34502
  console.log(`${result.stream}@${result.streamVersion}/${result.formatVersion}: ${result.itemCount} item(s), snapshot seq ${result.snapshotSeq}, page ${result.pageDigest}.`);
@@ -34286,7 +34516,7 @@ JSON field reference:
34286
34516
  streamVersion: parseArtifactInteger(options.streamVersion, "--stream-version")
34287
34517
  });
34288
34518
  if (options.json) {
34289
- printJson(result);
34519
+ printJson2(result);
34290
34520
  return;
34291
34521
  }
34292
34522
  console.log(`${result.checkpoint.stream}: ${result.checkpoint.state}, ${result.checkpoint.sourceItemCount} item(s) checkpointed.`);
@@ -34295,7 +34525,7 @@ JSON field reference:
34295
34525
  await Promise.resolve().then(() => init_admin_artifacts2());
34296
34526
  const result = await activateArtifactConsumerCommand(consumerId);
34297
34527
  if (options.json) {
34298
- printJson(result);
34528
+ printJson2(result);
34299
34529
  return;
34300
34530
  }
34301
34531
  console.log(`${result.consumer.consumerId}: active at seq ${result.consumer.appliedThroughSeq ?? 0}.`);
@@ -34309,7 +34539,7 @@ JSON field reference:
34309
34539
  })
34310
34540
  });
34311
34541
  if (options.json) {
34312
- printJson(result);
34542
+ printJson2(result);
34313
34543
  return;
34314
34544
  }
34315
34545
  console.log(`${result.events.length} event(s), seq ${result.fromSeq} through ${result.throughSeq}, head ${result.headSeq}, batch ${result.batchDigest}.`);
@@ -34328,7 +34558,7 @@ JSON field reference:
34328
34558
  })
34329
34559
  });
34330
34560
  if (options.json) {
34331
- printJson(result);
34561
+ printJson2(result);
34332
34562
  return;
34333
34563
  }
34334
34564
  console.log(`${result.consumer.consumerId}: applied through seq ${result.consumer.appliedThroughSeq ?? 0}.`);
@@ -34337,7 +34567,7 @@ JSON field reference:
34337
34567
  await Promise.resolve().then(() => init_admin_artifacts2());
34338
34568
  const result = await inactivateArtifactConsumerCommand(consumerId);
34339
34569
  if (options.json) {
34340
- printJson(result);
34570
+ printJson2(result);
34341
34571
  return;
34342
34572
  }
34343
34573
  console.log(`${result.consumer.consumerId}: inactive, bootstrap required before reuse.`);
@@ -34348,7 +34578,7 @@ JSON field reference:
34348
34578
  maximum: ARTIFACT_COMPACTION_API_MAX_LIMIT
34349
34579
  }));
34350
34580
  if (options.json) {
34351
- printJson(result);
34581
+ printJson2(result);
34352
34582
  return;
34353
34583
  }
34354
34584
  console.log(result.deletedCount === 0 ? `Compaction: ${result.reason}.` : `Compaction: deleted ${result.deletedCount} event(s), seq ${result.deletedFromSeq ?? 0} through ${result.deletedThroughSeq ?? 0}.`);
@@ -34402,15 +34632,15 @@ JSON field reference:
34402
34632
  });
34403
34633
  adminTracks.command("prepare-captures").description("Freeze a batch of capture snapshots in one admitted phase").requiredOption("--file <path>", "JSON file holding the batch body ({ items: [...] })").option("--json", "Print JSON", false).action(async (options) => {
34404
34634
  await Promise.resolve().then(() => init_admin_tracks2());
34405
- printJson(await prepareTrackCapturesCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34635
+ printJson2(await prepareTrackCapturesCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34406
34636
  });
34407
34637
  adminTracks.command("commit-captures").description("Commit a batch of prepared capture results in one admitted phase").requiredOption("--file <path>", "JSON file holding the batch body ({ items: [...] })").option("--json", "Print JSON", false).action(async (options) => {
34408
34638
  await Promise.resolve().then(() => init_admin_tracks2());
34409
- printJson(await commitTrackCapturesCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34639
+ printJson2(await commitTrackCapturesCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34410
34640
  });
34411
34641
  adminTracks.command("update-embeddings").description("Write a batch of audio embeddings in one admitted phase").requiredOption("--file <path>", "JSON file holding the batch body ({ items: [...] })").option("--json", "Print JSON", false).action(async (options) => {
34412
34642
  await Promise.resolve().then(() => init_admin_tracks2());
34413
- printJson(await updateTrackEmbeddingsCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34643
+ printJson2(await updateTrackEmbeddingsCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34414
34644
  });
34415
34645
  adminTracks.command("requeue-analysis").description("Re-queue findings whose BPM/key are preview-grade (dry-run; --apply to flip)").option("--apply", "Actually flip statuses (default is a dry-run preview)", false).option("--limit <limit>", "Cap the archive walk (default: the whole archive)").option("--json", "Print JSON", false).action(async (options) => {
34416
34646
  await Promise.resolve().then(() => init_admin_tracks2());
@@ -34546,7 +34776,7 @@ JSON field reference:
34546
34776
  await Promise.resolve().then(() => init_digests());
34547
34777
  const paused = await getFollowDigestStateCommand();
34548
34778
  if (options.json) {
34549
- printJson({ ok: true, paused });
34779
+ printJson2({ ok: true, paused });
34550
34780
  } else {
34551
34781
  console.log(paused ? "Follow digests are paused." : "Follow digests are running.");
34552
34782
  }
@@ -34559,7 +34789,7 @@ JSON field reference:
34559
34789
  await Promise.resolve().then(() => init_digests());
34560
34790
  const result = await setFollowDigestStateCommand(paused);
34561
34791
  if (options.json) {
34562
- printJson({ ok: true, paused: result });
34792
+ printJson2({ ok: true, paused: result });
34563
34793
  } else {
34564
34794
  console.log(result ? "Follow digests are paused." : "Follow digests are running.");
34565
34795
  }
@@ -34573,7 +34803,7 @@ JSON field reference:
34573
34803
  limit: options.limit === undefined ? undefined : Number(options.limit)
34574
34804
  });
34575
34805
  if (options.json) {
34576
- printJson(result);
34806
+ printJson2(result);
34577
34807
  } else if (result.windowClosed && !result.dryRun) {
34578
34808
  console.log("Follow digest sends open Friday at 17:00 Amsterdam.");
34579
34809
  } else {
@@ -34845,7 +35075,7 @@ JSON field reference:
34845
35075
  await Promise.resolve().then(() => init_admin_frontier2());
34846
35076
  const summary = await frontierRefreshCommand(options);
34847
35077
  if (options.json) {
34848
- printJson(summary);
35078
+ printJson2(summary);
34849
35079
  return;
34850
35080
  }
34851
35081
  const counts = `${summary.refreshed} refreshed, ${summary.minted} minted, ${summary.editionOnly} edition-only, ${summary.unchanged} unchanged, ${summary.failed} failed`;
@@ -34859,7 +35089,7 @@ JSON field reference:
34859
35089
  await Promise.resolve().then(() => init_admin_frontier2());
34860
35090
  const state = await frontierStatusCommand();
34861
35091
  if (options.json) {
34862
- printJson(state);
35092
+ printJson2(state);
34863
35093
  return;
34864
35094
  }
34865
35095
  console.log(state.open ? "Frontier minting is OPEN." : "Frontier minting is closed.");
@@ -34868,7 +35098,7 @@ JSON field reference:
34868
35098
  await Promise.resolve().then(() => init_admin_frontier2());
34869
35099
  const state = await frontierSetMintingCommand(true);
34870
35100
  if (options.json) {
34871
- printJson(state);
35101
+ printJson2(state);
34872
35102
  return;
34873
35103
  }
34874
35104
  console.log("Frontier minting is OPEN.");
@@ -34877,7 +35107,7 @@ JSON field reference:
34877
35107
  await Promise.resolve().then(() => init_admin_frontier2());
34878
35108
  const state = await frontierSetMintingCommand(false);
34879
35109
  if (options.json) {
34880
- printJson(state);
35110
+ printJson2(state);
34881
35111
  return;
34882
35112
  }
34883
35113
  console.log("Frontier minting is closed.");
@@ -34894,7 +35124,7 @@ JSON field reference:
34894
35124
  await Promise.resolve().then(() => init_admin_catalogue2());
34895
35125
  const summary = await catalogueDemandCommand();
34896
35126
  if (options.json) {
34897
- printJson({ ok: true, summary });
35127
+ printJson2({ ok: true, summary });
34898
35128
  return;
34899
35129
  }
34900
35130
  if (!summary.configured) {
@@ -34911,7 +35141,7 @@ JSON field reference:
34911
35141
  await Promise.resolve().then(() => init_admin_catalogue2());
34912
35142
  const { cleared } = await clearWrongAudioCommand(trackId);
34913
35143
  if (options.json) {
34914
- printJson({ cleared, ok: true });
35144
+ printJson2({ cleared, ok: true });
34915
35145
  return;
34916
35146
  }
34917
35147
  console.log(cleared ? `Kept ${trackId}. Its audio re-embeds and rejoins the ranking on the next sweep.` : `${trackId} was not quarantined \u2014 nothing to clear.`);
@@ -34920,7 +35150,7 @@ JSON field reference:
34920
35150
  await Promise.resolve().then(() => init_admin_catalogue2());
34921
35151
  const { paidCleared, requeued } = await requeueAnchorCommand(trackIds, options.clearPaid);
34922
35152
  if (options.json) {
34923
- printJson({ ok: true, paidCleared, requeued });
35153
+ printJson2({ ok: true, paidCleared, requeued });
34924
35154
  return;
34925
35155
  }
34926
35156
  console.log(`Re-queued ${requeued} of ${trackIds.length} row(s) \u2014 the next anchor tick picks them up at their real priority.`);
@@ -34933,7 +35163,7 @@ JSON field reference:
34933
35163
  await Promise.resolve().then(() => init_admin_catalogue2());
34934
35164
  const state = await spotifyConsumerBudgetCommand();
34935
35165
  if (options.json || spotifyBudget.opts().json) {
34936
- printJson({
35166
+ printJson2({
34937
35167
  consumerBudgets: state.consumerBudgets,
34938
35168
  ok: true,
34939
35169
  spotifyDailyCalls: state.spotifyDailyCalls
@@ -34957,7 +35187,7 @@ JSON field reference:
34957
35187
  await Promise.resolve().then(() => init_admin_catalogue2());
34958
35188
  await setSpotifyConsumerBudgetCommand(options.consumer, calls);
34959
35189
  if (options.json || spotifyBudget.opts().json) {
34960
- printJson({ calls, consumer: options.consumer, ok: true });
35190
+ printJson2({ calls, consumer: options.consumer, ok: true });
34961
35191
  return;
34962
35192
  }
34963
35193
  console.log(`SPOTIFY BUDGET ${options.consumer} \u2014 ${calls} calls per day`);
@@ -34966,7 +35196,7 @@ JSON field reference:
34966
35196
  await Promise.resolve().then(() => init_admin_catalogue2());
34967
35197
  const state = await anchorBreakerCommand();
34968
35198
  if (options.json) {
34969
- printJson({ ...state, ok: true });
35199
+ printJson2({ ...state, ok: true });
34970
35200
  return;
34971
35201
  }
34972
35202
  const minutes = Math.ceil(state.cooldownRemainingMs / 60000);
@@ -34997,7 +35227,7 @@ JSON field reference:
34997
35227
  since: options.since
34998
35228
  });
34999
35229
  if (options.json) {
35000
- printJson({ ok: true, ...result });
35230
+ printJson2({ ok: true, ...result });
35001
35231
  return;
35002
35232
  }
35003
35233
  console.log(result.dryRun ? `DRY RUN \u2014 ${result.matched} row(s) stamped Deezer-empty since ${options.since}. Re-run with --apply to clear them.` : `Cleared ${result.requeued} of ${result.matched} row(s) \u2014 the next isrc-recovery tick asks them again.`);
@@ -35006,7 +35236,7 @@ JSON field reference:
35006
35236
  await Promise.resolve().then(() => init_admin_catalogue2());
35007
35237
  const { requeued, skippedVetoed } = await requeueUnmatchedCommand();
35008
35238
  if (options.json) {
35009
- printJson({ ok: true, requeued, skippedVetoed });
35239
+ printJson2({ ok: true, requeued, skippedVetoed });
35010
35240
  return;
35011
35241
  }
35012
35242
  console.log(`Re-queued ${requeued} unmatched capture(s) for the upgraded search ladder; ${skippedVetoed} stay terminal under the duration vetoes.`);
@@ -35015,7 +35245,7 @@ JSON field reference:
35015
35245
  await Promise.resolve().then(() => init_admin_catalogue2());
35016
35246
  const { flagged } = await flagWrongAudioCommand(trackId);
35017
35247
  if (options.json) {
35018
- printJson({ flagged, ok: true });
35248
+ printJson2({ flagged, ok: true });
35019
35249
  return;
35020
35250
  }
35021
35251
  console.log(flagged ? `Flagged ${trackId}. Its vector is out of the ranking and a fresh capture is queued (the bad bytes are hash-rejected).` : `${trackId} is not a captured finding \u2014 nothing to flag.`);
@@ -35024,7 +35254,7 @@ JSON field reference:
35024
35254
  await Promise.resolve().then(() => init_admin_catalogue2());
35025
35255
  const { forced } = await forceCaptureCommand(trackId);
35026
35256
  if (options.json) {
35027
- printJson({ forced, ok: true });
35257
+ printJson2({ forced, ok: true });
35028
35258
  return;
35029
35259
  }
35030
35260
  console.log(forced ? `Forced ${trackId}. The duplicate veto is lifted; it re-ranks onto the capture ladder and the next open-budget tick buys it.` : `${trackId} was not vetoed as a duplicate \u2014 nothing to force.`);
@@ -35034,7 +35264,7 @@ JSON field reference:
35034
35264
  if (options.queue) {
35035
35265
  const { tracks } = await listUnverifiedCapturesCommand(options.limit ? { limit: options.limit } : {});
35036
35266
  if (options.json) {
35037
- printJson({ ok: true, tracks });
35267
+ printJson2({ ok: true, tracks });
35038
35268
  return;
35039
35269
  }
35040
35270
  if (tracks.length === 0) {
@@ -35055,7 +35285,7 @@ JSON field reference:
35055
35285
  }
35056
35286
  const { action } = await verifyCaptureCommand(trackId, verdict);
35057
35287
  if (options.json) {
35058
- printJson({ action, ok: true });
35288
+ printJson2({ action, ok: true });
35059
35289
  return;
35060
35290
  }
35061
35291
  console.log(`Recorded. ${trackId} \u2192 ${action}.`);
@@ -35064,7 +35294,7 @@ JSON field reference:
35064
35294
  await Promise.resolve().then(() => init_admin_catalogue2());
35065
35295
  const { logId } = await certifyTrackCommand(trackId, options.note);
35066
35296
  if (options.json) {
35067
- printJson({ logId, ok: true });
35297
+ printJson2({ logId, ok: true });
35068
35298
  return;
35069
35299
  }
35070
35300
  console.log(`Logged ${trackId} \u2014 ${logId}. Enrichment is running; finish it in the admin.`);
@@ -35073,7 +35303,7 @@ JSON field reference:
35073
35303
  await Promise.resolve().then(() => init_admin_catalogue2());
35074
35304
  const { changed } = await setTrackDismissedCommand(trackId, true);
35075
35305
  if (options.json) {
35076
- printJson({ changed, ok: true });
35306
+ printJson2({ changed, ok: true });
35077
35307
  return;
35078
35308
  }
35079
35309
  console.log(changed ? `Dismissed ${trackId}. It leaves the ranking and the capture ladder; restore it any time.` : `${trackId} was not a live catalogue row \u2014 nothing dismissed.`);
@@ -35082,7 +35312,7 @@ JSON field reference:
35082
35312
  await Promise.resolve().then(() => init_admin_catalogue2());
35083
35313
  const { changed } = await setTrackDismissedCommand(trackId, false);
35084
35314
  if (options.json) {
35085
- printJson({ changed, ok: true });
35315
+ printJson2({ changed, ok: true });
35086
35316
  return;
35087
35317
  }
35088
35318
  console.log(changed ? `Restored ${trackId}. It re-enters the ranking on the next sweep.` : `${trackId} was not dismissed \u2014 nothing to restore.`);
@@ -35095,7 +35325,7 @@ JSON field reference:
35095
35325
  }
35096
35326
  const body = JSON.parse(readFileSync6(options.phaseFile, "utf8"));
35097
35327
  const result = await crawlCataloguePhaseCommand(body);
35098
- printJson(result);
35328
+ printJson2(result);
35099
35329
  return;
35100
35330
  }
35101
35331
  await runCrawlCatalogue(options, crawlCatalogueCommand);
@@ -35103,7 +35333,7 @@ JSON field reference:
35103
35333
  catalogue.command("commit-nodes").description("Commit one claim's fetched crawl nodes in a single admitted phase").requiredOption("--file <path>", "JSON file holding the batch body ({ items: [...] })").option("--json", "Print JSON", false).action(async (options) => {
35104
35334
  await Promise.resolve().then(() => init_admin_catalogue2());
35105
35335
  const body = JSON.parse(readFileSync6(options.file, "utf8"));
35106
- printJson(await commitCrawlNodesCommand(body));
35336
+ printJson2(await commitCrawlNodesCommand(body));
35107
35337
  });
35108
35338
  catalogue.command("status").description("The crawl frontier's state, the catalogue's size, and the seed set").option("--json", "Print JSON", false).action(async (options) => {
35109
35339
  await Promise.resolve().then(() => init_admin_catalogue2());
@@ -35117,7 +35347,7 @@ JSON field reference:
35117
35347
  await Promise.resolve().then(() => init_admin_reach2());
35118
35348
  const { collected, failed, inserted, skipped } = await reachCollectCommand();
35119
35349
  if (options.json) {
35120
- printJson({ collected, failed, inserted, ok: true, skipped });
35350
+ printJson2({ collected, failed, inserted, ok: true, skipped });
35121
35351
  return;
35122
35352
  }
35123
35353
  const landed = collected.map((entry) => `${entry.platform} (${entry.metrics.join(", ")})`).join("; ");
@@ -35219,7 +35449,7 @@ JSON field reference:
35219
35449
  await Promise.resolve().then(() => init_admin_artists2());
35220
35450
  const rule = await addArtistRuleCommand(artistRuleInput(artistMbid, options.verdict, options.name));
35221
35451
  if (options.json) {
35222
- printJson({ ok: true, rule });
35452
+ printJson2({ ok: true, rule });
35223
35453
  return;
35224
35454
  }
35225
35455
  console.log(artistRuleWriteLine(`Rule set for ${rule.resolvedName ?? rule.artistName}: ${rule.verdict.toUpperCase()}`, rule.verdict === "unlisted" ? UNLISTED_RULE_BOUNDARY : ARTIST_RULE_BOUNDARY));
@@ -35228,7 +35458,7 @@ JSON field reference:
35228
35458
  await Promise.resolve().then(() => init_admin_artists2());
35229
35459
  const rules = await listArtistRulesCommand();
35230
35460
  if (options.json) {
35231
- printJson({ ok: true, rules });
35461
+ printJson2({ ok: true, rules });
35232
35462
  return;
35233
35463
  }
35234
35464
  console.log(artistRuleLines(rules).join(`
@@ -35238,7 +35468,7 @@ JSON field reference:
35238
35468
  await Promise.resolve().then(() => init_admin_artists2());
35239
35469
  const result = await removeArtistRuleCommand(id);
35240
35470
  if (options.json) {
35241
- printJson(result);
35471
+ printJson2(result);
35242
35472
  return;
35243
35473
  }
35244
35474
  console.log(artistRuleWriteLine(`Rule removed: ${id}`));
@@ -35274,7 +35504,7 @@ JSON field reference:
35274
35504
  }
35275
35505
  const result = options.replace ? await replaceLabelArtistRulesCommand(slug, parseLabelArtistRulesJson(readFileSync6(options.rulesFile ?? "", "utf8"))) : await listLabelArtistRulesCommand(slug);
35276
35506
  if (options.json) {
35277
- printJson({ ok: true, rules: result.rules });
35507
+ printJson2({ ok: true, rules: result.rules });
35278
35508
  return;
35279
35509
  }
35280
35510
  if (options.replace) {
@@ -35296,7 +35526,7 @@ JSON field reference:
35296
35526
  await Promise.resolve().then(() => init_admin_labels2());
35297
35527
  const label = await updateLabelCommand(slug, seedState, options.rewalk);
35298
35528
  if (options.json) {
35299
- printJson({ label, ok: true });
35529
+ printJson2({ label, ok: true });
35300
35530
  return;
35301
35531
  }
35302
35532
  for (const line of labelUpdateLines(label, options)) {
@@ -35333,7 +35563,7 @@ JSON field reference:
35333
35563
  process.exitCode = 1;
35334
35564
  }
35335
35565
  if (options.json) {
35336
- printJson(evidence);
35566
+ printJson2(evidence);
35337
35567
  return;
35338
35568
  }
35339
35569
  console.log(labelEvidenceLines(evidence).join(`
@@ -35362,7 +35592,7 @@ JSON field reference:
35362
35592
  await Promise.resolve().then(() => init_admin_labels2());
35363
35593
  const { label, outcome, takenOver } = await mintLabelCommand(mbid, seedState, takeOver);
35364
35594
  if (options.json) {
35365
- printJson({ label, ok: true, outcome, ...takenOver ? { takenOver } : {} });
35595
+ printJson2({ label, ok: true, outcome, ...takenOver ? { takenOver } : {} });
35366
35596
  return;
35367
35597
  }
35368
35598
  console.log(labelMintLine(label, outcome, mbid, takenOver));
@@ -35371,7 +35601,7 @@ JSON field reference:
35371
35601
  await Promise.resolve().then(() => init_admin_labels2());
35372
35602
  const result = await mergeLabelCommand(losingSlug, canonicalSlug);
35373
35603
  if (options.json) {
35374
- printJson({ ok: true, result });
35604
+ printJson2({ ok: true, result });
35375
35605
  return;
35376
35606
  }
35377
35607
  const {
@@ -35426,7 +35656,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
35426
35656
  source: options.source
35427
35657
  });
35428
35658
  if (options.json) {
35429
- printJson(result);
35659
+ printJson2(result);
35430
35660
  return;
35431
35661
  }
35432
35662
  console.log(`Archived preview for ${result.logId}`);
@@ -35457,7 +35687,7 @@ async function runTrackObserve(idOrLogId, options, trackObserveCommand) {
35457
35687
  voiceId: options.voiceId
35458
35688
  });
35459
35689
  if (options.json) {
35460
- printJson(result);
35690
+ printJson2(result);
35461
35691
  return;
35462
35692
  }
35463
35693
  console.log(`Recorded observation for ${result.logId}`);
@@ -35474,7 +35704,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand) {
35474
35704
  refresh: options.refresh
35475
35705
  });
35476
35706
  if (options.json) {
35477
- printJson(result);
35707
+ printJson2(result);
35478
35708
  return;
35479
35709
  }
35480
35710
  if (result.skipped) {
@@ -35509,7 +35739,7 @@ async function runTrackNote(idOrLogId, options, trackNoteCommand) {
35509
35739
  promptVersion: parsePromptVersion(options.promptVersion)
35510
35740
  });
35511
35741
  if (options.json) {
35512
- printJson(result);
35742
+ printJson2(result);
35513
35743
  return;
35514
35744
  }
35515
35745
  if (result.skipped) {
@@ -35540,7 +35770,7 @@ async function runEntityDescribe(kind, slug, options, describeCommand) {
35540
35770
  promptVersion: parsePromptVersion(options.promptVersion)
35541
35771
  });
35542
35772
  if (options.json) {
35543
- printJson(result);
35773
+ printJson2(result);
35544
35774
  return;
35545
35775
  }
35546
35776
  if (result.skipped) {
@@ -35565,7 +35795,7 @@ async function runEntityBioQueue(kind, options, queueCommand) {
35565
35795
  const limit = options.limit ? Number.parseInt(options.limit, 10) : 50;
35566
35796
  const items = await queueCommand(Number.isFinite(limit) ? limit : 50);
35567
35797
  if (options.json) {
35568
- printJson(items);
35798
+ printJson2(items);
35569
35799
  return;
35570
35800
  }
35571
35801
  if (items.length === 0) {
@@ -35581,7 +35811,7 @@ async function runEntityBioQueue(kind, options, queueCommand) {
35581
35811
  async function runEntityBioDraft(kind, slug, options, draftCommand) {
35582
35812
  const draft = await draftCommand(slug);
35583
35813
  if (options.json) {
35584
- printJson(draft);
35814
+ printJson2(draft);
35585
35815
  return;
35586
35816
  }
35587
35817
  if (!draft.found) {
@@ -35597,7 +35827,7 @@ async function runTrackSimilar(idOrLogId, options, trackSimilarCommand) {
35597
35827
  const limit = options.limit ? Number.parseInt(options.limit, 10) : undefined;
35598
35828
  const result = await trackSimilarCommand(idOrLogId, limit !== undefined && Number.isFinite(limit) ? limit : undefined);
35599
35829
  if (options.json) {
35600
- printJson(result);
35830
+ printJson2(result);
35601
35831
  return;
35602
35832
  }
35603
35833
  if (result.findings.length === 0) {
@@ -35612,7 +35842,7 @@ async function runTrackSimilar(idOrLogId, options, trackSimilarCommand) {
35612
35842
  }
35613
35843
  }
35614
35844
  function printSweepJson(payload, failedCount) {
35615
- printJson({ ...payload, failedCount, ok: failedCount === 0 });
35845
+ printJson2({ ...payload, failedCount, ok: failedCount === 0 });
35616
35846
  if (failedCount > 0) {
35617
35847
  process.exitCode = 1;
35618
35848
  }
@@ -35627,7 +35857,7 @@ async function runMigratePreviewArchive(options, migratePreviewArchiveCommand) {
35627
35857
  if (mode === "verify") {
35628
35858
  const result = await migratePreviewArchiveCommand({ dryRun: true, limit, mode });
35629
35859
  if (options.json) {
35630
- printJson({ mode, ok: true, remaining: result.remaining, sampleKeys: result.sampleKeys });
35860
+ printJson2({ mode, ok: true, remaining: result.remaining, sampleKeys: result.sampleKeys });
35631
35861
  return;
35632
35862
  }
35633
35863
  console.log(`${result.remaining} object(s) remain under the public analysis/previews/ prefix.`);
@@ -35780,7 +36010,7 @@ async function runCrawlCatalogue(options, crawlCatalogueCommand) {
35780
36010
  const maxHop = Number.parseInt(options.maxHop ?? "2", 10);
35781
36011
  const result = await crawlCatalogueCommand(limit, Number.isInteger(maxHop) && maxHop >= 0 ? maxHop : 2, options.dryRun);
35782
36012
  if (options.json) {
35783
- printJson(result);
36013
+ printJson2(result);
35784
36014
  } else if (result.dryRun) {
35785
36015
  console.log(`Dry run: would seed ${result.seeded} enabled label(s); ${result.frontierPending} node(s) pending. Nothing written.`);
35786
36016
  } else {
@@ -35803,7 +36033,7 @@ async function runCrawlCatalogue(options, crawlCatalogueCommand) {
35803
36033
  async function runCrawlStatus(options, crawlStatusCommand) {
35804
36034
  const result = await crawlStatusCommand();
35805
36035
  if (options.json) {
35806
- printJson(result);
36036
+ printJson2(result);
35807
36037
  return;
35808
36038
  }
35809
36039
  const { frontier, frontierByKind } = result;
@@ -35845,7 +36075,7 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand) {
35845
36075
  cursor = result.nextCursor;
35846
36076
  }
35847
36077
  if (options.json) {
35848
- printJson({
36078
+ printJson2({
35849
36079
  dryRun,
35850
36080
  ok: true,
35851
36081
  rateLimited: throttled,
@@ -36332,7 +36562,7 @@ async function runBackfillCoverMasters(options, backfillCoverMastersCommand) {
36332
36562
  cursor = result.nextCursor;
36333
36563
  }
36334
36564
  if (options.json) {
36335
- printJson({
36565
+ printJson2({
36336
36566
  dryRun,
36337
36567
  failed,
36338
36568
  failedCount: failed.length,
@@ -36395,7 +36625,7 @@ async function runBackfillLabelLineage(options, backfillLabelLineageCommand) {
36395
36625
  cursor = result.nextCursor;
36396
36626
  }
36397
36627
  if (options.json) {
36398
- printJson({
36628
+ printJson2({
36399
36629
  dryRun,
36400
36630
  failed,
36401
36631
  failedCount: failed.length,
@@ -36512,7 +36742,7 @@ async function runBackfillRecordingMbids(options, backfillRecordingMbidsCommand)
36512
36742
  cursor = result.nextCursor;
36513
36743
  }
36514
36744
  if (options.json) {
36515
- printJson({
36745
+ printJson2({
36516
36746
  dryRun,
36517
36747
  failed,
36518
36748
  failedCount: failed.length,
@@ -36575,7 +36805,7 @@ async function runBackfillArtistEdges(options, backfillArtistEdgesCommand) {
36575
36805
  cursor = result.nextCursor;
36576
36806
  }
36577
36807
  if (options.json) {
36578
- printJson({
36808
+ printJson2({
36579
36809
  dryRun,
36580
36810
  edgesWritten,
36581
36811
  fullyMatched,
@@ -36629,7 +36859,7 @@ async function runBackfillArtistCredits(options, backfillArtistCreditsCommand) {
36629
36859
  cursor = result.nextCursor;
36630
36860
  }
36631
36861
  if (options.json) {
36632
- printJson({
36862
+ printJson2({
36633
36863
  adoptedArtists,
36634
36864
  dryRun,
36635
36865
  edgesWritten,
@@ -36649,7 +36879,7 @@ async function runArtistResolveQueue(options, listArtistsCommand) {
36649
36879
  const limit = parseListLimit(options.limit);
36650
36880
  const result = await listArtistsCommand(limit);
36651
36881
  if (options.json) {
36652
- printJson({ artists: result.artists, ok: true });
36882
+ printJson2({ artists: result.artists, ok: true });
36653
36883
  return;
36654
36884
  }
36655
36885
  if (result.artists.length === 0) {
@@ -36668,7 +36898,7 @@ async function runArtistResolve(artistId, options, resolveArtistCommand) {
36668
36898
  }
36669
36899
  const result = await resolveArtistCommand(artistId);
36670
36900
  if (options.json) {
36671
- printJson(result);
36901
+ printJson2(result);
36672
36902
  return;
36673
36903
  }
36674
36904
  if (result.rateLimited) {
@@ -36732,7 +36962,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand) {
36732
36962
  allowPartial: options.allowPartial
36733
36963
  });
36734
36964
  if (options.json) {
36735
- printJson(result);
36965
+ printJson2(result);
36736
36966
  return;
36737
36967
  }
36738
36968
  console.log(`Linked video to ${result.logId}`);
@@ -36747,7 +36977,7 @@ async function runTrackDraft(idOrLogId, options, trackDraftCommand) {
36747
36977
  const platform = options.platform ?? "tiktok";
36748
36978
  const result = await trackDraftCommand(idOrLogId, platform);
36749
36979
  if (options.json) {
36750
- printJson(result);
36980
+ printJson2(result);
36751
36981
  return;
36752
36982
  }
36753
36983
  console.log(`Pushed ${platform} draft for ${result.trackId} (post ${result.externalId})`);
@@ -36759,7 +36989,7 @@ async function runTrackSocial(idOrLogId, options, trackSocialShowCommand, trackS
36759
36989
  if (!options.status) {
36760
36990
  const result = await trackSocialShowCommand(idOrLogId);
36761
36991
  if (options.json) {
36762
- printJson(result);
36992
+ printJson2(result);
36763
36993
  return;
36764
36994
  }
36765
36995
  if (result.posts.length === 0) {
@@ -36784,7 +37014,7 @@ async function runTrackSocial(idOrLogId, options, trackSocialShowCommand, trackS
36784
37014
  url: options.url
36785
37015
  });
36786
37016
  if (options.json) {
36787
- printJson(result);
37017
+ printJson2(result);
36788
37018
  return;
36789
37019
  }
36790
37020
  console.log(`${platform} \u2192 ${options.status} for ${result.trackId}`);
@@ -36796,7 +37026,7 @@ async function runTrackSocialCapture(options, trackSocialCaptureCommand) {
36796
37026
  }
36797
37027
  const result = await trackSocialCaptureCommand(limit);
36798
37028
  if (options.json) {
36799
- printJson(result);
37029
+ printJson2(result);
36800
37030
  return;
36801
37031
  }
36802
37032
  if (result.captured.length === 0) {
@@ -36814,7 +37044,7 @@ async function runTrackGet(idOrLogId, options, trackGetCommand) {
36814
37044
  }
36815
37045
  const result = await trackGetCommand(idOrLogId);
36816
37046
  if (options.json) {
36817
- printJson(result);
37047
+ printJson2(result);
36818
37048
  return;
36819
37049
  }
36820
37050
  if ("mixtape" in result) {
@@ -36840,7 +37070,7 @@ async function runMixableOrder(logIds, options, mixableOrderCommand) {
36840
37070
  }
36841
37071
  const result = await mixableOrderCommand(logIds, options.seed);
36842
37072
  if (options.json) {
36843
- printJson(result);
37073
+ printJson2(result);
36844
37074
  return;
36845
37075
  }
36846
37076
  console.log(`Proposed mix \u2014 a smooth chain (${result.algorithm}), not an energy-shaped set. Copy into Rekordbox as advisory.
@@ -36858,7 +37088,7 @@ async function runTrackGetAdmin(idOrLogId, options, trackGetAdminCommand) {
36858
37088
  }
36859
37089
  const result = await trackGetAdminCommand(idOrLogId);
36860
37090
  if (options.json) {
36861
- printJson(result);
37091
+ printJson2(result);
36862
37092
  return;
36863
37093
  }
36864
37094
  const t = result.track;
@@ -36927,7 +37157,7 @@ async function runTrackUpdate(trackId, options, trackUpdateCommand) {
36927
37157
  videoUrl: options.videoUrl
36928
37158
  });
36929
37159
  if (options.json) {
36930
- printJson(result);
37160
+ printJson2(result);
36931
37161
  return;
36932
37162
  }
36933
37163
  console.log(`Updated ${result.trackId}: ${result.fields.join(", ")}`);
@@ -36938,7 +37168,7 @@ async function runTrackRequeueVideo(idOrLogId, options, trackRequeueVideoCommand
36938
37168
  }
36939
37169
  const result = await trackRequeueVideoCommand(idOrLogId);
36940
37170
  if (options.json) {
36941
- printJson(result);
37171
+ printJson2(result);
36942
37172
  return;
36943
37173
  }
36944
37174
  console.log(result.alreadyClear ? `${result.logId} already had no video. Nothing to clear.` : `Cleared the video for ${result.logId}. It's back on the render queue (and off radio until re-rendered).`);
@@ -36962,7 +37192,7 @@ async function runTrackPinSource(idOrLogId, options, commands) {
36962
37192
  allowDurationMismatch: options.allowDurationMismatch
36963
37193
  });
36964
37194
  if (options.json) {
36965
- printJson(result);
37195
+ printJson2(result);
36966
37196
  return;
36967
37197
  }
36968
37198
  const who = result.logId ?? result.trackId;
@@ -36975,7 +37205,7 @@ async function runTrackPurgeVideo(idOrLogId, options, trackPurgeVideoCommand) {
36975
37205
  }
36976
37206
  const result = await trackPurgeVideoCommand(idOrLogId);
36977
37207
  if (options.json) {
36978
- printJson(result);
37208
+ printJson2(result);
36979
37209
  return;
36980
37210
  }
36981
37211
  console.log(result.noVideo ? `${result.logId} has no video. Nothing to purge.` : `Purging the stale renditions for ${result.logId} from the edge. The next play picks up the fresh render.`);
@@ -36986,7 +37216,7 @@ async function runMixtapeUpdate(id, options, mixtapeUpdateCommand) {
36986
37216
  }
36987
37217
  const result = await mixtapeUpdateCommand(id, options);
36988
37218
  if (options.json) {
36989
- printJson(result);
37219
+ printJson2(result);
36990
37220
  return;
36991
37221
  }
36992
37222
  console.log(`Saved ${result.mixtape.id}.`);
@@ -36998,7 +37228,7 @@ async function runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand
36998
37228
  const onProgress = options.json ? () => {} : (message) => console.log(message);
36999
37229
  const result = await mixtapeDistributeCommand(idOrLogId, options, onProgress);
37000
37230
  if (options.json) {
37001
- printJson(result);
37231
+ printJson2(result);
37002
37232
  return;
37003
37233
  }
37004
37234
  const links = result.results.map((r) => ` ${r.platform}: ${r.url}`).join(`
@@ -37012,7 +37242,7 @@ async function runMixtapePublishYoutube(idOrLogId, options, publishYoutubeComman
37012
37242
  }
37013
37243
  const result = await publishYoutubeCommand(idOrLogId);
37014
37244
  if (options.json) {
37015
- printJson(result);
37245
+ printJson2(result);
37016
37246
  return;
37017
37247
  }
37018
37248
  console.log(`YouTube video is now public: ${result.url}`);
@@ -37024,7 +37254,7 @@ async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand) {
37024
37254
  const onProgress = options.json ? () => {} : (message) => console.log(message);
37025
37255
  const result = await mixtapeResyncCommand(idOrLogId, options, onProgress);
37026
37256
  if (options.json) {
37027
- printJson(result);
37257
+ printJson2(result);
37028
37258
  return;
37029
37259
  }
37030
37260
  const links = result.results.map((r) => ` ${r.platform}: ${r.url}`).join(`
@@ -37035,7 +37265,7 @@ ${links}`);
37035
37265
  async function runMixtapeList(options, mixtapeListCommand) {
37036
37266
  const mixtapes = await mixtapeListCommand();
37037
37267
  if (options.json) {
37038
- printJson({ mixtapes, ok: true });
37268
+ printJson2({ mixtapes, ok: true });
37039
37269
  return;
37040
37270
  }
37041
37271
  if (mixtapes.length === 0) {
@@ -37055,7 +37285,7 @@ async function runClipsList(options, clipsListCommand, clipPostsListCommand) {
37055
37285
  ]);
37056
37286
  const dripByClip = new Map(posts.map((post) => [post.clipId, post]));
37057
37287
  if (options.json) {
37058
- printJson({
37288
+ printJson2({
37059
37289
  clips: clips.map((clip) => ({ ...clip, drip: dripByClip.get(clip.id) })),
37060
37290
  ok: true
37061
37291
  });
@@ -37079,7 +37309,7 @@ async function runClipsCut(clipId, options, clipCutCommand) {
37079
37309
  const onProgress = options.json ? () => {} : (message) => console.log(message);
37080
37310
  const result = await clipCutCommand(clipId, onProgress);
37081
37311
  if (options.json) {
37082
- printJson({ ok: true, ...result });
37312
+ printJson2({ ok: true, ...result });
37083
37313
  return;
37084
37314
  }
37085
37315
  console.log(`Cut ${result.clipId} \u2192 ${result.url} (${(result.sizeBytes / 1e6).toFixed(1)} MB).`);
@@ -37090,7 +37320,7 @@ async function runClipsSchedule(clipId, options, clipScheduleCommand) {
37090
37320
  }
37091
37321
  const post = await clipScheduleCommand(clipId, options.at);
37092
37322
  if (options.json) {
37093
- printJson({ ok: true, post });
37323
+ printJson2({ ok: true, post });
37094
37324
  return;
37095
37325
  }
37096
37326
  console.log(`Scheduled ${post.clipId} for ${post.scheduledFor}.`);
@@ -37098,7 +37328,7 @@ async function runClipsSchedule(clipId, options, clipScheduleCommand) {
37098
37328
  async function runClipsDripPause(paused, options, clipDripPauseCommand) {
37099
37329
  const result = await clipDripPauseCommand(paused);
37100
37330
  if (options.json) {
37101
- printJson({ ok: true, paused: result });
37331
+ printJson2({ ok: true, paused: result });
37102
37332
  return;
37103
37333
  }
37104
37334
  console.log(result ? "The drip-feed is paused." : "The drip-feed is running.");
@@ -37106,7 +37336,7 @@ async function runClipsDripPause(paused, options, clipDripPauseCommand) {
37106
37336
  async function runPublishAdvance(options, publishAdvanceCommand) {
37107
37337
  const result = await publishAdvanceCommand();
37108
37338
  if (options.json) {
37109
- printJson(result);
37339
+ printJson2(result);
37110
37340
  return;
37111
37341
  }
37112
37342
  if (result.paused) {
@@ -37130,7 +37360,7 @@ async function runPublishAdvance(options, publishAdvanceCommand) {
37130
37360
  }
37131
37361
  function printAnchorApifyBudget(state, options) {
37132
37362
  if (options.json) {
37133
- printJson({ ...state, ok: true });
37363
+ printJson2({ ...state, ok: true });
37134
37364
  return;
37135
37365
  }
37136
37366
  console.log(state.spent ? `APIFY ROWS SPENT for ${state.day} \u2014 the paid anchor rung sends nothing more today.` : `APIFY ROWS OPEN for ${state.day} \u2014 the paid anchor rung may still be sent rows.`);
@@ -37151,7 +37381,7 @@ function printCaptureBudget(state) {
37151
37381
  async function runCaptureBudget(options, captureBudgetCommand) {
37152
37382
  const state = await captureBudgetCommand();
37153
37383
  if (options.json) {
37154
- printJson(state);
37384
+ printJson2(state);
37155
37385
  return;
37156
37386
  }
37157
37387
  printCaptureBudget(state);
@@ -37159,7 +37389,7 @@ async function runCaptureBudget(options, captureBudgetCommand) {
37159
37389
  async function runSetCaptureBudget(input, options, setCaptureBudgetCommand) {
37160
37390
  const state = await setCaptureBudgetCommand(input);
37161
37391
  if (options.json) {
37162
- printJson(state);
37392
+ printJson2(state);
37163
37393
  return;
37164
37394
  }
37165
37395
  printCaptureBudget(state);
@@ -37194,7 +37424,7 @@ async function runSetCaptureCaps(options, setCaptureBudgetCommand) {
37194
37424
  async function runPublishAdvancePause(paused, options, publishAdvancePauseCommand) {
37195
37425
  const result = await publishAdvancePauseCommand(paused);
37196
37426
  if (options.json) {
37197
- printJson({ ok: true, paused: result });
37427
+ printJson2({ ok: true, paused: result });
37198
37428
  return;
37199
37429
  }
37200
37430
  console.log(result ? "The publish auto-advance is paused." : "The publish auto-advance is running.");
@@ -37205,7 +37435,7 @@ async function runMixtapeGet(idOrLogId, options, mixtapeGetCommand) {
37205
37435
  }
37206
37436
  const mixtape = await mixtapeGetCommand(idOrLogId);
37207
37437
  if (options.json) {
37208
- printJson(mixtape);
37438
+ printJson2(mixtape);
37209
37439
  return;
37210
37440
  }
37211
37441
  const coordinate = mixtape.logId ?? "unminted";
@@ -37236,7 +37466,7 @@ function formatCue(ms) {
37236
37466
  async function runNewsletterDraft(options, newsletterDraftCommand) {
37237
37467
  const result = await newsletterDraftCommand(options);
37238
37468
  if (options.json) {
37239
- printJson(result);
37469
+ printJson2(result);
37240
37470
  return;
37241
37471
  }
37242
37472
  console.log(`Drafted edition ${result.edition.id}. It stays a draft until the operator sends it.`);
@@ -37247,7 +37477,7 @@ async function runNewsletterUpdate(id, options, newsletterUpdateCommand) {
37247
37477
  }
37248
37478
  const result = await newsletterUpdateCommand(id, options);
37249
37479
  if (options.json) {
37250
- printJson(result);
37480
+ printJson2(result);
37251
37481
  return;
37252
37482
  }
37253
37483
  console.log(`Saved draft ${result.edition.id}.`);
@@ -37258,7 +37488,7 @@ async function runNewsletterSend(id, options, newsletterSendCommand) {
37258
37488
  }
37259
37489
  const result = await newsletterSendCommand(id);
37260
37490
  if (options.json) {
37261
- printJson(result);
37491
+ printJson2(result);
37262
37492
  return;
37263
37493
  }
37264
37494
  const number = result.edition.number;
@@ -37273,7 +37503,7 @@ async function runNewsletterDelete(id, options, newsletterDeleteCommand) {
37273
37503
  }
37274
37504
  const result = await newsletterDeleteCommand(id);
37275
37505
  if (options.json) {
37276
- printJson({ ...result, ok: true });
37506
+ printJson2({ ...result, ok: true });
37277
37507
  return;
37278
37508
  }
37279
37509
  console.log(`Deleted edition ${result.id}. The send window reopens to cover its finds.`);
@@ -37281,7 +37511,7 @@ async function runNewsletterDelete(id, options, newsletterDeleteCommand) {
37281
37511
  async function runNewsletterList(options, newsletterListCommand) {
37282
37512
  const editions = await newsletterListCommand();
37283
37513
  if (options.json) {
37284
- printJson({ editions, ok: true });
37514
+ printJson2({ editions, ok: true });
37285
37515
  return;
37286
37516
  }
37287
37517
  if (editions.length === 0) {
@@ -37298,7 +37528,7 @@ async function runLogbookGaps(options, logbookGapsCommand) {
37298
37528
  const limit = options.limit === undefined ? undefined : Number.parseInt(options.limit, 10);
37299
37529
  const { gaps, spent } = await logbookGapsCommand(limit !== undefined && Number.isFinite(limit) ? limit : undefined);
37300
37530
  if (options.json) {
37301
- printJson({ gaps, ok: true, spent });
37531
+ printJson2({ gaps, ok: true, spent });
37302
37532
  return;
37303
37533
  }
37304
37534
  if (gaps.length === 0) {
@@ -37320,7 +37550,7 @@ async function runLogbookWrite(sector, options, writeCommand) {
37320
37550
  title: options.title
37321
37551
  });
37322
37552
  if (options.json) {
37323
- printJson(result);
37553
+ printJson2(result);
37324
37554
  return;
37325
37555
  }
37326
37556
  const skipped = "skipped" in result && result.skipped;
@@ -37335,7 +37565,7 @@ function requirePromptSlug(slug) {
37335
37565
  async function runPromptsList(options, promptsListCommand) {
37336
37566
  const prompts = await promptsListCommand();
37337
37567
  if (options.json) {
37338
- printJson({ ok: true, prompts });
37568
+ printJson2({ ok: true, prompts });
37339
37569
  return;
37340
37570
  }
37341
37571
  await Promise.resolve().then(() => init_admin_prompts2());
@@ -37346,7 +37576,7 @@ async function runPromptsList(options, promptsListCommand) {
37346
37576
  async function runPromptGet(slug, options, promptGetCommand) {
37347
37577
  const resolved = await promptGetCommand(requirePromptSlug(slug));
37348
37578
  if (options.json) {
37349
- printJson(resolved);
37579
+ printJson2(resolved);
37350
37580
  return;
37351
37581
  }
37352
37582
  const live = resolved.source === "override" ? `v${resolved.version}, an operator edit` : "the repo's baked default (v0)";
@@ -37357,7 +37587,7 @@ async function runPromptGet(slug, options, promptGetCommand) {
37357
37587
  async function runPromptHistory(slug, options, promptDetailCommand) {
37358
37588
  const detail = await promptDetailCommand(requirePromptSlug(slug));
37359
37589
  if (options.json) {
37360
- printJson({
37590
+ printJson2({
37361
37591
  activeVersion: detail.activeVersion,
37362
37592
  ok: true,
37363
37593
  slug: detail.slug,
@@ -37379,7 +37609,7 @@ async function runPromptDiff(slug, options, commands) {
37379
37609
  const against = commands.parseAgainst(options.against);
37380
37610
  const result = await commands.promptDiffCommand(requirePromptSlug(slug), against);
37381
37611
  if (options.json) {
37382
- printJson({ ok: true, ...result });
37612
+ printJson2({ ok: true, ...result });
37383
37613
  return;
37384
37614
  }
37385
37615
  const live = result.live.source === "override" ? `v${result.live.version}` : "the repo's baked default";
@@ -37400,7 +37630,7 @@ async function runPromptUpdate(slug, options, promptUpdateCommand) {
37400
37630
  note: options.note
37401
37631
  });
37402
37632
  if (options.json) {
37403
- printJson({ ok: true, ...result });
37633
+ printJson2({ ok: true, ...result });
37404
37634
  return;
37405
37635
  }
37406
37636
  console.log(`${result.slug}: v${result.version} is live. Nothing to deploy.`);
@@ -37416,7 +37646,7 @@ async function runPromptRollback(slug, version, options, commands) {
37416
37646
  }
37417
37647
  const result = await commands.promptRollbackCommand(promptSlug, parsed);
37418
37648
  if (options.json) {
37419
- printJson({ ok: true, ...result });
37649
+ printJson2({ ok: true, ...result });
37420
37650
  return;
37421
37651
  }
37422
37652
  console.log(result.skipped ? `${result.slug}: v${result.from} is already the body running. Nothing appended.` : `${result.slug}: back on v${result.from}'s body, live as v${result.version}.`);
@@ -37424,7 +37654,7 @@ async function runPromptRollback(slug, version, options, commands) {
37424
37654
  async function runPromptReset(slug, options, promptResetCommand) {
37425
37655
  const result = await promptResetCommand(requirePromptSlug(slug));
37426
37656
  if (options.json) {
37427
- printJson({ ok: true, ...result });
37657
+ printJson2({ ok: true, ...result });
37428
37658
  return;
37429
37659
  }
37430
37660
  console.log(result.skipped ? `${result.slug}: already running the repo's baked default. Nothing appended.` : `${result.slug}: back on the repo's baked default, live as v${result.version}.`);
@@ -37439,7 +37669,7 @@ async function runAdd(spotifyUrl, options, addCommand) {
37439
37669
  note: options.note
37440
37670
  });
37441
37671
  if (options.json) {
37442
- printJson({
37672
+ printJson2({
37443
37673
  ok: true,
37444
37674
  ...result
37445
37675
  });
@@ -37467,7 +37697,7 @@ async function runRecent(options, recentCommand) {
37467
37697
  const limit = parseListLimit(options.limit);
37468
37698
  const tracks = await recentCommand(limit);
37469
37699
  if (options.json) {
37470
- printJson({
37700
+ printJson2({
37471
37701
  ok: true,
37472
37702
  tracks
37473
37703
  });
@@ -37485,7 +37715,7 @@ async function runGalaxies(slug, options, commands) {
37485
37715
  if (slug) {
37486
37716
  const { findings, galaxy } = await commands.galaxyGetCommand(slug);
37487
37717
  if (options.json) {
37488
- printJson({ findings, galaxy, ok: true });
37718
+ printJson2({ findings, galaxy, ok: true });
37489
37719
  return;
37490
37720
  }
37491
37721
  console.log(`${galaxy.name} (${galaxy.slug})`);
@@ -37498,7 +37728,7 @@ async function runGalaxies(slug, options, commands) {
37498
37728
  }
37499
37729
  const galaxies = await commands.galaxiesListCommand();
37500
37730
  if (options.json) {
37501
- printJson({ galaxies, ok: true });
37731
+ printJson2({ galaxies, ok: true });
37502
37732
  return;
37503
37733
  }
37504
37734
  if (galaxies.length === 0) {
@@ -37515,7 +37745,7 @@ async function runArtistsRank(options, rankArtistsCommand) {
37515
37745
  limit: options.limit
37516
37746
  });
37517
37747
  if (options.json) {
37518
- printJson({ ok: true, summary });
37748
+ printJson2({ ok: true, summary });
37519
37749
  return;
37520
37750
  }
37521
37751
  const removed = summary.centroidsRemoved > 0 ? ` Purged ${summary.centroidsRemoved} orphan(s).` : "";
@@ -37527,7 +37757,7 @@ async function runCatalogueRank(options, catalogueRankCommand) {
37527
37757
  limit: options.limit
37528
37758
  });
37529
37759
  if (options.json) {
37530
- printJson({ ok: true, summary, telescope });
37760
+ printJson2({ ok: true, summary, telescope });
37531
37761
  return;
37532
37762
  }
37533
37763
  const quarantine = summary.quarantined > 0 ? ` Quarantined ${summary.quarantined} as wrong audio.` : "";
@@ -37548,7 +37778,7 @@ async function runCatalogueList(options, catalogueListCommand) {
37548
37778
  limit: options.limit
37549
37779
  });
37550
37780
  if (options.json) {
37551
- printJson({ ok: true, summary, tracks });
37781
+ printJson2({ ok: true, summary, tracks });
37552
37782
  return;
37553
37783
  }
37554
37784
  if (tracks.length === 0) {
@@ -37584,7 +37814,7 @@ async function runCatalogueList(options, catalogueListCommand) {
37584
37814
  async function runNoteHeld(options, noteHeldCommand) {
37585
37815
  const { gate, rejections } = await noteHeldCommand({ settled: options.settled === true });
37586
37816
  if (options.json) {
37587
- printJson({ gate, ok: true, rejections });
37817
+ printJson2({ gate, ok: true, rejections });
37588
37818
  return;
37589
37819
  }
37590
37820
  if (rejections.length === 0) {
@@ -37614,7 +37844,7 @@ async function runNoteGate(options, noteGateCommand) {
37614
37844
  ...minPhraseWords === undefined ? {} : { minPhraseWords }
37615
37845
  });
37616
37846
  if (options.json) {
37617
- printJson({ gate, ok: true });
37847
+ printJson2({ gate, ok: true });
37618
37848
  return;
37619
37849
  }
37620
37850
  console.log(`The echo gate: a lift is ${gate.minPhraseWords}+ shared words; an echo is ${Math.round(gate.maxOverlap * 100)}%+ of a neighbour's words.`);
@@ -37622,7 +37852,7 @@ async function runNoteGate(options, noteGateCommand) {
37622
37852
  async function runObservationHeld(options, observationHeldCommand) {
37623
37853
  const { gate, rejections } = await observationHeldCommand({ settled: options.settled === true });
37624
37854
  if (options.json) {
37625
- printJson({ gate, ok: true, rejections });
37855
+ printJson2({ gate, ok: true, rejections });
37626
37856
  return;
37627
37857
  }
37628
37858
  if (rejections.length === 0) {
@@ -37652,7 +37882,7 @@ async function runObservationGate(options, observationGateCommand) {
37652
37882
  ...minPhraseWords === undefined ? {} : { minPhraseWords }
37653
37883
  });
37654
37884
  if (options.json) {
37655
- printJson({ gate, ok: true });
37885
+ printJson2({ gate, ok: true });
37656
37886
  return;
37657
37887
  }
37658
37888
  console.log(`The observation echo gate: a lift is ${gate.minPhraseWords}+ shared words; an echo is ${Math.round(gate.maxOverlap * 100)}%+ of a neighbour's words.`);
@@ -37660,7 +37890,7 @@ async function runObservationGate(options, observationGateCommand) {
37660
37890
  async function runGalaxyMapRead(options, galaxyMapReadCommand) {
37661
37891
  const galaxies = await galaxyMapReadCommand();
37662
37892
  if (options.json) {
37663
- printJson({ galaxies, ok: true });
37893
+ printJson2({ galaxies, ok: true });
37664
37894
  return;
37665
37895
  }
37666
37896
  if (galaxies.length === 0) {
@@ -37676,7 +37906,7 @@ async function runGalaxyMapRead(options, galaxyMapReadCommand) {
37676
37906
  async function runGalaxyEmbeddings(options, galaxyEmbeddingsCommand) {
37677
37907
  const result = await galaxyEmbeddingsCommand({ cursor: options.cursor, limit: options.limit });
37678
37908
  if (options.json) {
37679
- printJson(result);
37909
+ printJson2(result);
37680
37910
  return;
37681
37911
  }
37682
37912
  console.log(`${result.embeddings.length} embedding(s)${result.nextCursor ? ` \xB7 nextCursor: ${result.nextCursor}` : " \xB7 end of corpus"}`);
@@ -37689,7 +37919,7 @@ async function runGalaxyMapWrite(options, galaxyMapWriteCommand) {
37689
37919
  }
37690
37920
  const galaxies = await galaxyMapWriteCommand(clusters);
37691
37921
  if (options.json) {
37692
- printJson({ galaxies, ok: true });
37922
+ printJson2({ galaxies, ok: true });
37693
37923
  return;
37694
37924
  }
37695
37925
  console.log(`Wrote ${clusters.length} cluster(s); the map now holds ${galaxies.length} galaxy(ies).`);
@@ -37697,7 +37927,7 @@ async function runGalaxyMapWrite(options, galaxyMapWriteCommand) {
37697
37927
  async function runMixtapes(options, mixtapesCommand) {
37698
37928
  const mixtapes = await mixtapesCommand();
37699
37929
  if (options.json) {
37700
- printJson({ mixtapes, ok: true });
37930
+ printJson2({ mixtapes, ok: true });
37701
37931
  return;
37702
37932
  }
37703
37933
  if (mixtapes.length === 0) {
@@ -37832,7 +38062,7 @@ async function runAdminTracksList(options, listCommand) {
37832
38062
  const hasKey = resolveHasKey(options);
37833
38063
  const tracks = await listCommand({ hasKey, limit, order });
37834
38064
  if (options.json) {
37835
- printJson({ ok: true, tracks });
38065
+ printJson2({ ok: true, tracks });
37836
38066
  return;
37837
38067
  }
37838
38068
  if (tracks.length === 0) {
@@ -37850,7 +38080,7 @@ async function runAdminTracksList(options, listCommand) {
37850
38080
  async function runAdminAttention(options, attentionQueueCommand) {
37851
38081
  const queue = await attentionQueueCommand();
37852
38082
  if (options.json) {
37853
- printJson({ attention: queue, ok: true });
38083
+ printJson2({ attention: queue, ok: true });
37854
38084
  return;
37855
38085
  }
37856
38086
  await Promise.resolve().then(() => init_admin_attention());
@@ -37881,7 +38111,7 @@ async function runAdminTelemetry(options, telemetryCommand) {
37881
38111
  until
37882
38112
  });
37883
38113
  if (options.json) {
37884
- printJson({ ok: true, ...page });
38114
+ printJson2({ ok: true, ...page });
37885
38115
  return;
37886
38116
  }
37887
38117
  await Promise.resolve().then(() => init_admin_telemetry());
@@ -37894,7 +38124,7 @@ async function runAdminQueue(options, queueCommand) {
37894
38124
  hasObservation: options.hasObservation ? true : undefined
37895
38125
  });
37896
38126
  if (options.json) {
37897
- printJson({
38127
+ printJson2({
37898
38128
  ok: true,
37899
38129
  tracks
37900
38130
  });
@@ -37919,7 +38149,7 @@ async function runQueueView({
37919
38149
  const limit = parseListLimit(options.limit);
37920
38150
  const tracks = await fetch2(limit);
37921
38151
  if (options.json) {
37922
- printJson({
38152
+ printJson2({
37923
38153
  ok: true,
37924
38154
  tracks
37925
38155
  });
@@ -38007,7 +38237,7 @@ async function runAdminTrackWork(options, trackWorkCommand) {
38007
38237
  scope
38008
38238
  });
38009
38239
  if (options.json) {
38010
- printJson({ debtPending, ok: true, queued, tracks });
38240
+ printJson2({ debtPending, ok: true, queued, tracks });
38011
38241
  return;
38012
38242
  }
38013
38243
  if (debtPending === true) {
@@ -38091,7 +38321,7 @@ async function runAdminVehicles(options, vehiclesCommand) {
38091
38321
  const limit = parseListLimit(options.limit);
38092
38322
  const vehicles = await vehiclesCommand(limit);
38093
38323
  if (options.json) {
38094
- printJson({
38324
+ printJson2({
38095
38325
  ok: true,
38096
38326
  vehicles
38097
38327
  });
@@ -38137,7 +38367,7 @@ async function runOpen(target, extra, options, openCommands) {
38137
38367
  async function runRandom(options, randomCommand) {
38138
38368
  const track = await randomCommand();
38139
38369
  if (options.json) {
38140
- printJson({
38370
+ printJson2({
38141
38371
  ok: true,
38142
38372
  track
38143
38373
  });
@@ -38157,7 +38387,7 @@ async function runRandom(options, randomCommand) {
38157
38387
  async function runStatus(options, statusCommand) {
38158
38388
  const snapshot = await statusCommand();
38159
38389
  if (options.json) {
38160
- printJson({
38390
+ printJson2({
38161
38391
  ok: true,
38162
38392
  ...snapshot
38163
38393
  });
@@ -38170,7 +38400,7 @@ async function runStatus(options, statusCommand) {
38170
38400
  async function runMe(options, meCommand) {
38171
38401
  const me = await meCommand();
38172
38402
  if (options.json) {
38173
- printJson({ ok: true, ...me });
38403
+ printJson2({ ok: true, ...me });
38174
38404
  return;
38175
38405
  }
38176
38406
  console.log(me.name);
@@ -38291,6 +38521,10 @@ function topLevelCommand(args) {
38291
38521
  if (arg.startsWith("--env=")) {
38292
38522
  continue;
38293
38523
  }
38524
+ if (stringOptions.has(arg)) {
38525
+ index += 1;
38526
+ continue;
38527
+ }
38294
38528
  if (!arg.startsWith("-")) {
38295
38529
  return arg;
38296
38530
  }
@@ -38311,6 +38545,7 @@ function normalizeCommanderError(error) {
38311
38545
  return new Error(message);
38312
38546
  }
38313
38547
  var stringOptions = new Set([
38548
+ "--timeout",
38314
38549
  "--isrc-refresh-limit",
38315
38550
  "--action",
38316
38551
  "--against",