fluncle 0.275.0 → 0.276.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 +502 -314
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -60,6 +60,43 @@ 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 writeStdoutSync(text) {
66
+ const buf = Buffer.from(text, "utf8");
67
+ let offset = 0;
68
+ while (offset < buf.length) {
69
+ try {
70
+ offset += writeSync(1, buf, offset);
71
+ } catch (error) {
72
+ if (error.code === "EAGAIN") {
73
+ Atomics.wait(EAGAIN_WAIT, 0, 0, 1);
74
+ continue;
75
+ }
76
+ throw error;
77
+ }
78
+ }
79
+ }
80
+ function isJsonFailure(value) {
81
+ return typeof value === "object" && value !== null && typeof value.code === "string" && typeof value.message === "string";
82
+ }
83
+ function printJson(value) {
84
+ writeStdoutSync(JSON.stringify(value, null, 2) + `
85
+ `);
86
+ }
87
+ var EAGAIN_WAIT, CliError;
88
+ var init_output = __esm(() => {
89
+ EAGAIN_WAIT = new Int32Array(new SharedArrayBuffer(4));
90
+ CliError = class CliError extends Error {
91
+ code;
92
+ constructor(code, message) {
93
+ super(message);
94
+ this.name = "CliError";
95
+ this.code = code;
96
+ }
97
+ };
98
+ });
99
+
63
100
  // ../../node_modules/dotenv/lib/main.js
64
101
  var require_main = __commonJS(function(exports, module) {
65
102
  var fs = __require("fs");
@@ -379,6 +416,42 @@ var require_main = __commonJS(function(exports, module) {
379
416
  module.exports = DotenvModule;
380
417
  });
381
418
 
419
+ // src/deadline.ts
420
+ function fireDeadlineNow() {
421
+ state2.fire?.();
422
+ }
423
+ function deadlineAt() {
424
+ return state2.at;
425
+ }
426
+ function deadlineSignal() {
427
+ return state2.controller.signal;
428
+ }
429
+ function remainingDeadlineMs(now = Date.now) {
430
+ return state2.at === null ? null : state2.at - now();
431
+ }
432
+ function assertBeforeDeadline(doing) {
433
+ const remaining = remainingDeadlineMs();
434
+ if (remaining !== null && remaining <= 0) {
435
+ fireDeadlineNow();
436
+ throw new CliError("timeout", `the deadline passed while ${doing}`);
437
+ }
438
+ }
439
+ function noteProgress(note) {
440
+ state2.note = note;
441
+ }
442
+ var state2;
443
+ var init_deadline = __esm(() => {
444
+ init_output();
445
+ state2 = {
446
+ at: null,
447
+ cancel: null,
448
+ controller: new AbortController,
449
+ fire: null,
450
+ fired: false,
451
+ note: null
452
+ };
453
+ });
454
+
382
455
  // src/env.ts
383
456
  var exports_env = {};
384
457
  __export(exports_env, {
@@ -422,15 +495,22 @@ function resolveTokenRef() {
422
495
  if (process.env.FLUNCLE_API_TOKEN !== undefined || !ref || false) {
423
496
  return;
424
497
  }
498
+ assertBeforeDeadline("reading FLUNCLE_API_TOKEN_REF with op");
499
+ const remaining = remainingDeadlineMs();
500
+ const timeout = remaining === null ? OP_READ_TIMEOUT_MS : Math.max(1, Math.min(OP_READ_TIMEOUT_MS, remaining));
425
501
  try {
426
502
  const secretRef = ref.startsWith("op://") ? ref : `op://${ref}`;
427
503
  process.env.FLUNCLE_API_TOKEN = execFileSync("op", ["read", "--no-newline", secretRef], {
428
504
  encoding: "utf8",
429
- stdio: ["ignore", "pipe", "pipe"]
505
+ stdio: ["ignore", "pipe", "pipe"],
506
+ timeout
430
507
  });
431
- } catch {
432
- throw new Error(`Could not read FLUNCLE_API_TOKEN_REF (${ref}) with op.`);
508
+ } catch (error) {
509
+ assertBeforeDeadline("reading FLUNCLE_API_TOKEN_REF with op");
510
+ const timedOut = error.code === "ETIMEDOUT";
511
+ 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
512
  }
513
+ assertBeforeDeadline("reading FLUNCLE_API_TOKEN_REF with op");
434
514
  }
435
515
  function loadConfig() {
436
516
  const profile = getEnvProfile();
@@ -451,8 +531,9 @@ function getEnvProfile() {
451
531
  function isEnvProfile2(value) {
452
532
  return envProfiles2.includes(value);
453
533
  }
454
- var import_dotenv2, envProfiles2, defaultEnvProfile = "production", loadedProfile;
534
+ var import_dotenv2, envProfiles2, defaultEnvProfile = "production", OP_READ_TIMEOUT_MS = 20000, loadedProfile;
455
535
  var init_env = __esm(() => {
536
+ init_deadline();
456
537
  import_dotenv2 = __toESM(require_main(), 1);
457
538
  envProfiles2 = ["local", "production"];
458
539
  });
@@ -591,43 +672,6 @@ var init_util = __esm(() => {
591
672
  TIKTOK_DRAFT_STALE_MS = 24 * 60 * 60 * 1000;
592
673
  });
593
674
 
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
675
  // src/version.ts
632
676
  function normalizeVersion(version) {
633
677
  return version?.trim().replace(/^v/i, "");
@@ -651,7 +695,7 @@ function parseVersion(version) {
651
695
  var currentVersion;
652
696
  var init_version = __esm(() => {
653
697
  init_output();
654
- currentVersion = "0.275.0".trim() ? "0.275.0".trim() : "0.1.0";
698
+ currentVersion = "0.276.0".trim() ? "0.276.0".trim() : "0.1.0";
655
699
  });
656
700
 
657
701
  // src/update-notifier.ts
@@ -9598,7 +9642,7 @@ async function userApiGet(path) {
9598
9642
  function userHeaders() {
9599
9643
  const stored = readUserToken();
9600
9644
  if (!stored) {
9601
- throw new CliError2("not_logged_in", "You're not signed in. Run `fluncle login` to link this device to your account.");
9645
+ throw new CliError("not_logged_in", "You're not signed in. Run `fluncle login` to link this device to your account.");
9602
9646
  }
9603
9647
  return {
9604
9648
  Authorization: `Bearer ${stored.token}`
@@ -9672,11 +9716,15 @@ function adminHeaders() {
9672
9716
  }
9673
9717
  async function apiRequest(path, init = {}) {
9674
9718
  const url = `${getApiBaseUrl()}${path}`;
9719
+ noteProgress(`waiting for the Fluncle API: ${init.method ?? "GET"} ${path}`);
9675
9720
  return runPromise2(gen2(function* () {
9676
9721
  const { response, text } = yield* tryPromise2({
9677
9722
  catch: (cause) => new ApiTransportError({ cause }),
9678
9723
  try: async (signal) => {
9679
- const response = await fetch(url, { ...init, signal });
9724
+ const response = await fetch(url, {
9725
+ ...init,
9726
+ signal: AbortSignal.any([signal, deadlineSignal()])
9727
+ });
9680
9728
  return { response, text: await response.text() };
9681
9729
  }
9682
9730
  });
@@ -9689,7 +9737,7 @@ async function apiRequest(path, init = {}) {
9689
9737
  });
9690
9738
  }
9691
9739
  return data;
9692
- }).pipe(mapError2((error) => error._tag === "ApiTransportError" ? error.cause : new CliError2(error.code, error.message))));
9740
+ }).pipe(mapError2((error) => error._tag === "ApiTransportError" ? error.cause : new CliError(error.code, error.message))));
9693
9741
  }
9694
9742
  function parseJson2(text) {
9695
9743
  if (!text) {
@@ -9703,6 +9751,7 @@ function parseJson2(text) {
9703
9751
  var ApiTransportError, ApiResponseError;
9704
9752
  var init_api = __esm(() => {
9705
9753
  init_dist();
9754
+ init_deadline();
9706
9755
  init_env();
9707
9756
  init_output();
9708
9757
  init_user_token();
@@ -9788,7 +9837,7 @@ async function freshCommand({
9788
9837
  if (showTracks) {
9789
9838
  payload.tracks = response.tracks;
9790
9839
  }
9791
- printJson2(payload);
9840
+ printJson(payload);
9792
9841
  return;
9793
9842
  }
9794
9843
  const blocks = [];
@@ -9855,7 +9904,7 @@ async function albumsCommand({
9855
9904
  if (slug) {
9856
9905
  const response = await publicApiGet(`/api/v1/albums/${encodeURIComponent(slug)}`);
9857
9906
  if (json) {
9858
- printJson2(response);
9907
+ printJson(response);
9859
9908
  return;
9860
9909
  }
9861
9910
  const { album } = response;
@@ -9869,7 +9918,7 @@ async function albumsCommand({
9869
9918
  }
9870
9919
  const response = await publicApiGet(`/api/v1/albums?page=${page}`);
9871
9920
  if (json) {
9872
- printJson2(response);
9921
+ printJson(response);
9873
9922
  return;
9874
9923
  }
9875
9924
  printEntityIndex(response.albums, response, { plural: "albums", singular: "album" }, "albums");
@@ -9888,7 +9937,7 @@ async function artistsCommand({
9888
9937
  if (slug) {
9889
9938
  const response = await publicApiGet(`/api/v1/artists/${encodeURIComponent(slug)}`);
9890
9939
  if (json) {
9891
- printJson2(response);
9940
+ printJson(response);
9892
9941
  return;
9893
9942
  }
9894
9943
  const { artist } = response;
@@ -9902,7 +9951,7 @@ async function artistsCommand({
9902
9951
  }
9903
9952
  const response = await publicApiGet(`/api/v1/artists?page=${page}`);
9904
9953
  if (json) {
9905
- printJson2(response);
9954
+ printJson(response);
9906
9955
  return;
9907
9956
  }
9908
9957
  printEntityIndex(response.artists, response, { plural: "artists", singular: "artist" }, "artists");
@@ -9921,7 +9970,7 @@ async function labelsCommand({
9921
9970
  if (slug) {
9922
9971
  const response = await publicApiGet(`/api/v1/labels/${encodeURIComponent(slug)}`);
9923
9972
  if (json) {
9924
- printJson2(response);
9973
+ printJson(response);
9925
9974
  return;
9926
9975
  }
9927
9976
  const { label } = response;
@@ -9938,7 +9987,7 @@ async function labelsCommand({
9938
9987
  }
9939
9988
  const response = await publicApiGet(`/api/v1/labels?page=${page}`);
9940
9989
  if (json) {
9941
- printJson2(response);
9990
+ printJson(response);
9942
9991
  return;
9943
9992
  }
9944
9993
  printEntityIndex(response.labels, response, { plural: "labels", singular: "label" }, "labels");
@@ -9978,7 +10027,7 @@ async function searchCommand({
9978
10027
  }
9979
10028
  const response = await publicApiGet(`/api/v1/search/archive?${params.toString()}`);
9980
10029
  if (json) {
9981
- printJson2(response);
10030
+ printJson(response);
9982
10031
  return;
9983
10032
  }
9984
10033
  const blocks = [];
@@ -10049,7 +10098,7 @@ async function mixtapeGetCommand(idOrLogId) {
10049
10098
  const mixtapes = await mixtapeListCommand();
10050
10099
  const match = mixtapes.find((mixtape) => mixtape.id === idOrLogId || mixtape.logId === idOrLogId);
10051
10100
  if (!match) {
10052
- throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10101
+ throw new CliError("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10053
10102
  }
10054
10103
  return match;
10055
10104
  }
@@ -10071,7 +10120,7 @@ async function distributeYoutube(mixtapeId, videoPath, onProgress) {
10071
10120
  while (videoId === undefined) {
10072
10121
  attempts += 1;
10073
10122
  if (attempts > MAX_UPLOAD_ATTEMPTS) {
10074
- throw new CliError2("youtube_upload_failed", `YouTube upload did not complete after ${MAX_UPLOAD_ATTEMPTS} attempts`);
10123
+ throw new CliError("youtube_upload_failed", `YouTube upload did not complete after ${MAX_UPLOAD_ATTEMPTS} attempts`);
10075
10124
  }
10076
10125
  onProgress?.(offset > 0 ? `Resuming upload at ${formatBytes(offset)} / ${formatBytes(contentLength)}` : `Uploading video → YouTube`);
10077
10126
  const result = await putChunk({
@@ -10130,7 +10179,7 @@ async function putChunk(args) {
10130
10179
  if (response.status === 200 || response.status === 201) {
10131
10180
  const video = await response.json().catch(() => ({}));
10132
10181
  if (!video.id) {
10133
- throw new CliError2("youtube_no_video_id", "YouTube returned no video id on upload completion");
10182
+ throw new CliError("youtube_no_video_id", "YouTube returned no video id on upload completion");
10134
10183
  }
10135
10184
  return { kind: "done", videoId: video.id };
10136
10185
  }
@@ -10144,7 +10193,7 @@ async function putChunk(args) {
10144
10193
  return { kind: "reinit" };
10145
10194
  }
10146
10195
  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}` : ""}`);
10196
+ throw new CliError("youtube_put_failed", `YouTube upload PUT failed (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`);
10148
10197
  }
10149
10198
  async function queryOffset(sessionUri, accessToken, contentLength) {
10150
10199
  const response = await fetch(sessionUri, {
@@ -10176,7 +10225,7 @@ function fileSize(path) {
10176
10225
  try {
10177
10226
  return statSync(path).size;
10178
10227
  } catch {
10179
- throw new CliError2("video_not_found", `Cannot read video file at ${path}`);
10228
+ throw new CliError("video_not_found", `Cannot read video file at ${path}`);
10180
10229
  }
10181
10230
  }
10182
10231
  function formatBytes(bytes) {
@@ -10200,11 +10249,11 @@ async function distributeMixcloud(mixtapeId, audioPath, onProgress, unlisted = f
10200
10249
  const mixtape = await mixtapeGetCommand(mixtapeId);
10201
10250
  const logId = mixtape.logId;
10202
10251
  if (!logId) {
10203
- throw new CliError2("mixtape_no_log_id", "The mixtape has no Log ID; mint it before distributing");
10252
+ throw new CliError("mixtape_no_log_id", "The mixtape has no Log ID; mint it before distributing");
10204
10253
  }
10205
10254
  const audio = Bun.file(audioPath);
10206
10255
  if (!await audio.exists()) {
10207
- throw new CliError2("audio_not_found", `Audio master not found: ${audioPath}`);
10256
+ throw new CliError("audio_not_found", `Audio master not found: ${audioPath}`);
10208
10257
  }
10209
10258
  const form = new FormData;
10210
10259
  form.append("mp3", audio);
@@ -10241,7 +10290,7 @@ async function distributeMixcloud(mixtapeId, audioPath, onProgress, unlisted = f
10241
10290
  }
10242
10291
  const result = parseUploadResult(uploadText);
10243
10292
  if (!result.success || !result.key) {
10244
- throw new CliError2("mixcloud_upload_rejected", `Mixcloud rejected the upload: ${result.message ?? uploadText.slice(0, 300)}`);
10293
+ throw new CliError("mixcloud_upload_rejected", `Mixcloud rejected the upload: ${result.message ?? uploadText.slice(0, 300)}`);
10245
10294
  }
10246
10295
  const externalId = result.key;
10247
10296
  const url = `https://www.mixcloud.com${result.key}`;
@@ -10279,7 +10328,7 @@ async function fetchMixcloudToken() {
10279
10328
  const response = await adminApiPost("/api/v1/admin/mixcloud/token");
10280
10329
  return response.accessToken;
10281
10330
  } catch {
10282
- throw new CliError2("mixcloud_not_connected", "Mixcloud is not connected. Run `fluncle admin auth mixcloud` to authorize it.");
10331
+ throw new CliError("mixcloud_not_connected", "Mixcloud is not connected. Run `fluncle admin auth mixcloud` to authorize it.");
10283
10332
  }
10284
10333
  }
10285
10334
  async function fetchCover(logId) {
@@ -10309,9 +10358,9 @@ function parseUploadResult(body) {
10309
10358
  }
10310
10359
  function throwMixcloudError(status, body) {
10311
10360
  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`.");
10361
+ throw new CliError("mixcloud_invalid_token", "Mixcloud rejected the access token. Re-auth with `fluncle admin auth mixcloud`.");
10313
10362
  }
10314
- throw new CliError2("mixcloud_request_failed", `Mixcloud responded ${status}${body ? `: ${body.slice(0, 300)}` : ""}`);
10363
+ throw new CliError("mixcloud_request_failed", `Mixcloud responded ${status}${body ? `: ${body.slice(0, 300)}` : ""}`);
10315
10364
  }
10316
10365
  var MIXCLOUD_API = "https://api.mixcloud.com", DESCRIPTION_MAX = 1000, PICTURE_MAX_BYTES, COVER_BASE = "https://www.fluncle.com/api/mixtape-cover";
10317
10366
  var init_mixtape_mixcloud = __esm(() => {
@@ -10333,21 +10382,21 @@ async function mixtapeUpdateCommand(id, options) {
10333
10382
  async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {}) {
10334
10383
  const mixtape = await mixtapeGetCommand(idOrLogId);
10335
10384
  if (!mixtape.id) {
10336
- throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10385
+ throw new CliError("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10337
10386
  }
10338
10387
  const both = !options.youtube && !options.mixcloud;
10339
10388
  const doYoutube = both || Boolean(options.youtube);
10340
10389
  const doMixcloud = both || Boolean(options.mixcloud);
10341
10390
  if (doYoutube && !options.video) {
10342
- throw new CliError2("missing_video", "YouTube distribution needs --video <mp4>");
10391
+ throw new CliError("missing_video", "YouTube distribution needs --video <mp4>");
10343
10392
  }
10344
10393
  if (doMixcloud && !options.audio) {
10345
- throw new CliError2("missing_audio", "Mixcloud distribution needs --audio <file>");
10394
+ throw new CliError("missing_audio", "Mixcloud distribution needs --audio <file>");
10346
10395
  }
10347
10396
  const mixtapeId = mixtape.id;
10348
10397
  const logId = mixtape.logId;
10349
10398
  if (!logId) {
10350
- throw new CliError2("mixtape_not_promoted", `${mixtapeId} has no coordinate yet. Promote its recording first:
10399
+ throw new CliError("mixtape_not_promoted", `${mixtapeId} has no coordinate yet. Promote its recording first:
10351
10400
  ` + " fluncle admin recordings promote <recordingId>");
10352
10401
  }
10353
10402
  if (!mixtape.durationMs) {
@@ -10366,7 +10415,7 @@ async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {
10366
10415
  const results = [];
10367
10416
  if (doYoutube) {
10368
10417
  if (!options.video) {
10369
- throw new CliError2("missing_video", "YouTube distribution needs --video <mp4>");
10418
+ throw new CliError("missing_video", "YouTube distribution needs --video <mp4>");
10370
10419
  }
10371
10420
  onProgress("YouTube: uploading video…");
10372
10421
  await Promise.resolve().then(() => init_mixtape_youtube());
@@ -10376,7 +10425,7 @@ async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {
10376
10425
  }
10377
10426
  if (doMixcloud) {
10378
10427
  if (!options.audio) {
10379
- throw new CliError2("missing_audio", "Mixcloud distribution needs --audio <file>");
10428
+ throw new CliError("missing_audio", "Mixcloud distribution needs --audio <file>");
10380
10429
  }
10381
10430
  onProgress("Mixcloud: uploading audio…");
10382
10431
  await Promise.resolve().then(() => init_mixtape_mixcloud());
@@ -10389,10 +10438,10 @@ async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {
10389
10438
  async function mixtapeResyncCommand(idOrLogId, options, onProgress = () => {}) {
10390
10439
  const mixtape = await mixtapeGetCommand(idOrLogId);
10391
10440
  if (!mixtape.id) {
10392
- throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10441
+ throw new CliError("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
10393
10442
  }
10394
10443
  if (!mixtape.logId) {
10395
- throw new CliError2("mixtape_no_log_id", "The mixtape isn't published yet. Distribute it before re-syncing.");
10444
+ throw new CliError("mixtape_no_log_id", "The mixtape isn't published yet. Distribute it before re-syncing.");
10396
10445
  }
10397
10446
  const mixtapeId = mixtape.id;
10398
10447
  const explicit = Boolean(options.youtube) || Boolean(options.mixcloud);
@@ -10404,7 +10453,7 @@ async function mixtapeResyncCommand(idOrLogId, options, onProgress = () => {}) {
10404
10453
  doYoutube = platforms.has("youtube");
10405
10454
  doMixcloud = platforms.has("mixcloud");
10406
10455
  if (!doYoutube && !doMixcloud) {
10407
- throw new CliError2("mixtape_not_distributed", "The mixtape has no YouTube or Mixcloud link to re-sync.");
10456
+ throw new CliError("mixtape_not_distributed", "The mixtape has no YouTube or Mixcloud link to re-sync.");
10408
10457
  }
10409
10458
  }
10410
10459
  const results = [];
@@ -10449,7 +10498,7 @@ function buildBody(options) {
10449
10498
  if (options.durationMs !== undefined) {
10450
10499
  const parsed = parseDuration(options.durationMs);
10451
10500
  if (parsed === null) {
10452
- throw new CliError2("invalid_duration", "Duration must be mm:ss or h:mm:ss, or a millisecond count");
10501
+ throw new CliError("invalid_duration", "Duration must be mm:ss or h:mm:ss, or a millisecond count");
10453
10502
  }
10454
10503
  body.durationMs = parsed;
10455
10504
  }
@@ -10478,7 +10527,7 @@ var init_format = __esm(() => {
10478
10527
  import { createInterface } from "node:readline/promises";
10479
10528
  async function promptLine(label, nonInteractiveMessage) {
10480
10529
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
10481
- throw new CliError2("not_interactive", nonInteractiveMessage);
10530
+ throw new CliError("not_interactive", nonInteractiveMessage);
10482
10531
  }
10483
10532
  const rl = createInterface({
10484
10533
  input: process.stdin,
@@ -10494,7 +10543,7 @@ async function selectWithKeyboard(items, options) {
10494
10543
  const stdin = options.input ?? process.stdin;
10495
10544
  const stdout = options.output ?? process.stdout;
10496
10545
  if (!stdin.isTTY || !stdout.isTTY) {
10497
- throw new CliError2("not_interactive", options.nonInteractiveMessage);
10546
+ throw new CliError("not_interactive", options.nonInteractiveMessage);
10498
10547
  }
10499
10548
  let selectedIndex = 0;
10500
10549
  let renderedLines = 0;
@@ -10581,7 +10630,7 @@ async function openExternal(target) {
10581
10630
  const command = platformOpenCommand();
10582
10631
  if (!command) {
10583
10632
  console.log(target);
10584
- throw new CliError2("unsupported_platform", `Automatic opening is only supported on macOS and Linux. Open this manually: ${target}`);
10633
+ throw new CliError("unsupported_platform", `Automatic opening is only supported on macOS and Linux. Open this manually: ${target}`);
10585
10634
  }
10586
10635
  const child = Bun.spawn([command, target], {
10587
10636
  stderr: "pipe",
@@ -10590,7 +10639,7 @@ async function openExternal(target) {
10590
10639
  const exitCode = await child.exited;
10591
10640
  if (exitCode !== 0) {
10592
10641
  const stderr = await new Response(child.stderr).text();
10593
- throw new CliError2("open_failed", stderr.trim() || `Could not open ${target} with ${command}`);
10642
+ throw new CliError("open_failed", stderr.trim() || `Could not open ${target} with ${command}`);
10594
10643
  }
10595
10644
  }
10596
10645
  function platformOpenCommand() {
@@ -10665,7 +10714,7 @@ async function openRecentCommand(options) {
10665
10714
  function spotifyPlaylistUrlToAppUri(playlistUrl) {
10666
10715
  const playlistId = playlistUrl.match(/open\.spotify\.com\/playlist\/([^/?#]+)/)?.[1];
10667
10716
  if (!playlistId) {
10668
- throw new CliError2("invalid_playlist_url", `Could not parse Spotify playlist URL: ${playlistUrl}`);
10717
+ throw new CliError("invalid_playlist_url", `Could not parse Spotify playlist URL: ${playlistUrl}`);
10669
10718
  }
10670
10719
  return `spotify:playlist:${playlistId}`;
10671
10720
  }
@@ -10714,13 +10763,13 @@ var init_random = __esm(() => {
10714
10763
  async function subscribeCommand(email, json) {
10715
10764
  const address = email?.trim() || await promptLine("Email: ", PROMPT_NON_INTERACTIVE_MESSAGE);
10716
10765
  if (!address) {
10717
- throw new CliError2("invalid_email", "Enter a valid email address.");
10766
+ throw new CliError("invalid_email", "Enter a valid email address.");
10718
10767
  }
10719
10768
  await publicApiPost("/api/v1/newsletter", {
10720
10769
  email: address
10721
10770
  });
10722
10771
  if (json) {
10723
- printJson2({
10772
+ printJson({
10724
10773
  ok: true
10725
10774
  });
10726
10775
  return;
@@ -10738,11 +10787,11 @@ var init_subscribe = __esm(() => {
10738
10787
  async function submitCommand(input) {
10739
10788
  const query = input?.trim() || await promptLine("Track or Spotify link: ", PROMPT_NON_INTERACTIVE_MESSAGE2);
10740
10789
  if (!query) {
10741
- throw new CliError2("missing_query", "Missing search input");
10790
+ throw new CliError("missing_query", "Missing search input");
10742
10791
  }
10743
10792
  const response = await publicApiGet(`/api/v1/search?q=${encodeURIComponent(query)}`);
10744
10793
  if (response.results.length === 0) {
10745
- throw new CliError2("no_results", "Nothing came back for that search. Try the artist and the title.");
10794
+ throw new CliError("no_results", "Nothing came back for that search. Try the artist and the title.");
10746
10795
  }
10747
10796
  const selected = await selectCandidate(response.results);
10748
10797
  if (!selected) {
@@ -10844,7 +10893,7 @@ async function requestDeviceCode(baseUrl) {
10844
10893
  return;
10845
10894
  });
10846
10895
  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.`);
10896
+ throw new CliError("device_code_failed", `Couldn't start the login flow (${response.status}). Try again in a moment.`);
10848
10897
  }
10849
10898
  return data;
10850
10899
  }
@@ -10876,15 +10925,15 @@ async function pollForToken(baseUrl, device) {
10876
10925
  intervalMs += 5000;
10877
10926
  break;
10878
10927
  case "access_denied":
10879
- throw new CliError2("access_denied", "Sign-in was denied in the browser. Nothing linked.");
10928
+ throw new CliError("access_denied", "Sign-in was denied in the browser. Nothing linked.");
10880
10929
  case "expired_token":
10881
- throw new CliError2("expired_token", "The login request timed out. Run `fluncle login` again.");
10930
+ throw new CliError("expired_token", "The login request timed out. Run `fluncle login` again.");
10882
10931
  default:
10883
- throw new CliError2("device_token_failed", pending?.error_description ?? `Login failed (${response.status}).`);
10932
+ throw new CliError("device_token_failed", pending?.error_description ?? `Login failed (${response.status}).`);
10884
10933
  }
10885
10934
  await sleep3(intervalMs);
10886
10935
  }
10887
- throw new CliError2("expired_token", "The login request timed out. Run `fluncle login` again.");
10936
+ throw new CliError("expired_token", "The login request timed out. Run `fluncle login` again.");
10888
10937
  }
10889
10938
  async function fetchSessionUser(baseUrl, token) {
10890
10939
  const response = await fetch(`${baseUrl}/api/v1/me`, {
@@ -11020,7 +11069,7 @@ var init_about = __esm(() => {
11020
11069
  async function versionCommand(options) {
11021
11070
  const result = options.check ? await buildVersionCheck() : buildCurrentVersion();
11022
11071
  if (options.json) {
11023
- printJson2({
11072
+ printJson({
11024
11073
  ok: true,
11025
11074
  ...result
11026
11075
  });
@@ -11086,7 +11135,7 @@ function parseVersion2(version) {
11086
11135
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
11087
11136
  var init_version2 = __esm(() => {
11088
11137
  init_output();
11089
- currentVersion2 = "0.275.0".trim() ? "0.275.0".trim() : "0.1.0";
11138
+ currentVersion2 = "0.276.0".trim() ? "0.276.0".trim() : "0.1.0";
11090
11139
  });
11091
11140
 
11092
11141
  // ../../packages/registry/src/index.ts
@@ -12605,7 +12654,7 @@ var init_src = __esm(() => {
12605
12654
  ],
12606
12655
  kind: "cron",
12607
12656
  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.",
12657
+ 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
12658
  probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-publish-advance", kind: "cron" },
12610
12659
  statusDescription: "advances the publish queue on his own clock",
12611
12660
  title: "Publish advance",
@@ -12712,7 +12761,7 @@ var init_src = __esm(() => {
12712
12761
  ],
12713
12762
  kind: "cron",
12714
12763
  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/.",
12764
+ 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
12765
  operatorOnly: "Search-engine notification is plumbing, not a listener-facing automation.",
12717
12766
  probeConfig: {
12718
12767
  cadenceMs: 24 * 60 * MINUTE_MS,
@@ -12978,7 +13027,7 @@ function checkBundleCompleteness(files) {
12978
13027
  function validateBundleCompleteness(files, onProgress, options) {
12979
13028
  const completeness = checkBundleCompleteness(files);
12980
13029
  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).`);
13030
+ 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
13031
  }
12983
13032
  if (completeness.uploadingFootage) {
12984
13033
  if (completeness.missingContract.length > 0) {
@@ -12999,7 +13048,7 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
12999
13048
  path: files[spec.option]
13000
13049
  })).filter((spec) => Boolean(spec.path));
13001
13050
  if (present.length === 0) {
13002
- throw new CliError2("nothing_to_upload", "No bundle files resolved to upload (pass --dir <bundle> or explicit file flags).");
13051
+ throw new CliError("nothing_to_upload", "No bundle files resolved to upload (pass --dir <bundle> or explicit file flags).");
13003
13052
  }
13004
13053
  const platesOnly = isPlatesOnlyUpload(files);
13005
13054
  const presign = await adminApiPost(`/api/v1/admin/tracks/${encodeURIComponent(idOrLogId)}/video/uploads`, { fields: present.map((spec) => spec.field) });
@@ -13009,7 +13058,7 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
13009
13058
  for (const spec of present) {
13010
13059
  const upload = byField.get(spec.field);
13011
13060
  if (!upload) {
13012
- throw new CliError2("presign_missing", `Worker did not sign an upload for ${spec.field}`);
13061
+ throw new CliError("presign_missing", `Worker did not sign an upload for ${spec.field}`);
13013
13062
  }
13014
13063
  onProgress?.(`Uploading ${spec.field} → ${upload.key}`);
13015
13064
  const file = Bun.file(spec.path);
@@ -13021,7 +13070,7 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
13021
13070
  });
13022
13071
  if (!response.ok) {
13023
13072
  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}` : ""}`);
13073
+ throw new CliError("r2_put_failed", `R2 rejected ${spec.field} with ${response.status} ${response.statusText}${detail ? `: ${detail}` : ""}`);
13025
13074
  }
13026
13075
  urls[spec.field] = `${FOUND_BASE}/${upload.key}`;
13027
13076
  }
@@ -26162,6 +26211,10 @@ var init_admin_tracks = __esm(() => {
26162
26211
  scope: TrackWorkScopeSchema.default("all")
26163
26212
  })).output(object({
26164
26213
  capabilities: TrackWorkCapabilitiesSchema.optional(),
26214
+ catalogueCapture: object({
26215
+ closedReason: _enum(["paused", "bytes_spent", "tracks_spent"]),
26216
+ open: literal(false)
26217
+ }).optional(),
26165
26218
  debtPending: boolean3().optional(),
26166
26219
  ok: literal(true),
26167
26220
  oldestQueuedCaptureOver24h: boolean3().optional(),
@@ -28970,7 +29023,7 @@ async function resolveMixtapeId(idOrLogId) {
28970
29023
  const response = await adminApiGet("/api/v1/admin/mixtapes");
28971
29024
  const match = response.mixtapes.find((mixtape) => mixtape.id === idOrLogId || mixtape.logId === idOrLogId);
28972
29025
  if (!match?.id) {
28973
- throw new CliError2("mixtape_not_found", `No mixtape with id or Log ID ${idOrLogId}`);
29026
+ throw new CliError("mixtape_not_found", `No mixtape with id or Log ID ${idOrLogId}`);
28974
29027
  }
28975
29028
  return match.id;
28976
29029
  }
@@ -29085,7 +29138,7 @@ function renditionFfmpegArgs(inputPath, outputPath) {
29085
29138
  }
29086
29139
  async function uploadRenditionMultipart(masterPath, presignPath, onProgress = () => {}) {
29087
29140
  if (!existsSync(masterPath)) {
29088
- throw new CliError2("file_not_found", `Set-video master not found: ${masterPath}`);
29141
+ throw new CliError("file_not_found", `Set-video master not found: ${masterPath}`);
29089
29142
  }
29090
29143
  await assertFfmpeg();
29091
29144
  const renditionPath = join4(tmpdir(), `fluncle-set-${randomUUID()}.mp4`);
@@ -29104,7 +29157,7 @@ async function uploadRenditionMultipart(masterPath, presignPath, onProgress = ()
29104
29157
  for (const part of plan.parts) {
29105
29158
  const url = urlByPart.get(part.partNumber);
29106
29159
  if (!url) {
29107
- throw new CliError2("presign_missing", `Worker did not sign part ${part.partNumber} of ${plan.partCount}`);
29160
+ throw new CliError("presign_missing", `Worker did not sign part ${part.partNumber} of ${plan.partCount}`);
29108
29161
  }
29109
29162
  onProgress(`Set video: part ${part.partNumber}/${plan.partCount}`);
29110
29163
  const etag = await putPart(url, renditionPath, part, onProgress);
@@ -29126,11 +29179,11 @@ async function putPart(url, path, part, onProgress = () => {}) {
29126
29179
  try {
29127
29180
  return await putPartOnce(url, body, part);
29128
29181
  } catch (error) {
29129
- if (error instanceof CliError2) {
29182
+ if (error instanceof CliError || deadlineSignal().aborted) {
29130
29183
  throw error;
29131
29184
  }
29132
29185
  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)}`);
29186
+ throw new CliError("r2_part_failed", `Part ${part.partNumber} failed after ${MAX_PART_ATTEMPTS} attempts: ${error instanceof Error ? error.message : String(error)}`);
29134
29187
  }
29135
29188
  const backoffMs = 500 * 2 ** (attempt - 1);
29136
29189
  onProgress(`Set video: part ${part.partNumber} dropped, retry ${attempt}/${MAX_PART_ATTEMPTS - 1} in ${backoffMs}ms…`);
@@ -29139,17 +29192,17 @@ async function putPart(url, path, part, onProgress = () => {}) {
29139
29192
  }
29140
29193
  }
29141
29194
  async function putPartOnce(url, body, part) {
29142
- const response = await fetch(url, { body, method: "PUT" });
29195
+ const response = await fetch(url, { body, method: "PUT", signal: deadlineSignal() });
29143
29196
  if (!response.ok) {
29144
29197
  const detail = (await response.text().catch(() => "")).slice(0, 300);
29145
29198
  if (response.status < 500) {
29146
- throw new CliError2("r2_part_failed", `R2 rejected part ${part.partNumber} (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`);
29199
+ throw new CliError("r2_part_failed", `R2 rejected part ${part.partNumber} (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`);
29147
29200
  }
29148
29201
  throw new Error(`R2 ${response.status} ${response.statusText} on part ${part.partNumber}${detail ? `: ${detail}` : ""}`);
29149
29202
  }
29150
29203
  const etag = response.headers.get("etag");
29151
29204
  if (!etag) {
29152
- throw new CliError2("r2_no_etag", `R2 returned no ETag for part ${part.partNumber}`);
29205
+ throw new CliError("r2_no_etag", `R2 returned no ETag for part ${part.partNumber}`);
29153
29206
  }
29154
29207
  return etag;
29155
29208
  }
@@ -29157,15 +29210,16 @@ async function completeUpload(url, parts) {
29157
29210
  const response = await fetch(url, {
29158
29211
  body: buildCompleteXml(parts),
29159
29212
  headers: { "content-type": "application/xml" },
29160
- method: "POST"
29213
+ method: "POST",
29214
+ signal: deadlineSignal()
29161
29215
  });
29162
29216
  const text = await response.text().catch(() => "");
29163
29217
  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)}` : ""}`);
29218
+ throw new CliError("r2_complete_failed", `R2 CompleteMultipartUpload failed (${response.status} ${response.statusText})${text ? `: ${text.slice(0, 300)}` : ""}`);
29165
29219
  }
29166
29220
  }
29167
29221
  async function abortUpload(url) {
29168
- await fetch(url, { method: "DELETE" });
29222
+ await fetch(url, { method: "DELETE", signal: AbortSignal.timeout(UPLOAD_ABORT_TIMEOUT_MS) });
29169
29223
  }
29170
29224
  async function assertFfmpeg() {
29171
29225
  try {
@@ -29175,7 +29229,7 @@ async function assertFfmpeg() {
29175
29229
  throw new Error("ffmpeg -version exited non-zero");
29176
29230
  }
29177
29231
  } catch {
29178
- throw new CliError2("ffmpeg_missing", "--set-video needs ffmpeg to derive the 1080p rendition. Install it (brew install ffmpeg).");
29232
+ throw new CliError("ffmpeg_missing", "--set-video needs ffmpeg to derive the 1080p rendition. Install it (brew install ffmpeg).");
29179
29233
  }
29180
29234
  }
29181
29235
  async function deriveRendition(inputPath, outputPath) {
@@ -29186,13 +29240,14 @@ async function deriveRendition(inputPath, outputPath) {
29186
29240
  await proc.exited;
29187
29241
  if (proc.exitCode !== 0) {
29188
29242
  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}` : ""}`);
29243
+ throw new CliError("ffmpeg_failed", `ffmpeg failed to derive the set-video rendition${detail ? `: ${detail}` : ""}`);
29190
29244
  }
29191
29245
  }
29192
- var FOUND_BASE2 = "https://found.fluncle.com", SET_VIDEO_RENDITION, MAX_PART_ATTEMPTS = 5;
29246
+ var FOUND_BASE2 = "https://found.fluncle.com", SET_VIDEO_RENDITION, MAX_PART_ATTEMPTS = 5, UPLOAD_ABORT_TIMEOUT_MS = 5000;
29193
29247
  var init_mixtape_set_video = __esm(() => {
29194
29248
  init_multipart();
29195
29249
  init_api();
29250
+ init_deadline();
29196
29251
  init_output();
29197
29252
  SET_VIDEO_RENDITION = {
29198
29253
  audioBitrate: "192k",
@@ -29289,12 +29344,12 @@ async function clipDripPauseCommand(paused) {
29289
29344
  }
29290
29345
  async function resolveClipSource(clip) {
29291
29346
  if (!clip.recordingId) {
29292
- throw new CliError2("clip_unlinked", `Clip ${clip.id} is linked to no recording`);
29347
+ throw new CliError("clip_unlinked", `Clip ${clip.id} is linked to no recording`);
29293
29348
  }
29294
29349
  await Promise.resolve().then(() => init_recordings());
29295
29350
  const recording = await recordingGet(clip.recordingId);
29296
29351
  if (!recording.r2Key) {
29297
- throw new CliError2("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
29352
+ throw new CliError("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
29298
29353
  }
29299
29354
  return {
29300
29355
  setUrl: r2PublicUrl(FOUND_BASE3, recording.r2Key)
@@ -29304,7 +29359,7 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
29304
29359
  const clips = await clipsListCommand();
29305
29360
  const clip = clips.find((candidate) => candidate.id === clipId);
29306
29361
  if (!clip) {
29307
- throw new CliError2("clip_not_found", `No clip with id ${clipId}`);
29362
+ throw new CliError("clip_not_found", `No clip with id ${clipId}`);
29308
29363
  }
29309
29364
  const source = await resolveClipSource(clip);
29310
29365
  await assertFfmpeg2();
@@ -29320,7 +29375,7 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
29320
29375
  });
29321
29376
  const sizeBytes = statSync3(outputPath).size;
29322
29377
  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`);
29378
+ 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
29379
  }
29325
29380
  onProgress(`Clip ${clipId}: uploading ${(sizeBytes / 1e6).toFixed(1)} MB…`);
29326
29381
  const presign = await adminApiPost(`/api/v1/admin/clips/${encodeURIComponent(clipId)}/cut/presign`, { contentType: "video/mp4" });
@@ -29345,7 +29400,7 @@ async function putClip(url, contentType, path) {
29345
29400
  });
29346
29401
  if (!response.ok) {
29347
29402
  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}` : ""}`);
29403
+ throw new CliError("r2_put_failed", `R2 rejected the clip upload (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}`);
29349
29404
  }
29350
29405
  }
29351
29406
  async function assertFfmpeg2() {
@@ -29356,12 +29411,12 @@ async function assertFfmpeg2() {
29356
29411
  throw new Error("ffmpeg -version exited non-zero");
29357
29412
  }
29358
29413
  } catch {
29359
- throw new CliError2("ffmpeg_missing", "The clip cut needs ffmpeg. Install it on the box (apt-get install -y ffmpeg).");
29414
+ throw new CliError("ffmpeg_missing", "The clip cut needs ffmpeg. Install it on the box (apt-get install -y ffmpeg).");
29360
29415
  }
29361
29416
  }
29362
29417
  async function runClipCut(options) {
29363
29418
  if (!options.setUrl) {
29364
- throw new CliError2("missing_set_url", "The clip cut needs the set rendition URL");
29419
+ throw new CliError("missing_set_url", "The clip cut needs the set rendition URL");
29365
29420
  }
29366
29421
  const proc = Bun.spawn(["ffmpeg", ...clipCutFfmpegArgs(options)], {
29367
29422
  stderr: "pipe",
@@ -29370,7 +29425,7 @@ async function runClipCut(options) {
29370
29425
  await proc.exited;
29371
29426
  if (proc.exitCode !== 0) {
29372
29427
  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}` : ""}`);
29428
+ throw new CliError("ffmpeg_failed", `ffmpeg failed to cut the clip${detail ? `: ${detail}` : ""}`);
29374
29429
  }
29375
29430
  }
29376
29431
  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 +29459,7 @@ async function recordingsListCommand(options = {}) {
29404
29459
  const suffix = query.toString() ? `?${query.toString()}` : "";
29405
29460
  const response = await adminApiGet(`/api/v1/admin/recordings${suffix}`);
29406
29461
  if (options.json) {
29407
- printJson2({ ok: true, recordings: response.recordings });
29462
+ printJson({ ok: true, recordings: response.recordings });
29408
29463
  return;
29409
29464
  }
29410
29465
  if (response.recordings.length === 0) {
@@ -29416,11 +29471,11 @@ async function recordingsListCommand(options = {}) {
29416
29471
  }
29417
29472
  async function recordingGetCommand(id, options = {}) {
29418
29473
  if (!id) {
29419
- throw new CliError2("missing_id", "Missing recording id for: get");
29474
+ throw new CliError("missing_id", "Missing recording id for: get");
29420
29475
  }
29421
29476
  const recording = await recordingGet2(id);
29422
29477
  if (options.json) {
29423
- printJson2({ ok: true, recording });
29478
+ printJson({ ok: true, recording });
29424
29479
  return;
29425
29480
  }
29426
29481
  console.log(formatRecordingSummary(recording));
@@ -29438,7 +29493,7 @@ async function recordingCreateCommand(options = {}) {
29438
29493
  });
29439
29494
  const recording = created.recording;
29440
29495
  if (options.json) {
29441
- printJson2({ ok: true, recording });
29496
+ printJson({ ok: true, recording });
29442
29497
  return;
29443
29498
  }
29444
29499
  console.log(`Plan ${recording.id} created, handle "${recording.title}"`);
@@ -29446,13 +29501,13 @@ async function recordingCreateCommand(options = {}) {
29446
29501
  }
29447
29502
  const title = options.title?.trim();
29448
29503
  if (!title) {
29449
- throw new CliError2("missing_title", "A recording needs a --title (or --plan for a videoless plan)");
29504
+ throw new CliError("missing_title", "A recording needs a --title (or --plan for a videoless plan)");
29450
29505
  }
29451
29506
  if (!options.video) {
29452
- throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
29507
+ throw new CliError("missing_video", "A recording needs a --video <file> to stage");
29453
29508
  }
29454
29509
  if (!existsSync2(options.video)) {
29455
- throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
29510
+ throw new CliError("file_not_found", `Set-video master not found: ${options.video}`);
29456
29511
  }
29457
29512
  const created = await adminApiPost("/api/v1/admin/recordings", {
29458
29513
  recordedAt: options.recordedAt,
@@ -29467,14 +29522,14 @@ async function recordingCreateCommand(options = {}) {
29467
29522
  log(`Recording ${recording.id} created, staging the set video…`);
29468
29523
  const upload = await uploadRenditionMultipart(options.video, `/api/v1/admin/recordings/${encodeURIComponent(recording.id)}/set-video/presign`, log);
29469
29524
  if (options.json) {
29470
- printJson2({ key: upload.key, ok: true, recording, url: upload.url });
29525
+ printJson({ key: upload.key, ok: true, recording, url: upload.url });
29471
29526
  return;
29472
29527
  }
29473
29528
  console.log(`Recording ${recording.id} staged → ${upload.url}`);
29474
29529
  }
29475
29530
  async function recordingUpdateCommand(id, options = {}) {
29476
29531
  if (!id) {
29477
- throw new CliError2("missing_id", "Missing recording id for: update");
29532
+ throw new CliError("missing_id", "Missing recording id for: update");
29478
29533
  }
29479
29534
  const body = {};
29480
29535
  if (options.title !== undefined) {
@@ -29488,66 +29543,66 @@ async function recordingUpdateCommand(id, options = {}) {
29488
29543
  }
29489
29544
  if (options.tracklistFile !== undefined) {
29490
29545
  if (!existsSync2(options.tracklistFile)) {
29491
- throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
29546
+ throw new CliError("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
29492
29547
  }
29493
29548
  try {
29494
29549
  body.tracklistJson = JSON.parse(readFileSync2(options.tracklistFile, "utf8"));
29495
29550
  } catch {
29496
- throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
29551
+ throw new CliError("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
29497
29552
  }
29498
29553
  }
29499
29554
  if (Object.keys(body).length === 0) {
29500
- throw new CliError2("no_fields", "Nothing to update. Pass --title, --recorded-at, --parent-id, or --tracklist-file");
29555
+ throw new CliError("no_fields", "Nothing to update. Pass --title, --recorded-at, --parent-id, or --tracklist-file");
29501
29556
  }
29502
29557
  const response = await adminApiPatch(`/api/v1/admin/recordings/${encodeURIComponent(id)}`, body);
29503
29558
  if (options.json) {
29504
- printJson2({ ok: true, recording: response.recording });
29559
+ printJson({ ok: true, recording: response.recording });
29505
29560
  return;
29506
29561
  }
29507
29562
  console.log(`Updated ${formatRecordingSummary(response.recording)}`);
29508
29563
  }
29509
29564
  async function recordingDeleteCommand(id, options = {}) {
29510
29565
  if (!id) {
29511
- throw new CliError2("missing_id", "Missing recording id for: delete");
29566
+ throw new CliError("missing_id", "Missing recording id for: delete");
29512
29567
  }
29513
29568
  await adminApiDelete(`/api/v1/admin/recordings/${encodeURIComponent(id)}`);
29514
29569
  if (options.json) {
29515
- printJson2({ ok: true });
29570
+ printJson({ ok: true });
29516
29571
  return;
29517
29572
  }
29518
29573
  console.log(`Deleted recording ${id} (and its clips).`);
29519
29574
  }
29520
29575
  async function recordingPromoteCommand(id, options = {}) {
29521
29576
  if (!id) {
29522
- throw new CliError2("missing_id", "Missing recording id for: promote");
29577
+ throw new CliError("missing_id", "Missing recording id for: promote");
29523
29578
  }
29524
29579
  const response = await adminApiPost(`/api/v1/admin/recordings/${encodeURIComponent(id)}/promote`);
29525
29580
  const recording = response.recording;
29526
29581
  if (options.json) {
29527
- printJson2({ ok: true, recording });
29582
+ printJson({ ok: true, recording });
29528
29583
  return;
29529
29584
  }
29530
29585
  console.log(recording.logId ? `Promoted recording ${id} → mixtape fluncle://${recording.logId}` : `Promoted recording ${id}`);
29531
29586
  }
29532
29587
  async function recordingReplaceCuesCommand(id, options = {}) {
29533
29588
  if (!id) {
29534
- throw new CliError2("missing_id", "Missing recording id for: replace-cues");
29589
+ throw new CliError("missing_id", "Missing recording id for: replace-cues");
29535
29590
  }
29536
29591
  if (!options.cuesFile) {
29537
- throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
29592
+ throw new CliError("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
29538
29593
  }
29539
29594
  if (!existsSync2(options.cuesFile)) {
29540
- throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
29595
+ throw new CliError("file_not_found", `Cues file not found: ${options.cuesFile}`);
29541
29596
  }
29542
29597
  let cues;
29543
29598
  try {
29544
29599
  cues = JSON.parse(readFileSync2(options.cuesFile, "utf8"));
29545
29600
  } catch {
29546
- throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
29601
+ throw new CliError("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
29547
29602
  }
29548
29603
  const response = await adminApiPut(`/api/v1/admin/recordings/${encodeURIComponent(id)}/cues`, { cues });
29549
29604
  if (options.json) {
29550
- printJson2({ ok: true, recording: response.recording });
29605
+ printJson({ ok: true, recording: response.recording });
29551
29606
  return;
29552
29607
  }
29553
29608
  console.log(`Replaced cues for ${formatRecordingSummary(response.recording)}`);
@@ -29565,7 +29620,7 @@ function buildBody2(options, { requireContent }) {
29565
29620
  if (options.contentFile !== undefined) {
29566
29621
  body.contentJson = readContentFile(options.contentFile);
29567
29622
  } else if (requireContent) {
29568
- throw new CliError2("missing_content", "A draft needs the structured content payload via --content-file <edition.json>");
29623
+ throw new CliError("missing_content", "A draft needs the structured content payload via --content-file <edition.json>");
29569
29624
  }
29570
29625
  if (requireContent && typeof options.promptVersion === "number") {
29571
29626
  body.promptVersion = options.promptVersion;
@@ -29583,13 +29638,13 @@ function buildBody2(options, { requireContent }) {
29583
29638
  }
29584
29639
  function readContentFile(filePath) {
29585
29640
  if (!existsSync3(filePath)) {
29586
- throw new CliError2("file_not_found", `Edition payload file not found: ${filePath}`);
29641
+ throw new CliError("file_not_found", `Edition payload file not found: ${filePath}`);
29587
29642
  }
29588
29643
  const text = readFileSync3(filePath, "utf-8");
29589
29644
  try {
29590
29645
  return JSON.parse(text);
29591
29646
  } catch (error) {
29592
- throw new CliError2("invalid_content_json", `Edition payload JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
29647
+ throw new CliError("invalid_content_json", `Edition payload JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
29593
29648
  }
29594
29649
  }
29595
29650
  async function newsletterDraftCommand(options) {
@@ -29626,15 +29681,15 @@ function resolveBody(options) {
29626
29681
  }
29627
29682
  if (options.bodyFile !== undefined) {
29628
29683
  if (!existsSync4(options.bodyFile)) {
29629
- throw new CliError2("file_not_found", `Body file not found: ${options.bodyFile}`);
29684
+ throw new CliError("file_not_found", `Body file not found: ${options.bodyFile}`);
29630
29685
  }
29631
29686
  return readFileSync4(options.bodyFile, "utf-8");
29632
29687
  }
29633
- throw new CliError2("missing_body", "An entry needs a body via --body <text> or --body-file <entry.md>");
29688
+ throw new CliError("missing_body", "An entry needs a body via --body <text> or --body-file <entry.md>");
29634
29689
  }
29635
29690
  function requireTitle(options) {
29636
29691
  if (options.title === undefined || !options.title.trim()) {
29637
- throw new CliError2("missing_title", "An entry needs a title via --title <text>");
29692
+ throw new CliError("missing_title", "An entry needs a title via --title <text>");
29638
29693
  }
29639
29694
  return options.title;
29640
29695
  }
@@ -29669,20 +29724,20 @@ async function promptDetailCommand(slug) {
29669
29724
  const prompts = await promptsListCommand();
29670
29725
  const detail = prompts.find((prompt) => prompt.slug === slug);
29671
29726
  if (!detail) {
29672
- throw new CliError2("unknown_prompt", `No prompt goes by "${slug}". The registered ones: ${prompts.map((prompt) => prompt.slug).join(", ")}`);
29727
+ throw new CliError("unknown_prompt", `No prompt goes by "${slug}". The registered ones: ${prompts.map((prompt) => prompt.slug).join(", ")}`);
29673
29728
  }
29674
29729
  return detail;
29675
29730
  }
29676
29731
  function resolveBody2(options) {
29677
29732
  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`.");
29733
+ 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
29734
  }
29680
29735
  if (!existsSync5(options.bodyFile)) {
29681
- throw new CliError2("file_not_found", `Body file not found: ${options.bodyFile}`);
29736
+ throw new CliError("file_not_found", `Body file not found: ${options.bodyFile}`);
29682
29737
  }
29683
29738
  const body = readFileSync5(options.bodyFile, "utf-8");
29684
29739
  if (body.trim().length === 0) {
29685
- throw new CliError2("empty_body", "A prompt body cannot be empty. Nothing was appended.");
29740
+ throw new CliError("empty_body", "A prompt body cannot be empty. Nothing was appended.");
29686
29741
  }
29687
29742
  return body;
29688
29743
  }
@@ -29697,7 +29752,7 @@ async function promptRollbackCommand(slug, version) {
29697
29752
  const target = detail.versions.find((candidate) => candidate.version === version);
29698
29753
  if (!target) {
29699
29754
  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.`);
29755
+ 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
29756
  }
29702
29757
  return restore(detail, { body: target.body, from: version, note: `rolled back to v${version}` });
29703
29758
  }
@@ -29722,7 +29777,7 @@ function parseAgainst(value) {
29722
29777
  }
29723
29778
  const version = parseVersion3(value);
29724
29779
  if (version === undefined) {
29725
- throw new CliError2("invalid_against", `--against takes a version (3, or v3) or the word default. Got "${value}".`);
29780
+ throw new CliError("invalid_against", `--against takes a version (3, or v3) or the word default. Got "${value}".`);
29726
29781
  }
29727
29782
  return { kind: "version", version };
29728
29783
  }
@@ -29802,7 +29857,7 @@ function resolveAgainstVersion(detail, version) {
29802
29857
  const target = detail.versions.find((candidate) => candidate.version === version);
29803
29858
  if (!target) {
29804
29859
  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.`);
29860
+ 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
29861
  }
29807
29862
  return { body: target.body, label: `v${version}`, version };
29808
29863
  }
@@ -29853,7 +29908,7 @@ import { createInterface as createInterface2 } from "node:readline/promises";
29853
29908
  async function listSubmissionsCommand(options = {}) {
29854
29909
  const response = await adminApiGet("/api/v1/admin/submissions");
29855
29910
  if (options.json) {
29856
- printJson2({ ok: true, submissions: response.submissions });
29911
+ printJson({ ok: true, submissions: response.submissions });
29857
29912
  return;
29858
29913
  }
29859
29914
  if (response.submissions.length === 0) {
@@ -29867,7 +29922,7 @@ async function listSubmissionsCommand(options = {}) {
29867
29922
  async function reviewSubmissionCommand(submissionId, options = {}) {
29868
29923
  const submission = await fetchSubmission(submissionId);
29869
29924
  if (options.json) {
29870
- printJson2({ ok: true, submission });
29925
+ printJson({ ok: true, submission });
29871
29926
  return;
29872
29927
  }
29873
29928
  console.log(formatSubmissionDetail(submission));
@@ -29875,7 +29930,7 @@ async function reviewSubmissionCommand(submissionId, options = {}) {
29875
29930
  async function rejectSubmissionCommand(submissionId, options = {}) {
29876
29931
  const response = await adminApiPost(`/api/v1/admin/submissions/${encodeURIComponent(submissionId)}/reject`);
29877
29932
  if (options.json) {
29878
- printJson2({ ok: true, submission: response.submission });
29933
+ printJson({ ok: true, submission: response.submission });
29879
29934
  return;
29880
29935
  }
29881
29936
  console.log(`Rejected ${formatTrackLine(response.submission)}.`);
@@ -29900,7 +29955,7 @@ async function approveSubmissionCommand(submissionId, options = {}) {
29900
29955
  });
29901
29956
  const response = await adminApiPost(`/api/v1/admin/submissions/${encodeURIComponent(submission.id)}/approve`);
29902
29957
  if (options.json) {
29903
- printJson2({ ok: true, submission: response.submission });
29958
+ printJson({ ok: true, submission: response.submission });
29904
29959
  return;
29905
29960
  }
29906
29961
  console.log(`Approved ${formatTrackLine(submission)}.`);
@@ -29911,7 +29966,7 @@ async function triageSubmissionCommand(submissionId, verdict, options = {}) {
29911
29966
  verdict
29912
29967
  });
29913
29968
  if (options.json) {
29914
- printJson2({ ok: true, submission: response.submission });
29969
+ printJson({ ok: true, submission: response.submission });
29915
29970
  return;
29916
29971
  }
29917
29972
  console.log(`Triaged ${formatTrackLine(response.submission)}: ${response.submission.triageVerdict ?? ""}`);
@@ -29922,7 +29977,7 @@ async function fetchSubmission(submissionId) {
29922
29977
  }
29923
29978
  async function confirm(label) {
29924
29979
  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.");
29980
+ 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
29981
  }
29927
29982
  const rl = createInterface2({
29928
29983
  input: process.stdin,
@@ -30508,9 +30563,11 @@ function createEvidenceHttp(overrides = {}) {
30508
30563
  return {
30509
30564
  cacheDir: defaultCacheDir(),
30510
30565
  cacheTtlMs: 7 * 24 * 60 * 60 * 1000,
30566
+ deadline: null,
30511
30567
  fetch: (url, init) => globalThis.fetch(url, init),
30512
30568
  now: () => Date.now(),
30513
30569
  policies: SOURCE_POLICIES,
30570
+ progress: noteProgress,
30514
30571
  random: Math.random,
30515
30572
  refresh: false,
30516
30573
  sleep: (ms) => runPromise2(sleep2(ms)),
@@ -30584,16 +30641,26 @@ async function readNextSlot(slotFile) {
30584
30641
  const value = Number(raw);
30585
30642
  return Number.isFinite(value) ? value : 0;
30586
30643
  }
30644
+ function seconds2(ms) {
30645
+ return Math.ceil(ms / 1000);
30646
+ }
30587
30647
  async function reserveSlot(http, source) {
30588
30648
  const interval = http.policies[source].intervalMs;
30589
30649
  const slotAt = await withSlotLock(http, source, async (slotFile) => {
30590
30650
  const now = http.now();
30591
30651
  const slot = Math.max(now, await readNextSlot(slotFile));
30652
+ if (http.deadline !== null && slot >= http.deadline) {
30653
+ 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);
30654
+ }
30592
30655
  await writeFile2(slotFile, String(slot + interval));
30593
30656
  return slot;
30594
30657
  });
30658
+ if (slotAt instanceof EvidenceFetchError) {
30659
+ throw slotAt;
30660
+ }
30595
30661
  const wait = slotAt - http.now();
30596
30662
  if (wait > 0) {
30663
+ 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
30664
  await http.sleep(wait);
30598
30665
  }
30599
30666
  }
@@ -30634,8 +30701,12 @@ async function fetchEvidenceText(http, source, cacheKey, perform, accept = () =>
30634
30701
  let attempt = 0;
30635
30702
  const request = gen2(function* () {
30636
30703
  attempt += 1;
30637
- yield* promise2(() => reserveSlot(http, source));
30704
+ yield* tryPromise2({
30705
+ catch: (error) => error instanceof EvidenceFetchError ? error : new EvidencePerformError({ cause: error }),
30706
+ try: () => reserveSlot(http, source)
30707
+ });
30638
30708
  http.stats.requests += 1;
30709
+ http.progress(`${source} request ${http.stats.requests} in flight`);
30639
30710
  let returned = false;
30640
30711
  const response = yield* tryPromise2({
30641
30712
  catch: (error) => {
@@ -30705,7 +30776,7 @@ async function fetchEvidenceJson(http, source, url, headers = {}) {
30705
30776
  const { cached, text } = await fetchEvidenceText(http, source, `GET ${url}`, async (signal) => {
30706
30777
  const response = await http.fetch(url, {
30707
30778
  headers: { Accept: "application/json", "User-Agent": EVIDENCE_USER_AGENT, ...headers },
30708
- signal
30779
+ signal: AbortSignal.any([signal, deadlineSignal()])
30709
30780
  });
30710
30781
  return { headers: response.headers, status: response.status, text: await response.text() };
30711
30782
  }, isJson2);
@@ -30714,6 +30785,7 @@ async function fetchEvidenceJson(http, source, url, headers = {}) {
30714
30785
  var SOURCE_POLICIES, EVIDENCE_USER_AGENT = "FluncleLabelTriage/1.0 ( https://www.fluncle.com )", EvidenceFetchError, EvidenceRetryable, EvidencePerformError, LOCK_STALE_MS = 1e4;
30715
30786
  var init_evidence_http = __esm(() => {
30716
30787
  init_dist();
30788
+ init_deadline();
30717
30789
  SOURCE_POLICIES = {
30718
30790
  apple: { attempts: 3, intervalMs: 3000, maxBackoffMs: 30000, timeoutMs: 1e4 },
30719
30791
  beatport: { attempts: 2, intervalMs: 1000, maxBackoffMs: 30000, timeoutMs: 75000 },
@@ -31407,7 +31479,7 @@ async function labelEvidenceCommand(input, options) {
31407
31479
  Promise.resolve().then(() => (init_env(), exports_env)),
31408
31480
  Promise.resolve().then(() => (init_admin_labels3(), exports_admin_labels))
31409
31481
  ]);
31410
- const http = createEvidenceHttp({ refresh: options.refresh });
31482
+ const http = createEvidenceHttp({ deadline: deadlineAt(), refresh: options.refresh });
31411
31483
  const discogsToken = readOptionalEnv("DISCOGS_USER_TOKEN");
31412
31484
  if (discogsToken) {
31413
31485
  http.policies = { ...http.policies, discogs: { ...http.policies.discogs, intervalMs: 1100 } };
@@ -31458,6 +31530,7 @@ function labelEvidenceLines(evidence) {
31458
31530
  }
31459
31531
  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
31532
  var init_admin_label_evidence = __esm(() => {
31533
+ init_deadline();
31461
31534
  init_evidence_http();
31462
31535
  MBID_PATTERN3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
31463
31536
  EVIDENCE_SOURCES = ["musicbrainz", "discogs", "beatport", "apple"];
@@ -31500,7 +31573,7 @@ async function paginateWithKeyboard(options) {
31500
31573
  const stdin = options.input ?? process.stdin;
31501
31574
  const stdout = options.output ?? process.stdout;
31502
31575
  if (!stdin.isTTY || !stdout.isTTY) {
31503
- throw new CliError2("not_interactive", options.nonInteractiveMessage);
31576
+ throw new CliError("not_interactive", options.nonInteractiveMessage);
31504
31577
  }
31505
31578
  const first = await options.fetchPage(undefined);
31506
31579
  if (first.lines.length === 0) {
@@ -33728,8 +33801,96 @@ var fluncleAsciiLogo = `███████╗██╗ ██╗ ██
33728
33801
  ╚═╝ ╚══════╝ ╚═════╝ ╚═╝ ╚═══╝ ╚═════╝╚══════╝╚══════╝`;
33729
33802
  var fluncleTagline = "Drum & bass bangers from another dimension";
33730
33803
 
33804
+ // src/deadline.ts
33805
+ init_output();
33806
+ var DEFAULT_NON_INTERACTIVE_TIMEOUT_SECONDS = 240;
33807
+ var TIMEOUT_EXIT_CODE = 124;
33808
+ var TIMEOUT_ENV = "FLUNCLE_TIMEOUT";
33809
+ var CLEANUP_GRACE_MS = 1e4;
33810
+ var state = {
33811
+ at: null,
33812
+ cancel: null,
33813
+ controller: new AbortController,
33814
+ fire: null,
33815
+ fired: false,
33816
+ note: null
33817
+ };
33818
+ function parseTimeoutSeconds(value, source) {
33819
+ if (value === undefined || value.trim() === "") {
33820
+ return null;
33821
+ }
33822
+ const trimmed = value.trim();
33823
+ if (["0", "off", "none"].includes(trimmed.toLowerCase())) {
33824
+ return 0;
33825
+ }
33826
+ const seconds = Number(trimmed);
33827
+ if (!Number.isFinite(seconds) || seconds <= 0) {
33828
+ throw new CliError("invalid_timeout", `${source} must be a number of seconds (0 disables the deadline), not "${value}"`);
33829
+ }
33830
+ return seconds;
33831
+ }
33832
+ function resolveTimeoutSeconds(input) {
33833
+ const flag = parseTimeoutSeconds(input.flag, "--timeout");
33834
+ if (flag !== null) {
33835
+ return flag;
33836
+ }
33837
+ const env = parseTimeoutSeconds(input.env, TIMEOUT_ENV);
33838
+ if (env !== null) {
33839
+ return env;
33840
+ }
33841
+ return input.interactive ? 0 : DEFAULT_NON_INTERACTIVE_TIMEOUT_SECONDS;
33842
+ }
33843
+ function timeoutMessage(command, seconds, note) {
33844
+ const waiting = note ? `: ${note}` : "";
33845
+ return `fluncle ${command} stopped after ${seconds} s${waiting}. Set --timeout or ${TIMEOUT_ENV} in seconds (0 disables the deadline).`;
33846
+ }
33847
+ function defaultSchedule(fire, ms) {
33848
+ const timer = setTimeout(fire, ms);
33849
+ timer.unref();
33850
+ return () => clearTimeout(timer);
33851
+ }
33852
+ function armDeadline(seconds, options) {
33853
+ disarmDeadline();
33854
+ if (seconds <= 0) {
33855
+ state.at = null;
33856
+ return;
33857
+ }
33858
+ const now = options.now ?? Date.now;
33859
+ const exit = options.exit ?? ((code) => process.exit(code));
33860
+ const schedule = options.schedule ?? defaultSchedule;
33861
+ const report = options.report ?? ((message) => {
33862
+ if (options.json) {
33863
+ printJson({ code: "timeout", message, ok: false });
33864
+ return;
33865
+ }
33866
+ console.error(message);
33867
+ });
33868
+ state.at = now() + seconds * 1000;
33869
+ state.fire = () => {
33870
+ if (state.fired) {
33871
+ return;
33872
+ }
33873
+ const message = timeoutMessage(options.command, seconds, state.note);
33874
+ state.fired = true;
33875
+ disarmDeadline();
33876
+ report(message);
33877
+ process.exitCode = TIMEOUT_EXIT_CODE;
33878
+ state.controller.abort(new CliError("timeout", message));
33879
+ schedule(() => exit(TIMEOUT_EXIT_CODE), options.graceMs ?? CLEANUP_GRACE_MS);
33880
+ };
33881
+ state.cancel = schedule(state.fire, seconds * 1000);
33882
+ }
33883
+ function disarmDeadline() {
33884
+ state.cancel?.();
33885
+ state.cancel = null;
33886
+ }
33887
+ function deadlineFired() {
33888
+ return state.fired;
33889
+ }
33890
+
33731
33891
  // src/env.ts
33732
33892
  var import_dotenv = __toESM(require_main(), 1);
33893
+ init_deadline();
33733
33894
  var envProfiles = ["local", "production"];
33734
33895
  function setEnvProfile(profile) {
33735
33896
  if (!profile) {
@@ -33803,24 +33964,24 @@ async function maybePrintLiveCallout(args) {
33803
33964
  }
33804
33965
 
33805
33966
  // src/output.ts
33806
- import { writeSync } from "node:fs";
33807
- var EAGAIN_WAIT = new Int32Array(new SharedArrayBuffer(4));
33808
- function writeStdoutSync(text) {
33967
+ import { writeSync as writeSync2 } from "node:fs";
33968
+ var EAGAIN_WAIT2 = new Int32Array(new SharedArrayBuffer(4));
33969
+ function writeStdoutSync2(text) {
33809
33970
  const buf = Buffer.from(text, "utf8");
33810
33971
  let offset = 0;
33811
33972
  while (offset < buf.length) {
33812
33973
  try {
33813
- offset += writeSync(1, buf, offset);
33974
+ offset += writeSync2(1, buf, offset);
33814
33975
  } catch (error) {
33815
33976
  if (error.code === "EAGAIN") {
33816
- Atomics.wait(EAGAIN_WAIT, 0, 0, 1);
33977
+ Atomics.wait(EAGAIN_WAIT2, 0, 0, 1);
33817
33978
  continue;
33818
33979
  }
33819
33980
  throw error;
33820
33981
  }
33821
33982
  }
33822
33983
  }
33823
- class CliError extends Error {
33984
+ class CliError2 extends Error {
33824
33985
  code;
33825
33986
  constructor(code, message) {
33826
33987
  super(message);
@@ -33828,12 +33989,12 @@ class CliError extends Error {
33828
33989
  this.code = code;
33829
33990
  }
33830
33991
  }
33831
- function printJson(value) {
33832
- writeStdoutSync(JSON.stringify(value, null, 2) + `
33992
+ function printJson2(value) {
33993
+ writeStdoutSync2(JSON.stringify(value, null, 2) + `
33833
33994
  `);
33834
33995
  }
33835
33996
  function toJsonFailure(error) {
33836
- if (error instanceof CliError) {
33997
+ if (error instanceof CliError2) {
33837
33998
  return {
33838
33999
  code: error.code,
33839
34000
  message: error.message,
@@ -33915,9 +34076,17 @@ function artistRuleLines(rules) {
33915
34076
  }
33916
34077
  function createProgram() {
33917
34078
  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) => {
34079
+ 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
34080
  const options = actionCommand.optsWithGlobals();
33920
34081
  setEnvProfile(options.env);
34082
+ armDeadline(resolveTimeoutSeconds({
34083
+ env: process.env[TIMEOUT_ENV],
34084
+ flag: options.timeout,
34085
+ interactive: Boolean(process.stdout.isTTY)
34086
+ }), {
34087
+ command: commandPath(actionCommand),
34088
+ json: process.argv.includes("--json")
34089
+ });
33921
34090
  }).addHelpText("before", `
33922
34091
  ${fluncleAsciiLogo}
33923
34092
  `).addHelpText("after", rootHelpSections);
@@ -33944,17 +34113,31 @@ async function main(args = process.argv.slice(2)) {
33944
34113
  if (isHelpDisplayed(error)) {
33945
34114
  return;
33946
34115
  }
34116
+ if (deadlineFired()) {
34117
+ process.exit(TIMEOUT_EXIT_CODE);
34118
+ }
33947
34119
  const normalized = normalizeCommanderError(error);
33948
34120
  if (process.argv.includes("--json") || args.includes("--json")) {
33949
- printJson(toJsonFailure(normalized));
34121
+ printJson2(toJsonFailure(normalized));
33950
34122
  } else {
33951
34123
  console.error(formatError(normalized));
33952
34124
  }
33953
34125
  process.exit(1);
33954
34126
  }
34127
+ disarmDeadline();
34128
+ if (deadlineFired()) {
34129
+ process.exit(TIMEOUT_EXIT_CODE);
34130
+ }
33955
34131
  await Promise.resolve().then(() => init_update_notifier());
33956
34132
  await notifyIfUpdateAvailable(args);
33957
34133
  }
34134
+ function commandPath(command) {
34135
+ const names = [];
34136
+ for (let current = command;current?.parent; current = current.parent) {
34137
+ names.unshift(current.name());
34138
+ }
34139
+ return [...names, ...command.args.slice(0, 1)].join(" ");
34140
+ }
33958
34141
  function configureCommand(command) {
33959
34142
  return command.exitOverride().helpOption("-h, --help", "Display help for command").addHelpCommand(false).configureOutput({
33960
34143
  writeErr: () => {}
@@ -34109,7 +34292,7 @@ JSON field reference:
34109
34292
  const receipts = await Promise.resolve().then(() => (init_admin_operation_receipts2(), {}));
34110
34293
  const result = await getOperationReceiptCommand(operationKey);
34111
34294
  if (options.json) {
34112
- printJson(result);
34295
+ printJson2(result);
34113
34296
  return;
34114
34297
  }
34115
34298
  console.log(operationReceiptLines(result.receipt).join(`
@@ -34123,7 +34306,7 @@ JSON field reference:
34123
34306
  requestDigest
34124
34307
  });
34125
34308
  if (options.json) {
34126
- printJson(result);
34309
+ printJson2(result);
34127
34310
  return;
34128
34311
  }
34129
34312
  console.log(operationReceiptLines(result.receipt).join(`
@@ -34136,7 +34319,7 @@ JSON field reference:
34136
34319
  staleBefore: parseOperationReceiptFence(options.staleBefore)
34137
34320
  });
34138
34321
  if (options.json) {
34139
- printJson(result);
34322
+ printJson2(result);
34140
34323
  return;
34141
34324
  }
34142
34325
  const noun = result.repaired === 1 ? "receipt" : "receipts";
@@ -34150,7 +34333,7 @@ JSON field reference:
34150
34333
  const projections = await Promise.resolve().then(() => (init_admin_projections2(), {}));
34151
34334
  const result = await getProjectionStatusCommand();
34152
34335
  if (options.json) {
34153
- printJson(result);
34336
+ printJson2(result);
34154
34337
  return;
34155
34338
  }
34156
34339
  console.log(projectionStatusLines(result.status).join(`
@@ -34171,7 +34354,7 @@ JSON field reference:
34171
34354
  wallMs: options.wallMs === undefined ? undefined : parseProjectionWallMs(options.wallMs)
34172
34355
  });
34173
34356
  if (options.json) {
34174
- printJson(result);
34357
+ printJson2(result);
34175
34358
  return;
34176
34359
  }
34177
34360
  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 +34369,7 @@ JSON field reference:
34186
34369
  workKind: options.workKind
34187
34370
  });
34188
34371
  if (options.json) {
34189
- printJson(result);
34372
+ printJson2(result);
34190
34373
  return;
34191
34374
  }
34192
34375
  console.log(dueWorkRekeyLine(result));
@@ -34198,7 +34381,7 @@ JSON field reference:
34198
34381
  target: parseProjectionCutover(options.target)
34199
34382
  });
34200
34383
  if (options.json) {
34201
- printJson(result);
34384
+ printJson2(result);
34202
34385
  return;
34203
34386
  }
34204
34387
  console.log(`${result.target}: cutover ${result.enabled ? "open" : "dark"}.`);
@@ -34211,7 +34394,7 @@ JSON field reference:
34211
34394
  const vectors = await Promise.resolve().then(() => (init_admin_vectors2(), {}));
34212
34395
  const result = await getVectorServingCommand(parseVectorTarget(targetValue));
34213
34396
  if (options.json) {
34214
- printJson(result);
34397
+ printJson2(result);
34215
34398
  return;
34216
34399
  }
34217
34400
  console.log(vectorServingStatusLines(result.status).join(`
@@ -34224,7 +34407,7 @@ JSON field reference:
34224
34407
  target: parseVectorTarget(targetValue)
34225
34408
  });
34226
34409
  if (options.json) {
34227
- printJson(result);
34410
+ printJson2(result);
34228
34411
  return;
34229
34412
  }
34230
34413
  console.log(vectorServingStatusLines(result.status).join(`
@@ -34241,7 +34424,7 @@ JSON field reference:
34241
34424
  contracts: parseArtifactContracts(options.contract)
34242
34425
  });
34243
34426
  if (options.json) {
34244
- printJson(result);
34427
+ printJson2(result);
34245
34428
  return;
34246
34429
  }
34247
34430
  console.log(`${result.consumer.consumerId}: rebuilding at snapshot seq ${result.consumer.snapshotSeq ?? 0}.`);
@@ -34250,7 +34433,7 @@ JSON field reference:
34250
34433
  await Promise.resolve().then(() => init_admin_artifacts2());
34251
34434
  const result = await getArtifactConsumerCommand(consumerId);
34252
34435
  if (options.json) {
34253
- printJson(result);
34436
+ printJson2(result);
34254
34437
  return;
34255
34438
  }
34256
34439
  console.log(`${result.consumer.consumerId}: ${result.consumer.state}, head ${result.consumer.headSeq}, applied ${result.consumer.appliedThroughSeq ?? "none"}.`);
@@ -34266,7 +34449,7 @@ JSON field reference:
34266
34449
  streamVersion: parseArtifactInteger(options.streamVersion, "--stream-version")
34267
34450
  });
34268
34451
  if (options.json) {
34269
- printJson(result);
34452
+ printJson2(result);
34270
34453
  return;
34271
34454
  }
34272
34455
  console.log(`${result.stream}@${result.streamVersion}/${result.formatVersion}: ${result.itemCount} item(s), snapshot seq ${result.snapshotSeq}, page ${result.pageDigest}.`);
@@ -34286,7 +34469,7 @@ JSON field reference:
34286
34469
  streamVersion: parseArtifactInteger(options.streamVersion, "--stream-version")
34287
34470
  });
34288
34471
  if (options.json) {
34289
- printJson(result);
34472
+ printJson2(result);
34290
34473
  return;
34291
34474
  }
34292
34475
  console.log(`${result.checkpoint.stream}: ${result.checkpoint.state}, ${result.checkpoint.sourceItemCount} item(s) checkpointed.`);
@@ -34295,7 +34478,7 @@ JSON field reference:
34295
34478
  await Promise.resolve().then(() => init_admin_artifacts2());
34296
34479
  const result = await activateArtifactConsumerCommand(consumerId);
34297
34480
  if (options.json) {
34298
- printJson(result);
34481
+ printJson2(result);
34299
34482
  return;
34300
34483
  }
34301
34484
  console.log(`${result.consumer.consumerId}: active at seq ${result.consumer.appliedThroughSeq ?? 0}.`);
@@ -34309,7 +34492,7 @@ JSON field reference:
34309
34492
  })
34310
34493
  });
34311
34494
  if (options.json) {
34312
- printJson(result);
34495
+ printJson2(result);
34313
34496
  return;
34314
34497
  }
34315
34498
  console.log(`${result.events.length} event(s), seq ${result.fromSeq} through ${result.throughSeq}, head ${result.headSeq}, batch ${result.batchDigest}.`);
@@ -34328,7 +34511,7 @@ JSON field reference:
34328
34511
  })
34329
34512
  });
34330
34513
  if (options.json) {
34331
- printJson(result);
34514
+ printJson2(result);
34332
34515
  return;
34333
34516
  }
34334
34517
  console.log(`${result.consumer.consumerId}: applied through seq ${result.consumer.appliedThroughSeq ?? 0}.`);
@@ -34337,7 +34520,7 @@ JSON field reference:
34337
34520
  await Promise.resolve().then(() => init_admin_artifacts2());
34338
34521
  const result = await inactivateArtifactConsumerCommand(consumerId);
34339
34522
  if (options.json) {
34340
- printJson(result);
34523
+ printJson2(result);
34341
34524
  return;
34342
34525
  }
34343
34526
  console.log(`${result.consumer.consumerId}: inactive, bootstrap required before reuse.`);
@@ -34348,7 +34531,7 @@ JSON field reference:
34348
34531
  maximum: ARTIFACT_COMPACTION_API_MAX_LIMIT
34349
34532
  }));
34350
34533
  if (options.json) {
34351
- printJson(result);
34534
+ printJson2(result);
34352
34535
  return;
34353
34536
  }
34354
34537
  console.log(result.deletedCount === 0 ? `Compaction: ${result.reason}.` : `Compaction: deleted ${result.deletedCount} event(s), seq ${result.deletedFromSeq ?? 0} through ${result.deletedThroughSeq ?? 0}.`);
@@ -34402,15 +34585,15 @@ JSON field reference:
34402
34585
  });
34403
34586
  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
34587
  await Promise.resolve().then(() => init_admin_tracks2());
34405
- printJson(await prepareTrackCapturesCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34588
+ printJson2(await prepareTrackCapturesCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34406
34589
  });
34407
34590
  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
34591
  await Promise.resolve().then(() => init_admin_tracks2());
34409
- printJson(await commitTrackCapturesCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34592
+ printJson2(await commitTrackCapturesCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34410
34593
  });
34411
34594
  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
34595
  await Promise.resolve().then(() => init_admin_tracks2());
34413
- printJson(await updateTrackEmbeddingsCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34596
+ printJson2(await updateTrackEmbeddingsCommand(JSON.parse(readFileSync6(options.file, "utf8"))));
34414
34597
  });
34415
34598
  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
34599
  await Promise.resolve().then(() => init_admin_tracks2());
@@ -34546,7 +34729,7 @@ JSON field reference:
34546
34729
  await Promise.resolve().then(() => init_digests());
34547
34730
  const paused = await getFollowDigestStateCommand();
34548
34731
  if (options.json) {
34549
- printJson({ ok: true, paused });
34732
+ printJson2({ ok: true, paused });
34550
34733
  } else {
34551
34734
  console.log(paused ? "Follow digests are paused." : "Follow digests are running.");
34552
34735
  }
@@ -34559,7 +34742,7 @@ JSON field reference:
34559
34742
  await Promise.resolve().then(() => init_digests());
34560
34743
  const result = await setFollowDigestStateCommand(paused);
34561
34744
  if (options.json) {
34562
- printJson({ ok: true, paused: result });
34745
+ printJson2({ ok: true, paused: result });
34563
34746
  } else {
34564
34747
  console.log(result ? "Follow digests are paused." : "Follow digests are running.");
34565
34748
  }
@@ -34573,7 +34756,7 @@ JSON field reference:
34573
34756
  limit: options.limit === undefined ? undefined : Number(options.limit)
34574
34757
  });
34575
34758
  if (options.json) {
34576
- printJson(result);
34759
+ printJson2(result);
34577
34760
  } else if (result.windowClosed && !result.dryRun) {
34578
34761
  console.log("Follow digest sends open Friday at 17:00 Amsterdam.");
34579
34762
  } else {
@@ -34845,7 +35028,7 @@ JSON field reference:
34845
35028
  await Promise.resolve().then(() => init_admin_frontier2());
34846
35029
  const summary = await frontierRefreshCommand(options);
34847
35030
  if (options.json) {
34848
- printJson(summary);
35031
+ printJson2(summary);
34849
35032
  return;
34850
35033
  }
34851
35034
  const counts = `${summary.refreshed} refreshed, ${summary.minted} minted, ${summary.editionOnly} edition-only, ${summary.unchanged} unchanged, ${summary.failed} failed`;
@@ -34859,7 +35042,7 @@ JSON field reference:
34859
35042
  await Promise.resolve().then(() => init_admin_frontier2());
34860
35043
  const state = await frontierStatusCommand();
34861
35044
  if (options.json) {
34862
- printJson(state);
35045
+ printJson2(state);
34863
35046
  return;
34864
35047
  }
34865
35048
  console.log(state.open ? "Frontier minting is OPEN." : "Frontier minting is closed.");
@@ -34868,7 +35051,7 @@ JSON field reference:
34868
35051
  await Promise.resolve().then(() => init_admin_frontier2());
34869
35052
  const state = await frontierSetMintingCommand(true);
34870
35053
  if (options.json) {
34871
- printJson(state);
35054
+ printJson2(state);
34872
35055
  return;
34873
35056
  }
34874
35057
  console.log("Frontier minting is OPEN.");
@@ -34877,7 +35060,7 @@ JSON field reference:
34877
35060
  await Promise.resolve().then(() => init_admin_frontier2());
34878
35061
  const state = await frontierSetMintingCommand(false);
34879
35062
  if (options.json) {
34880
- printJson(state);
35063
+ printJson2(state);
34881
35064
  return;
34882
35065
  }
34883
35066
  console.log("Frontier minting is closed.");
@@ -34894,7 +35077,7 @@ JSON field reference:
34894
35077
  await Promise.resolve().then(() => init_admin_catalogue2());
34895
35078
  const summary = await catalogueDemandCommand();
34896
35079
  if (options.json) {
34897
- printJson({ ok: true, summary });
35080
+ printJson2({ ok: true, summary });
34898
35081
  return;
34899
35082
  }
34900
35083
  if (!summary.configured) {
@@ -34911,7 +35094,7 @@ JSON field reference:
34911
35094
  await Promise.resolve().then(() => init_admin_catalogue2());
34912
35095
  const { cleared } = await clearWrongAudioCommand(trackId);
34913
35096
  if (options.json) {
34914
- printJson({ cleared, ok: true });
35097
+ printJson2({ cleared, ok: true });
34915
35098
  return;
34916
35099
  }
34917
35100
  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 +35103,7 @@ JSON field reference:
34920
35103
  await Promise.resolve().then(() => init_admin_catalogue2());
34921
35104
  const { paidCleared, requeued } = await requeueAnchorCommand(trackIds, options.clearPaid);
34922
35105
  if (options.json) {
34923
- printJson({ ok: true, paidCleared, requeued });
35106
+ printJson2({ ok: true, paidCleared, requeued });
34924
35107
  return;
34925
35108
  }
34926
35109
  console.log(`Re-queued ${requeued} of ${trackIds.length} row(s) \u2014 the next anchor tick picks them up at their real priority.`);
@@ -34933,7 +35116,7 @@ JSON field reference:
34933
35116
  await Promise.resolve().then(() => init_admin_catalogue2());
34934
35117
  const state = await spotifyConsumerBudgetCommand();
34935
35118
  if (options.json || spotifyBudget.opts().json) {
34936
- printJson({
35119
+ printJson2({
34937
35120
  consumerBudgets: state.consumerBudgets,
34938
35121
  ok: true,
34939
35122
  spotifyDailyCalls: state.spotifyDailyCalls
@@ -34957,7 +35140,7 @@ JSON field reference:
34957
35140
  await Promise.resolve().then(() => init_admin_catalogue2());
34958
35141
  await setSpotifyConsumerBudgetCommand(options.consumer, calls);
34959
35142
  if (options.json || spotifyBudget.opts().json) {
34960
- printJson({ calls, consumer: options.consumer, ok: true });
35143
+ printJson2({ calls, consumer: options.consumer, ok: true });
34961
35144
  return;
34962
35145
  }
34963
35146
  console.log(`SPOTIFY BUDGET ${options.consumer} \u2014 ${calls} calls per day`);
@@ -34966,7 +35149,7 @@ JSON field reference:
34966
35149
  await Promise.resolve().then(() => init_admin_catalogue2());
34967
35150
  const state = await anchorBreakerCommand();
34968
35151
  if (options.json) {
34969
- printJson({ ...state, ok: true });
35152
+ printJson2({ ...state, ok: true });
34970
35153
  return;
34971
35154
  }
34972
35155
  const minutes = Math.ceil(state.cooldownRemainingMs / 60000);
@@ -34997,7 +35180,7 @@ JSON field reference:
34997
35180
  since: options.since
34998
35181
  });
34999
35182
  if (options.json) {
35000
- printJson({ ok: true, ...result });
35183
+ printJson2({ ok: true, ...result });
35001
35184
  return;
35002
35185
  }
35003
35186
  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 +35189,7 @@ JSON field reference:
35006
35189
  await Promise.resolve().then(() => init_admin_catalogue2());
35007
35190
  const { requeued, skippedVetoed } = await requeueUnmatchedCommand();
35008
35191
  if (options.json) {
35009
- printJson({ ok: true, requeued, skippedVetoed });
35192
+ printJson2({ ok: true, requeued, skippedVetoed });
35010
35193
  return;
35011
35194
  }
35012
35195
  console.log(`Re-queued ${requeued} unmatched capture(s) for the upgraded search ladder; ${skippedVetoed} stay terminal under the duration vetoes.`);
@@ -35015,7 +35198,7 @@ JSON field reference:
35015
35198
  await Promise.resolve().then(() => init_admin_catalogue2());
35016
35199
  const { flagged } = await flagWrongAudioCommand(trackId);
35017
35200
  if (options.json) {
35018
- printJson({ flagged, ok: true });
35201
+ printJson2({ flagged, ok: true });
35019
35202
  return;
35020
35203
  }
35021
35204
  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 +35207,7 @@ JSON field reference:
35024
35207
  await Promise.resolve().then(() => init_admin_catalogue2());
35025
35208
  const { forced } = await forceCaptureCommand(trackId);
35026
35209
  if (options.json) {
35027
- printJson({ forced, ok: true });
35210
+ printJson2({ forced, ok: true });
35028
35211
  return;
35029
35212
  }
35030
35213
  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 +35217,7 @@ JSON field reference:
35034
35217
  if (options.queue) {
35035
35218
  const { tracks } = await listUnverifiedCapturesCommand(options.limit ? { limit: options.limit } : {});
35036
35219
  if (options.json) {
35037
- printJson({ ok: true, tracks });
35220
+ printJson2({ ok: true, tracks });
35038
35221
  return;
35039
35222
  }
35040
35223
  if (tracks.length === 0) {
@@ -35055,7 +35238,7 @@ JSON field reference:
35055
35238
  }
35056
35239
  const { action } = await verifyCaptureCommand(trackId, verdict);
35057
35240
  if (options.json) {
35058
- printJson({ action, ok: true });
35241
+ printJson2({ action, ok: true });
35059
35242
  return;
35060
35243
  }
35061
35244
  console.log(`Recorded. ${trackId} \u2192 ${action}.`);
@@ -35064,7 +35247,7 @@ JSON field reference:
35064
35247
  await Promise.resolve().then(() => init_admin_catalogue2());
35065
35248
  const { logId } = await certifyTrackCommand(trackId, options.note);
35066
35249
  if (options.json) {
35067
- printJson({ logId, ok: true });
35250
+ printJson2({ logId, ok: true });
35068
35251
  return;
35069
35252
  }
35070
35253
  console.log(`Logged ${trackId} \u2014 ${logId}. Enrichment is running; finish it in the admin.`);
@@ -35073,7 +35256,7 @@ JSON field reference:
35073
35256
  await Promise.resolve().then(() => init_admin_catalogue2());
35074
35257
  const { changed } = await setTrackDismissedCommand(trackId, true);
35075
35258
  if (options.json) {
35076
- printJson({ changed, ok: true });
35259
+ printJson2({ changed, ok: true });
35077
35260
  return;
35078
35261
  }
35079
35262
  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 +35265,7 @@ JSON field reference:
35082
35265
  await Promise.resolve().then(() => init_admin_catalogue2());
35083
35266
  const { changed } = await setTrackDismissedCommand(trackId, false);
35084
35267
  if (options.json) {
35085
- printJson({ changed, ok: true });
35268
+ printJson2({ changed, ok: true });
35086
35269
  return;
35087
35270
  }
35088
35271
  console.log(changed ? `Restored ${trackId}. It re-enters the ranking on the next sweep.` : `${trackId} was not dismissed \u2014 nothing to restore.`);
@@ -35095,7 +35278,7 @@ JSON field reference:
35095
35278
  }
35096
35279
  const body = JSON.parse(readFileSync6(options.phaseFile, "utf8"));
35097
35280
  const result = await crawlCataloguePhaseCommand(body);
35098
- printJson(result);
35281
+ printJson2(result);
35099
35282
  return;
35100
35283
  }
35101
35284
  await runCrawlCatalogue(options, crawlCatalogueCommand);
@@ -35103,7 +35286,7 @@ JSON field reference:
35103
35286
  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
35287
  await Promise.resolve().then(() => init_admin_catalogue2());
35105
35288
  const body = JSON.parse(readFileSync6(options.file, "utf8"));
35106
- printJson(await commitCrawlNodesCommand(body));
35289
+ printJson2(await commitCrawlNodesCommand(body));
35107
35290
  });
35108
35291
  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
35292
  await Promise.resolve().then(() => init_admin_catalogue2());
@@ -35117,7 +35300,7 @@ JSON field reference:
35117
35300
  await Promise.resolve().then(() => init_admin_reach2());
35118
35301
  const { collected, failed, inserted, skipped } = await reachCollectCommand();
35119
35302
  if (options.json) {
35120
- printJson({ collected, failed, inserted, ok: true, skipped });
35303
+ printJson2({ collected, failed, inserted, ok: true, skipped });
35121
35304
  return;
35122
35305
  }
35123
35306
  const landed = collected.map((entry) => `${entry.platform} (${entry.metrics.join(", ")})`).join("; ");
@@ -35219,7 +35402,7 @@ JSON field reference:
35219
35402
  await Promise.resolve().then(() => init_admin_artists2());
35220
35403
  const rule = await addArtistRuleCommand(artistRuleInput(artistMbid, options.verdict, options.name));
35221
35404
  if (options.json) {
35222
- printJson({ ok: true, rule });
35405
+ printJson2({ ok: true, rule });
35223
35406
  return;
35224
35407
  }
35225
35408
  console.log(artistRuleWriteLine(`Rule set for ${rule.resolvedName ?? rule.artistName}: ${rule.verdict.toUpperCase()}`, rule.verdict === "unlisted" ? UNLISTED_RULE_BOUNDARY : ARTIST_RULE_BOUNDARY));
@@ -35228,7 +35411,7 @@ JSON field reference:
35228
35411
  await Promise.resolve().then(() => init_admin_artists2());
35229
35412
  const rules = await listArtistRulesCommand();
35230
35413
  if (options.json) {
35231
- printJson({ ok: true, rules });
35414
+ printJson2({ ok: true, rules });
35232
35415
  return;
35233
35416
  }
35234
35417
  console.log(artistRuleLines(rules).join(`
@@ -35238,7 +35421,7 @@ JSON field reference:
35238
35421
  await Promise.resolve().then(() => init_admin_artists2());
35239
35422
  const result = await removeArtistRuleCommand(id);
35240
35423
  if (options.json) {
35241
- printJson(result);
35424
+ printJson2(result);
35242
35425
  return;
35243
35426
  }
35244
35427
  console.log(artistRuleWriteLine(`Rule removed: ${id}`));
@@ -35274,7 +35457,7 @@ JSON field reference:
35274
35457
  }
35275
35458
  const result = options.replace ? await replaceLabelArtistRulesCommand(slug, parseLabelArtistRulesJson(readFileSync6(options.rulesFile ?? "", "utf8"))) : await listLabelArtistRulesCommand(slug);
35276
35459
  if (options.json) {
35277
- printJson({ ok: true, rules: result.rules });
35460
+ printJson2({ ok: true, rules: result.rules });
35278
35461
  return;
35279
35462
  }
35280
35463
  if (options.replace) {
@@ -35296,7 +35479,7 @@ JSON field reference:
35296
35479
  await Promise.resolve().then(() => init_admin_labels2());
35297
35480
  const label = await updateLabelCommand(slug, seedState, options.rewalk);
35298
35481
  if (options.json) {
35299
- printJson({ label, ok: true });
35482
+ printJson2({ label, ok: true });
35300
35483
  return;
35301
35484
  }
35302
35485
  for (const line of labelUpdateLines(label, options)) {
@@ -35333,7 +35516,7 @@ JSON field reference:
35333
35516
  process.exitCode = 1;
35334
35517
  }
35335
35518
  if (options.json) {
35336
- printJson(evidence);
35519
+ printJson2(evidence);
35337
35520
  return;
35338
35521
  }
35339
35522
  console.log(labelEvidenceLines(evidence).join(`
@@ -35362,7 +35545,7 @@ JSON field reference:
35362
35545
  await Promise.resolve().then(() => init_admin_labels2());
35363
35546
  const { label, outcome, takenOver } = await mintLabelCommand(mbid, seedState, takeOver);
35364
35547
  if (options.json) {
35365
- printJson({ label, ok: true, outcome, ...takenOver ? { takenOver } : {} });
35548
+ printJson2({ label, ok: true, outcome, ...takenOver ? { takenOver } : {} });
35366
35549
  return;
35367
35550
  }
35368
35551
  console.log(labelMintLine(label, outcome, mbid, takenOver));
@@ -35371,7 +35554,7 @@ JSON field reference:
35371
35554
  await Promise.resolve().then(() => init_admin_labels2());
35372
35555
  const result = await mergeLabelCommand(losingSlug, canonicalSlug);
35373
35556
  if (options.json) {
35374
- printJson({ ok: true, result });
35557
+ printJson2({ ok: true, result });
35375
35558
  return;
35376
35559
  }
35377
35560
  const {
@@ -35426,7 +35609,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
35426
35609
  source: options.source
35427
35610
  });
35428
35611
  if (options.json) {
35429
- printJson(result);
35612
+ printJson2(result);
35430
35613
  return;
35431
35614
  }
35432
35615
  console.log(`Archived preview for ${result.logId}`);
@@ -35457,7 +35640,7 @@ async function runTrackObserve(idOrLogId, options, trackObserveCommand) {
35457
35640
  voiceId: options.voiceId
35458
35641
  });
35459
35642
  if (options.json) {
35460
- printJson(result);
35643
+ printJson2(result);
35461
35644
  return;
35462
35645
  }
35463
35646
  console.log(`Recorded observation for ${result.logId}`);
@@ -35474,7 +35657,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand) {
35474
35657
  refresh: options.refresh
35475
35658
  });
35476
35659
  if (options.json) {
35477
- printJson(result);
35660
+ printJson2(result);
35478
35661
  return;
35479
35662
  }
35480
35663
  if (result.skipped) {
@@ -35509,7 +35692,7 @@ async function runTrackNote(idOrLogId, options, trackNoteCommand) {
35509
35692
  promptVersion: parsePromptVersion(options.promptVersion)
35510
35693
  });
35511
35694
  if (options.json) {
35512
- printJson(result);
35695
+ printJson2(result);
35513
35696
  return;
35514
35697
  }
35515
35698
  if (result.skipped) {
@@ -35540,7 +35723,7 @@ async function runEntityDescribe(kind, slug, options, describeCommand) {
35540
35723
  promptVersion: parsePromptVersion(options.promptVersion)
35541
35724
  });
35542
35725
  if (options.json) {
35543
- printJson(result);
35726
+ printJson2(result);
35544
35727
  return;
35545
35728
  }
35546
35729
  if (result.skipped) {
@@ -35565,7 +35748,7 @@ async function runEntityBioQueue(kind, options, queueCommand) {
35565
35748
  const limit = options.limit ? Number.parseInt(options.limit, 10) : 50;
35566
35749
  const items = await queueCommand(Number.isFinite(limit) ? limit : 50);
35567
35750
  if (options.json) {
35568
- printJson(items);
35751
+ printJson2(items);
35569
35752
  return;
35570
35753
  }
35571
35754
  if (items.length === 0) {
@@ -35581,7 +35764,7 @@ async function runEntityBioQueue(kind, options, queueCommand) {
35581
35764
  async function runEntityBioDraft(kind, slug, options, draftCommand) {
35582
35765
  const draft = await draftCommand(slug);
35583
35766
  if (options.json) {
35584
- printJson(draft);
35767
+ printJson2(draft);
35585
35768
  return;
35586
35769
  }
35587
35770
  if (!draft.found) {
@@ -35597,7 +35780,7 @@ async function runTrackSimilar(idOrLogId, options, trackSimilarCommand) {
35597
35780
  const limit = options.limit ? Number.parseInt(options.limit, 10) : undefined;
35598
35781
  const result = await trackSimilarCommand(idOrLogId, limit !== undefined && Number.isFinite(limit) ? limit : undefined);
35599
35782
  if (options.json) {
35600
- printJson(result);
35783
+ printJson2(result);
35601
35784
  return;
35602
35785
  }
35603
35786
  if (result.findings.length === 0) {
@@ -35612,7 +35795,7 @@ async function runTrackSimilar(idOrLogId, options, trackSimilarCommand) {
35612
35795
  }
35613
35796
  }
35614
35797
  function printSweepJson(payload, failedCount) {
35615
- printJson({ ...payload, failedCount, ok: failedCount === 0 });
35798
+ printJson2({ ...payload, failedCount, ok: failedCount === 0 });
35616
35799
  if (failedCount > 0) {
35617
35800
  process.exitCode = 1;
35618
35801
  }
@@ -35627,7 +35810,7 @@ async function runMigratePreviewArchive(options, migratePreviewArchiveCommand) {
35627
35810
  if (mode === "verify") {
35628
35811
  const result = await migratePreviewArchiveCommand({ dryRun: true, limit, mode });
35629
35812
  if (options.json) {
35630
- printJson({ mode, ok: true, remaining: result.remaining, sampleKeys: result.sampleKeys });
35813
+ printJson2({ mode, ok: true, remaining: result.remaining, sampleKeys: result.sampleKeys });
35631
35814
  return;
35632
35815
  }
35633
35816
  console.log(`${result.remaining} object(s) remain under the public analysis/previews/ prefix.`);
@@ -35780,7 +35963,7 @@ async function runCrawlCatalogue(options, crawlCatalogueCommand) {
35780
35963
  const maxHop = Number.parseInt(options.maxHop ?? "2", 10);
35781
35964
  const result = await crawlCatalogueCommand(limit, Number.isInteger(maxHop) && maxHop >= 0 ? maxHop : 2, options.dryRun);
35782
35965
  if (options.json) {
35783
- printJson(result);
35966
+ printJson2(result);
35784
35967
  } else if (result.dryRun) {
35785
35968
  console.log(`Dry run: would seed ${result.seeded} enabled label(s); ${result.frontierPending} node(s) pending. Nothing written.`);
35786
35969
  } else {
@@ -35803,7 +35986,7 @@ async function runCrawlCatalogue(options, crawlCatalogueCommand) {
35803
35986
  async function runCrawlStatus(options, crawlStatusCommand) {
35804
35987
  const result = await crawlStatusCommand();
35805
35988
  if (options.json) {
35806
- printJson(result);
35989
+ printJson2(result);
35807
35990
  return;
35808
35991
  }
35809
35992
  const { frontier, frontierByKind } = result;
@@ -35845,7 +36028,7 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand) {
35845
36028
  cursor = result.nextCursor;
35846
36029
  }
35847
36030
  if (options.json) {
35848
- printJson({
36031
+ printJson2({
35849
36032
  dryRun,
35850
36033
  ok: true,
35851
36034
  rateLimited: throttled,
@@ -36332,7 +36515,7 @@ async function runBackfillCoverMasters(options, backfillCoverMastersCommand) {
36332
36515
  cursor = result.nextCursor;
36333
36516
  }
36334
36517
  if (options.json) {
36335
- printJson({
36518
+ printJson2({
36336
36519
  dryRun,
36337
36520
  failed,
36338
36521
  failedCount: failed.length,
@@ -36395,7 +36578,7 @@ async function runBackfillLabelLineage(options, backfillLabelLineageCommand) {
36395
36578
  cursor = result.nextCursor;
36396
36579
  }
36397
36580
  if (options.json) {
36398
- printJson({
36581
+ printJson2({
36399
36582
  dryRun,
36400
36583
  failed,
36401
36584
  failedCount: failed.length,
@@ -36512,7 +36695,7 @@ async function runBackfillRecordingMbids(options, backfillRecordingMbidsCommand)
36512
36695
  cursor = result.nextCursor;
36513
36696
  }
36514
36697
  if (options.json) {
36515
- printJson({
36698
+ printJson2({
36516
36699
  dryRun,
36517
36700
  failed,
36518
36701
  failedCount: failed.length,
@@ -36575,7 +36758,7 @@ async function runBackfillArtistEdges(options, backfillArtistEdgesCommand) {
36575
36758
  cursor = result.nextCursor;
36576
36759
  }
36577
36760
  if (options.json) {
36578
- printJson({
36761
+ printJson2({
36579
36762
  dryRun,
36580
36763
  edgesWritten,
36581
36764
  fullyMatched,
@@ -36629,7 +36812,7 @@ async function runBackfillArtistCredits(options, backfillArtistCreditsCommand) {
36629
36812
  cursor = result.nextCursor;
36630
36813
  }
36631
36814
  if (options.json) {
36632
- printJson({
36815
+ printJson2({
36633
36816
  adoptedArtists,
36634
36817
  dryRun,
36635
36818
  edgesWritten,
@@ -36649,7 +36832,7 @@ async function runArtistResolveQueue(options, listArtistsCommand) {
36649
36832
  const limit = parseListLimit(options.limit);
36650
36833
  const result = await listArtistsCommand(limit);
36651
36834
  if (options.json) {
36652
- printJson({ artists: result.artists, ok: true });
36835
+ printJson2({ artists: result.artists, ok: true });
36653
36836
  return;
36654
36837
  }
36655
36838
  if (result.artists.length === 0) {
@@ -36668,7 +36851,7 @@ async function runArtistResolve(artistId, options, resolveArtistCommand) {
36668
36851
  }
36669
36852
  const result = await resolveArtistCommand(artistId);
36670
36853
  if (options.json) {
36671
- printJson(result);
36854
+ printJson2(result);
36672
36855
  return;
36673
36856
  }
36674
36857
  if (result.rateLimited) {
@@ -36732,7 +36915,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand) {
36732
36915
  allowPartial: options.allowPartial
36733
36916
  });
36734
36917
  if (options.json) {
36735
- printJson(result);
36918
+ printJson2(result);
36736
36919
  return;
36737
36920
  }
36738
36921
  console.log(`Linked video to ${result.logId}`);
@@ -36747,7 +36930,7 @@ async function runTrackDraft(idOrLogId, options, trackDraftCommand) {
36747
36930
  const platform = options.platform ?? "tiktok";
36748
36931
  const result = await trackDraftCommand(idOrLogId, platform);
36749
36932
  if (options.json) {
36750
- printJson(result);
36933
+ printJson2(result);
36751
36934
  return;
36752
36935
  }
36753
36936
  console.log(`Pushed ${platform} draft for ${result.trackId} (post ${result.externalId})`);
@@ -36759,7 +36942,7 @@ async function runTrackSocial(idOrLogId, options, trackSocialShowCommand, trackS
36759
36942
  if (!options.status) {
36760
36943
  const result = await trackSocialShowCommand(idOrLogId);
36761
36944
  if (options.json) {
36762
- printJson(result);
36945
+ printJson2(result);
36763
36946
  return;
36764
36947
  }
36765
36948
  if (result.posts.length === 0) {
@@ -36784,7 +36967,7 @@ async function runTrackSocial(idOrLogId, options, trackSocialShowCommand, trackS
36784
36967
  url: options.url
36785
36968
  });
36786
36969
  if (options.json) {
36787
- printJson(result);
36970
+ printJson2(result);
36788
36971
  return;
36789
36972
  }
36790
36973
  console.log(`${platform} \u2192 ${options.status} for ${result.trackId}`);
@@ -36796,7 +36979,7 @@ async function runTrackSocialCapture(options, trackSocialCaptureCommand) {
36796
36979
  }
36797
36980
  const result = await trackSocialCaptureCommand(limit);
36798
36981
  if (options.json) {
36799
- printJson(result);
36982
+ printJson2(result);
36800
36983
  return;
36801
36984
  }
36802
36985
  if (result.captured.length === 0) {
@@ -36814,7 +36997,7 @@ async function runTrackGet(idOrLogId, options, trackGetCommand) {
36814
36997
  }
36815
36998
  const result = await trackGetCommand(idOrLogId);
36816
36999
  if (options.json) {
36817
- printJson(result);
37000
+ printJson2(result);
36818
37001
  return;
36819
37002
  }
36820
37003
  if ("mixtape" in result) {
@@ -36840,7 +37023,7 @@ async function runMixableOrder(logIds, options, mixableOrderCommand) {
36840
37023
  }
36841
37024
  const result = await mixableOrderCommand(logIds, options.seed);
36842
37025
  if (options.json) {
36843
- printJson(result);
37026
+ printJson2(result);
36844
37027
  return;
36845
37028
  }
36846
37029
  console.log(`Proposed mix \u2014 a smooth chain (${result.algorithm}), not an energy-shaped set. Copy into Rekordbox as advisory.
@@ -36858,7 +37041,7 @@ async function runTrackGetAdmin(idOrLogId, options, trackGetAdminCommand) {
36858
37041
  }
36859
37042
  const result = await trackGetAdminCommand(idOrLogId);
36860
37043
  if (options.json) {
36861
- printJson(result);
37044
+ printJson2(result);
36862
37045
  return;
36863
37046
  }
36864
37047
  const t = result.track;
@@ -36927,7 +37110,7 @@ async function runTrackUpdate(trackId, options, trackUpdateCommand) {
36927
37110
  videoUrl: options.videoUrl
36928
37111
  });
36929
37112
  if (options.json) {
36930
- printJson(result);
37113
+ printJson2(result);
36931
37114
  return;
36932
37115
  }
36933
37116
  console.log(`Updated ${result.trackId}: ${result.fields.join(", ")}`);
@@ -36938,7 +37121,7 @@ async function runTrackRequeueVideo(idOrLogId, options, trackRequeueVideoCommand
36938
37121
  }
36939
37122
  const result = await trackRequeueVideoCommand(idOrLogId);
36940
37123
  if (options.json) {
36941
- printJson(result);
37124
+ printJson2(result);
36942
37125
  return;
36943
37126
  }
36944
37127
  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 +37145,7 @@ async function runTrackPinSource(idOrLogId, options, commands) {
36962
37145
  allowDurationMismatch: options.allowDurationMismatch
36963
37146
  });
36964
37147
  if (options.json) {
36965
- printJson(result);
37148
+ printJson2(result);
36966
37149
  return;
36967
37150
  }
36968
37151
  const who = result.logId ?? result.trackId;
@@ -36975,7 +37158,7 @@ async function runTrackPurgeVideo(idOrLogId, options, trackPurgeVideoCommand) {
36975
37158
  }
36976
37159
  const result = await trackPurgeVideoCommand(idOrLogId);
36977
37160
  if (options.json) {
36978
- printJson(result);
37161
+ printJson2(result);
36979
37162
  return;
36980
37163
  }
36981
37164
  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 +37169,7 @@ async function runMixtapeUpdate(id, options, mixtapeUpdateCommand) {
36986
37169
  }
36987
37170
  const result = await mixtapeUpdateCommand(id, options);
36988
37171
  if (options.json) {
36989
- printJson(result);
37172
+ printJson2(result);
36990
37173
  return;
36991
37174
  }
36992
37175
  console.log(`Saved ${result.mixtape.id}.`);
@@ -36998,7 +37181,7 @@ async function runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand
36998
37181
  const onProgress = options.json ? () => {} : (message) => console.log(message);
36999
37182
  const result = await mixtapeDistributeCommand(idOrLogId, options, onProgress);
37000
37183
  if (options.json) {
37001
- printJson(result);
37184
+ printJson2(result);
37002
37185
  return;
37003
37186
  }
37004
37187
  const links = result.results.map((r) => ` ${r.platform}: ${r.url}`).join(`
@@ -37012,7 +37195,7 @@ async function runMixtapePublishYoutube(idOrLogId, options, publishYoutubeComman
37012
37195
  }
37013
37196
  const result = await publishYoutubeCommand(idOrLogId);
37014
37197
  if (options.json) {
37015
- printJson(result);
37198
+ printJson2(result);
37016
37199
  return;
37017
37200
  }
37018
37201
  console.log(`YouTube video is now public: ${result.url}`);
@@ -37024,7 +37207,7 @@ async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand) {
37024
37207
  const onProgress = options.json ? () => {} : (message) => console.log(message);
37025
37208
  const result = await mixtapeResyncCommand(idOrLogId, options, onProgress);
37026
37209
  if (options.json) {
37027
- printJson(result);
37210
+ printJson2(result);
37028
37211
  return;
37029
37212
  }
37030
37213
  const links = result.results.map((r) => ` ${r.platform}: ${r.url}`).join(`
@@ -37035,7 +37218,7 @@ ${links}`);
37035
37218
  async function runMixtapeList(options, mixtapeListCommand) {
37036
37219
  const mixtapes = await mixtapeListCommand();
37037
37220
  if (options.json) {
37038
- printJson({ mixtapes, ok: true });
37221
+ printJson2({ mixtapes, ok: true });
37039
37222
  return;
37040
37223
  }
37041
37224
  if (mixtapes.length === 0) {
@@ -37055,7 +37238,7 @@ async function runClipsList(options, clipsListCommand, clipPostsListCommand) {
37055
37238
  ]);
37056
37239
  const dripByClip = new Map(posts.map((post) => [post.clipId, post]));
37057
37240
  if (options.json) {
37058
- printJson({
37241
+ printJson2({
37059
37242
  clips: clips.map((clip) => ({ ...clip, drip: dripByClip.get(clip.id) })),
37060
37243
  ok: true
37061
37244
  });
@@ -37079,7 +37262,7 @@ async function runClipsCut(clipId, options, clipCutCommand) {
37079
37262
  const onProgress = options.json ? () => {} : (message) => console.log(message);
37080
37263
  const result = await clipCutCommand(clipId, onProgress);
37081
37264
  if (options.json) {
37082
- printJson({ ok: true, ...result });
37265
+ printJson2({ ok: true, ...result });
37083
37266
  return;
37084
37267
  }
37085
37268
  console.log(`Cut ${result.clipId} \u2192 ${result.url} (${(result.sizeBytes / 1e6).toFixed(1)} MB).`);
@@ -37090,7 +37273,7 @@ async function runClipsSchedule(clipId, options, clipScheduleCommand) {
37090
37273
  }
37091
37274
  const post = await clipScheduleCommand(clipId, options.at);
37092
37275
  if (options.json) {
37093
- printJson({ ok: true, post });
37276
+ printJson2({ ok: true, post });
37094
37277
  return;
37095
37278
  }
37096
37279
  console.log(`Scheduled ${post.clipId} for ${post.scheduledFor}.`);
@@ -37098,7 +37281,7 @@ async function runClipsSchedule(clipId, options, clipScheduleCommand) {
37098
37281
  async function runClipsDripPause(paused, options, clipDripPauseCommand) {
37099
37282
  const result = await clipDripPauseCommand(paused);
37100
37283
  if (options.json) {
37101
- printJson({ ok: true, paused: result });
37284
+ printJson2({ ok: true, paused: result });
37102
37285
  return;
37103
37286
  }
37104
37287
  console.log(result ? "The drip-feed is paused." : "The drip-feed is running.");
@@ -37106,7 +37289,7 @@ async function runClipsDripPause(paused, options, clipDripPauseCommand) {
37106
37289
  async function runPublishAdvance(options, publishAdvanceCommand) {
37107
37290
  const result = await publishAdvanceCommand();
37108
37291
  if (options.json) {
37109
- printJson(result);
37292
+ printJson2(result);
37110
37293
  return;
37111
37294
  }
37112
37295
  if (result.paused) {
@@ -37130,7 +37313,7 @@ async function runPublishAdvance(options, publishAdvanceCommand) {
37130
37313
  }
37131
37314
  function printAnchorApifyBudget(state, options) {
37132
37315
  if (options.json) {
37133
- printJson({ ...state, ok: true });
37316
+ printJson2({ ...state, ok: true });
37134
37317
  return;
37135
37318
  }
37136
37319
  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 +37334,7 @@ function printCaptureBudget(state) {
37151
37334
  async function runCaptureBudget(options, captureBudgetCommand) {
37152
37335
  const state = await captureBudgetCommand();
37153
37336
  if (options.json) {
37154
- printJson(state);
37337
+ printJson2(state);
37155
37338
  return;
37156
37339
  }
37157
37340
  printCaptureBudget(state);
@@ -37159,7 +37342,7 @@ async function runCaptureBudget(options, captureBudgetCommand) {
37159
37342
  async function runSetCaptureBudget(input, options, setCaptureBudgetCommand) {
37160
37343
  const state = await setCaptureBudgetCommand(input);
37161
37344
  if (options.json) {
37162
- printJson(state);
37345
+ printJson2(state);
37163
37346
  return;
37164
37347
  }
37165
37348
  printCaptureBudget(state);
@@ -37194,7 +37377,7 @@ async function runSetCaptureCaps(options, setCaptureBudgetCommand) {
37194
37377
  async function runPublishAdvancePause(paused, options, publishAdvancePauseCommand) {
37195
37378
  const result = await publishAdvancePauseCommand(paused);
37196
37379
  if (options.json) {
37197
- printJson({ ok: true, paused: result });
37380
+ printJson2({ ok: true, paused: result });
37198
37381
  return;
37199
37382
  }
37200
37383
  console.log(result ? "The publish auto-advance is paused." : "The publish auto-advance is running.");
@@ -37205,7 +37388,7 @@ async function runMixtapeGet(idOrLogId, options, mixtapeGetCommand) {
37205
37388
  }
37206
37389
  const mixtape = await mixtapeGetCommand(idOrLogId);
37207
37390
  if (options.json) {
37208
- printJson(mixtape);
37391
+ printJson2(mixtape);
37209
37392
  return;
37210
37393
  }
37211
37394
  const coordinate = mixtape.logId ?? "unminted";
@@ -37236,7 +37419,7 @@ function formatCue(ms) {
37236
37419
  async function runNewsletterDraft(options, newsletterDraftCommand) {
37237
37420
  const result = await newsletterDraftCommand(options);
37238
37421
  if (options.json) {
37239
- printJson(result);
37422
+ printJson2(result);
37240
37423
  return;
37241
37424
  }
37242
37425
  console.log(`Drafted edition ${result.edition.id}. It stays a draft until the operator sends it.`);
@@ -37247,7 +37430,7 @@ async function runNewsletterUpdate(id, options, newsletterUpdateCommand) {
37247
37430
  }
37248
37431
  const result = await newsletterUpdateCommand(id, options);
37249
37432
  if (options.json) {
37250
- printJson(result);
37433
+ printJson2(result);
37251
37434
  return;
37252
37435
  }
37253
37436
  console.log(`Saved draft ${result.edition.id}.`);
@@ -37258,7 +37441,7 @@ async function runNewsletterSend(id, options, newsletterSendCommand) {
37258
37441
  }
37259
37442
  const result = await newsletterSendCommand(id);
37260
37443
  if (options.json) {
37261
- printJson(result);
37444
+ printJson2(result);
37262
37445
  return;
37263
37446
  }
37264
37447
  const number = result.edition.number;
@@ -37273,7 +37456,7 @@ async function runNewsletterDelete(id, options, newsletterDeleteCommand) {
37273
37456
  }
37274
37457
  const result = await newsletterDeleteCommand(id);
37275
37458
  if (options.json) {
37276
- printJson({ ...result, ok: true });
37459
+ printJson2({ ...result, ok: true });
37277
37460
  return;
37278
37461
  }
37279
37462
  console.log(`Deleted edition ${result.id}. The send window reopens to cover its finds.`);
@@ -37281,7 +37464,7 @@ async function runNewsletterDelete(id, options, newsletterDeleteCommand) {
37281
37464
  async function runNewsletterList(options, newsletterListCommand) {
37282
37465
  const editions = await newsletterListCommand();
37283
37466
  if (options.json) {
37284
- printJson({ editions, ok: true });
37467
+ printJson2({ editions, ok: true });
37285
37468
  return;
37286
37469
  }
37287
37470
  if (editions.length === 0) {
@@ -37298,7 +37481,7 @@ async function runLogbookGaps(options, logbookGapsCommand) {
37298
37481
  const limit = options.limit === undefined ? undefined : Number.parseInt(options.limit, 10);
37299
37482
  const { gaps, spent } = await logbookGapsCommand(limit !== undefined && Number.isFinite(limit) ? limit : undefined);
37300
37483
  if (options.json) {
37301
- printJson({ gaps, ok: true, spent });
37484
+ printJson2({ gaps, ok: true, spent });
37302
37485
  return;
37303
37486
  }
37304
37487
  if (gaps.length === 0) {
@@ -37320,7 +37503,7 @@ async function runLogbookWrite(sector, options, writeCommand) {
37320
37503
  title: options.title
37321
37504
  });
37322
37505
  if (options.json) {
37323
- printJson(result);
37506
+ printJson2(result);
37324
37507
  return;
37325
37508
  }
37326
37509
  const skipped = "skipped" in result && result.skipped;
@@ -37335,7 +37518,7 @@ function requirePromptSlug(slug) {
37335
37518
  async function runPromptsList(options, promptsListCommand) {
37336
37519
  const prompts = await promptsListCommand();
37337
37520
  if (options.json) {
37338
- printJson({ ok: true, prompts });
37521
+ printJson2({ ok: true, prompts });
37339
37522
  return;
37340
37523
  }
37341
37524
  await Promise.resolve().then(() => init_admin_prompts2());
@@ -37346,7 +37529,7 @@ async function runPromptsList(options, promptsListCommand) {
37346
37529
  async function runPromptGet(slug, options, promptGetCommand) {
37347
37530
  const resolved = await promptGetCommand(requirePromptSlug(slug));
37348
37531
  if (options.json) {
37349
- printJson(resolved);
37532
+ printJson2(resolved);
37350
37533
  return;
37351
37534
  }
37352
37535
  const live = resolved.source === "override" ? `v${resolved.version}, an operator edit` : "the repo's baked default (v0)";
@@ -37357,7 +37540,7 @@ async function runPromptGet(slug, options, promptGetCommand) {
37357
37540
  async function runPromptHistory(slug, options, promptDetailCommand) {
37358
37541
  const detail = await promptDetailCommand(requirePromptSlug(slug));
37359
37542
  if (options.json) {
37360
- printJson({
37543
+ printJson2({
37361
37544
  activeVersion: detail.activeVersion,
37362
37545
  ok: true,
37363
37546
  slug: detail.slug,
@@ -37379,7 +37562,7 @@ async function runPromptDiff(slug, options, commands) {
37379
37562
  const against = commands.parseAgainst(options.against);
37380
37563
  const result = await commands.promptDiffCommand(requirePromptSlug(slug), against);
37381
37564
  if (options.json) {
37382
- printJson({ ok: true, ...result });
37565
+ printJson2({ ok: true, ...result });
37383
37566
  return;
37384
37567
  }
37385
37568
  const live = result.live.source === "override" ? `v${result.live.version}` : "the repo's baked default";
@@ -37400,7 +37583,7 @@ async function runPromptUpdate(slug, options, promptUpdateCommand) {
37400
37583
  note: options.note
37401
37584
  });
37402
37585
  if (options.json) {
37403
- printJson({ ok: true, ...result });
37586
+ printJson2({ ok: true, ...result });
37404
37587
  return;
37405
37588
  }
37406
37589
  console.log(`${result.slug}: v${result.version} is live. Nothing to deploy.`);
@@ -37416,7 +37599,7 @@ async function runPromptRollback(slug, version, options, commands) {
37416
37599
  }
37417
37600
  const result = await commands.promptRollbackCommand(promptSlug, parsed);
37418
37601
  if (options.json) {
37419
- printJson({ ok: true, ...result });
37602
+ printJson2({ ok: true, ...result });
37420
37603
  return;
37421
37604
  }
37422
37605
  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 +37607,7 @@ async function runPromptRollback(slug, version, options, commands) {
37424
37607
  async function runPromptReset(slug, options, promptResetCommand) {
37425
37608
  const result = await promptResetCommand(requirePromptSlug(slug));
37426
37609
  if (options.json) {
37427
- printJson({ ok: true, ...result });
37610
+ printJson2({ ok: true, ...result });
37428
37611
  return;
37429
37612
  }
37430
37613
  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 +37622,7 @@ async function runAdd(spotifyUrl, options, addCommand) {
37439
37622
  note: options.note
37440
37623
  });
37441
37624
  if (options.json) {
37442
- printJson({
37625
+ printJson2({
37443
37626
  ok: true,
37444
37627
  ...result
37445
37628
  });
@@ -37467,7 +37650,7 @@ async function runRecent(options, recentCommand) {
37467
37650
  const limit = parseListLimit(options.limit);
37468
37651
  const tracks = await recentCommand(limit);
37469
37652
  if (options.json) {
37470
- printJson({
37653
+ printJson2({
37471
37654
  ok: true,
37472
37655
  tracks
37473
37656
  });
@@ -37485,7 +37668,7 @@ async function runGalaxies(slug, options, commands) {
37485
37668
  if (slug) {
37486
37669
  const { findings, galaxy } = await commands.galaxyGetCommand(slug);
37487
37670
  if (options.json) {
37488
- printJson({ findings, galaxy, ok: true });
37671
+ printJson2({ findings, galaxy, ok: true });
37489
37672
  return;
37490
37673
  }
37491
37674
  console.log(`${galaxy.name} (${galaxy.slug})`);
@@ -37498,7 +37681,7 @@ async function runGalaxies(slug, options, commands) {
37498
37681
  }
37499
37682
  const galaxies = await commands.galaxiesListCommand();
37500
37683
  if (options.json) {
37501
- printJson({ galaxies, ok: true });
37684
+ printJson2({ galaxies, ok: true });
37502
37685
  return;
37503
37686
  }
37504
37687
  if (galaxies.length === 0) {
@@ -37515,7 +37698,7 @@ async function runArtistsRank(options, rankArtistsCommand) {
37515
37698
  limit: options.limit
37516
37699
  });
37517
37700
  if (options.json) {
37518
- printJson({ ok: true, summary });
37701
+ printJson2({ ok: true, summary });
37519
37702
  return;
37520
37703
  }
37521
37704
  const removed = summary.centroidsRemoved > 0 ? ` Purged ${summary.centroidsRemoved} orphan(s).` : "";
@@ -37527,7 +37710,7 @@ async function runCatalogueRank(options, catalogueRankCommand) {
37527
37710
  limit: options.limit
37528
37711
  });
37529
37712
  if (options.json) {
37530
- printJson({ ok: true, summary, telescope });
37713
+ printJson2({ ok: true, summary, telescope });
37531
37714
  return;
37532
37715
  }
37533
37716
  const quarantine = summary.quarantined > 0 ? ` Quarantined ${summary.quarantined} as wrong audio.` : "";
@@ -37548,7 +37731,7 @@ async function runCatalogueList(options, catalogueListCommand) {
37548
37731
  limit: options.limit
37549
37732
  });
37550
37733
  if (options.json) {
37551
- printJson({ ok: true, summary, tracks });
37734
+ printJson2({ ok: true, summary, tracks });
37552
37735
  return;
37553
37736
  }
37554
37737
  if (tracks.length === 0) {
@@ -37584,7 +37767,7 @@ async function runCatalogueList(options, catalogueListCommand) {
37584
37767
  async function runNoteHeld(options, noteHeldCommand) {
37585
37768
  const { gate, rejections } = await noteHeldCommand({ settled: options.settled === true });
37586
37769
  if (options.json) {
37587
- printJson({ gate, ok: true, rejections });
37770
+ printJson2({ gate, ok: true, rejections });
37588
37771
  return;
37589
37772
  }
37590
37773
  if (rejections.length === 0) {
@@ -37614,7 +37797,7 @@ async function runNoteGate(options, noteGateCommand) {
37614
37797
  ...minPhraseWords === undefined ? {} : { minPhraseWords }
37615
37798
  });
37616
37799
  if (options.json) {
37617
- printJson({ gate, ok: true });
37800
+ printJson2({ gate, ok: true });
37618
37801
  return;
37619
37802
  }
37620
37803
  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 +37805,7 @@ async function runNoteGate(options, noteGateCommand) {
37622
37805
  async function runObservationHeld(options, observationHeldCommand) {
37623
37806
  const { gate, rejections } = await observationHeldCommand({ settled: options.settled === true });
37624
37807
  if (options.json) {
37625
- printJson({ gate, ok: true, rejections });
37808
+ printJson2({ gate, ok: true, rejections });
37626
37809
  return;
37627
37810
  }
37628
37811
  if (rejections.length === 0) {
@@ -37652,7 +37835,7 @@ async function runObservationGate(options, observationGateCommand) {
37652
37835
  ...minPhraseWords === undefined ? {} : { minPhraseWords }
37653
37836
  });
37654
37837
  if (options.json) {
37655
- printJson({ gate, ok: true });
37838
+ printJson2({ gate, ok: true });
37656
37839
  return;
37657
37840
  }
37658
37841
  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 +37843,7 @@ async function runObservationGate(options, observationGateCommand) {
37660
37843
  async function runGalaxyMapRead(options, galaxyMapReadCommand) {
37661
37844
  const galaxies = await galaxyMapReadCommand();
37662
37845
  if (options.json) {
37663
- printJson({ galaxies, ok: true });
37846
+ printJson2({ galaxies, ok: true });
37664
37847
  return;
37665
37848
  }
37666
37849
  if (galaxies.length === 0) {
@@ -37676,7 +37859,7 @@ async function runGalaxyMapRead(options, galaxyMapReadCommand) {
37676
37859
  async function runGalaxyEmbeddings(options, galaxyEmbeddingsCommand) {
37677
37860
  const result = await galaxyEmbeddingsCommand({ cursor: options.cursor, limit: options.limit });
37678
37861
  if (options.json) {
37679
- printJson(result);
37862
+ printJson2(result);
37680
37863
  return;
37681
37864
  }
37682
37865
  console.log(`${result.embeddings.length} embedding(s)${result.nextCursor ? ` \xB7 nextCursor: ${result.nextCursor}` : " \xB7 end of corpus"}`);
@@ -37689,7 +37872,7 @@ async function runGalaxyMapWrite(options, galaxyMapWriteCommand) {
37689
37872
  }
37690
37873
  const galaxies = await galaxyMapWriteCommand(clusters);
37691
37874
  if (options.json) {
37692
- printJson({ galaxies, ok: true });
37875
+ printJson2({ galaxies, ok: true });
37693
37876
  return;
37694
37877
  }
37695
37878
  console.log(`Wrote ${clusters.length} cluster(s); the map now holds ${galaxies.length} galaxy(ies).`);
@@ -37697,7 +37880,7 @@ async function runGalaxyMapWrite(options, galaxyMapWriteCommand) {
37697
37880
  async function runMixtapes(options, mixtapesCommand) {
37698
37881
  const mixtapes = await mixtapesCommand();
37699
37882
  if (options.json) {
37700
- printJson({ mixtapes, ok: true });
37883
+ printJson2({ mixtapes, ok: true });
37701
37884
  return;
37702
37885
  }
37703
37886
  if (mixtapes.length === 0) {
@@ -37832,7 +38015,7 @@ async function runAdminTracksList(options, listCommand) {
37832
38015
  const hasKey = resolveHasKey(options);
37833
38016
  const tracks = await listCommand({ hasKey, limit, order });
37834
38017
  if (options.json) {
37835
- printJson({ ok: true, tracks });
38018
+ printJson2({ ok: true, tracks });
37836
38019
  return;
37837
38020
  }
37838
38021
  if (tracks.length === 0) {
@@ -37850,7 +38033,7 @@ async function runAdminTracksList(options, listCommand) {
37850
38033
  async function runAdminAttention(options, attentionQueueCommand) {
37851
38034
  const queue = await attentionQueueCommand();
37852
38035
  if (options.json) {
37853
- printJson({ attention: queue, ok: true });
38036
+ printJson2({ attention: queue, ok: true });
37854
38037
  return;
37855
38038
  }
37856
38039
  await Promise.resolve().then(() => init_admin_attention());
@@ -37881,7 +38064,7 @@ async function runAdminTelemetry(options, telemetryCommand) {
37881
38064
  until
37882
38065
  });
37883
38066
  if (options.json) {
37884
- printJson({ ok: true, ...page });
38067
+ printJson2({ ok: true, ...page });
37885
38068
  return;
37886
38069
  }
37887
38070
  await Promise.resolve().then(() => init_admin_telemetry());
@@ -37894,7 +38077,7 @@ async function runAdminQueue(options, queueCommand) {
37894
38077
  hasObservation: options.hasObservation ? true : undefined
37895
38078
  });
37896
38079
  if (options.json) {
37897
- printJson({
38080
+ printJson2({
37898
38081
  ok: true,
37899
38082
  tracks
37900
38083
  });
@@ -37919,7 +38102,7 @@ async function runQueueView({
37919
38102
  const limit = parseListLimit(options.limit);
37920
38103
  const tracks = await fetch2(limit);
37921
38104
  if (options.json) {
37922
- printJson({
38105
+ printJson2({
37923
38106
  ok: true,
37924
38107
  tracks
37925
38108
  });
@@ -38007,7 +38190,7 @@ async function runAdminTrackWork(options, trackWorkCommand) {
38007
38190
  scope
38008
38191
  });
38009
38192
  if (options.json) {
38010
- printJson({ debtPending, ok: true, queued, tracks });
38193
+ printJson2({ debtPending, ok: true, queued, tracks });
38011
38194
  return;
38012
38195
  }
38013
38196
  if (debtPending === true) {
@@ -38091,7 +38274,7 @@ async function runAdminVehicles(options, vehiclesCommand) {
38091
38274
  const limit = parseListLimit(options.limit);
38092
38275
  const vehicles = await vehiclesCommand(limit);
38093
38276
  if (options.json) {
38094
- printJson({
38277
+ printJson2({
38095
38278
  ok: true,
38096
38279
  vehicles
38097
38280
  });
@@ -38137,7 +38320,7 @@ async function runOpen(target, extra, options, openCommands) {
38137
38320
  async function runRandom(options, randomCommand) {
38138
38321
  const track = await randomCommand();
38139
38322
  if (options.json) {
38140
- printJson({
38323
+ printJson2({
38141
38324
  ok: true,
38142
38325
  track
38143
38326
  });
@@ -38157,7 +38340,7 @@ async function runRandom(options, randomCommand) {
38157
38340
  async function runStatus(options, statusCommand) {
38158
38341
  const snapshot = await statusCommand();
38159
38342
  if (options.json) {
38160
- printJson({
38343
+ printJson2({
38161
38344
  ok: true,
38162
38345
  ...snapshot
38163
38346
  });
@@ -38170,7 +38353,7 @@ async function runStatus(options, statusCommand) {
38170
38353
  async function runMe(options, meCommand) {
38171
38354
  const me = await meCommand();
38172
38355
  if (options.json) {
38173
- printJson({ ok: true, ...me });
38356
+ printJson2({ ok: true, ...me });
38174
38357
  return;
38175
38358
  }
38176
38359
  console.log(me.name);
@@ -38291,6 +38474,10 @@ function topLevelCommand(args) {
38291
38474
  if (arg.startsWith("--env=")) {
38292
38475
  continue;
38293
38476
  }
38477
+ if (stringOptions.has(arg)) {
38478
+ index += 1;
38479
+ continue;
38480
+ }
38294
38481
  if (!arg.startsWith("-")) {
38295
38482
  return arg;
38296
38483
  }
@@ -38311,6 +38498,7 @@ function normalizeCommanderError(error) {
38311
38498
  return new Error(message);
38312
38499
  }
38313
38500
  var stringOptions = new Set([
38501
+ "--timeout",
38314
38502
  "--isrc-refresh-limit",
38315
38503
  "--action",
38316
38504
  "--against",