fluncle 0.81.0 → 0.83.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 +75 -162
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -462,26 +462,6 @@ function versionMatches(findingTitle, candidateTitle) {
462
462
  }
463
463
  return !candidateIsRemix;
464
464
  }
465
- function trackLabel(artists, title) {
466
- const joined = artists.join(", ");
467
- return joined && title ? `${joined} — ${title}` : joined || title;
468
- }
469
- function resolveClipTracks(options) {
470
- const { inMs, members, outMs, setDurationMs } = options;
471
- const cued = members.filter((member) => member.startMs != null).sort((a, b) => a.startMs - b.startMs);
472
- if (cued.length === 0) {
473
- return [];
474
- }
475
- const lastIndex = cued.length - 1;
476
- return cued.filter((member, index) => {
477
- const lo = index === 0 ? Math.min(member.startMs, inMs) : member.startMs;
478
- const hi = index === lastIndex ? Math.max(setDurationMs, outMs) : cued[index + 1]?.startMs ?? Number.POSITIVE_INFINITY;
479
- return lo < outMs && inMs < hi;
480
- }).map((member) => ({
481
- label: trackLabel(member.artists, member.title),
482
- startMs: member.startMs
483
- }));
484
- }
485
465
  function mixcloudSections(members) {
486
466
  return members.filter((member) => member.startMs != null).sort((a, b) => a.startMs - b.startMs).map((member) => ({
487
467
  artist: member.artists.join(", "),
@@ -558,7 +538,7 @@ function parseVersion(version) {
558
538
  var currentVersion;
559
539
  var init_version = __esm(() => {
560
540
  init_output();
561
- currentVersion = "0.81.0".trim() ? "0.81.0".trim() : "0.1.0";
541
+ currentVersion = "0.83.0".trim() ? "0.83.0".trim() : "0.1.0";
562
542
  });
563
543
 
564
544
  // src/update-notifier.ts
@@ -2466,7 +2446,7 @@ function parseVersion2(version) {
2466
2446
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2467
2447
  var init_version2 = __esm(() => {
2468
2448
  init_output();
2469
- currentVersion2 = "0.81.0".trim() ? "0.81.0".trim() : "0.1.0";
2449
+ currentVersion2 = "0.83.0".trim() ? "0.83.0".trim() : "0.1.0";
2470
2450
  });
2471
2451
 
2472
2452
  // ../../packages/registry/src/index.ts
@@ -3385,13 +3365,24 @@ __export(exports_admin_tracks, {
3385
3365
  queueCommand: () => queueCommand,
3386
3366
  observeQueueCommand: () => observeQueueCommand,
3387
3367
  noteQueueCommand: () => noteQueueCommand,
3368
+ listCommand: () => listCommand,
3388
3369
  enrichQueueCommand: () => enrichQueueCommand,
3389
3370
  contextQueueCommand: () => contextQueueCommand,
3390
3371
  backfillLastfmCommand: () => backfillLastfmCommand,
3391
3372
  backfillDiscogsCommand: () => backfillDiscogsCommand
3392
3373
  });
3393
3374
  async function fetchAdminTracks(options) {
3394
- const { hasContext, hasNote, hasObservation, hasVideo, max, order, retryEmptyContext, status } = options;
3375
+ const {
3376
+ hasContext,
3377
+ hasKey,
3378
+ hasNote,
3379
+ hasObservation,
3380
+ hasVideo,
3381
+ max,
3382
+ order,
3383
+ retryEmptyContext,
3384
+ status
3385
+ } = options;
3395
3386
  const results = [];
3396
3387
  let cursor;
3397
3388
  do {
@@ -3399,6 +3390,9 @@ async function fetchAdminTracks(options) {
3399
3390
  if (hasVideo !== undefined) {
3400
3391
  params.set("hasVideo", String(hasVideo));
3401
3392
  }
3393
+ if (hasKey !== undefined) {
3394
+ params.set("hasKey", String(hasKey));
3395
+ }
3402
3396
  if (hasContext !== undefined) {
3403
3397
  params.set("hasContext", String(hasContext));
3404
3398
  }
@@ -3432,6 +3426,13 @@ async function fetchAdminTracks(options) {
3432
3426
  } while (cursor);
3433
3427
  return results;
3434
3428
  }
3429
+ async function listCommand(options) {
3430
+ return fetchAdminTracks({
3431
+ hasKey: options.hasKey,
3432
+ max: options.limit,
3433
+ order: options.order
3434
+ });
3435
+ }
3435
3436
  async function queueCommand(limit, filters = {}) {
3436
3437
  return fetchAdminTracks({
3437
3438
  hasContext: true,
@@ -4250,135 +4251,32 @@ var init_mixtapes2 = __esm(() => {
4250
4251
  var exports_clips = {};
4251
4252
  __export(exports_clips, {
4252
4253
  setVideoUrl: () => setVideoUrl,
4253
- resolveClipSansFontFile: () => resolveClipSansFontFile,
4254
- resolveClipFontFile: () => resolveClipFontFile,
4255
- escapeDrawtextValue: () => escapeDrawtextValue,
4256
4254
  clipsListCommand: () => clipsListCommand,
4257
4255
  clipFootageKey: () => clipFootageKey,
4258
4256
  clipCutFilterComplex: () => clipCutFilterComplex,
4259
4257
  clipCutFfmpegArgs: () => clipCutFfmpegArgs,
4260
4258
  clipCutCommand: () => clipCutCommand,
4261
- brandDrawtext: () => brandDrawtext,
4262
4259
  MAX_CLIP_BYTES: () => MAX_CLIP_BYTES,
4263
4260
  CLIP_WIDTH: () => CLIP_WIDTH,
4264
- CLIP_TITLE_SIZE: () => CLIP_TITLE_SIZE,
4265
- CLIP_TITLE_COLOR: () => CLIP_TITLE_COLOR,
4266
- CLIP_SHARP_BORDERW: () => CLIP_SHARP_BORDERW,
4267
- CLIP_SAFE_BOTTOM: () => CLIP_SAFE_BOTTOM,
4268
4261
  CLIP_MAXRATE: () => CLIP_MAXRATE,
4269
- CLIP_MARGIN_X: () => CLIP_MARGIN_X,
4270
- CLIP_LINE_GAP: () => CLIP_LINE_GAP,
4271
4262
  CLIP_HEIGHT: () => CLIP_HEIGHT,
4272
- CLIP_HALO_FEATHER_SIGMA: () => CLIP_HALO_FEATHER_SIGMA,
4273
- CLIP_HALO_CORE_SIGMA: () => CLIP_HALO_CORE_SIGMA,
4274
- CLIP_HALO_COLOR: () => CLIP_HALO_COLOR,
4275
4263
  CLIP_CRF: () => CLIP_CRF,
4276
- CLIP_COORDINATE_SIZE: () => CLIP_COORDINATE_SIZE,
4277
- CLIP_COORDINATE_COLOR: () => CLIP_COORDINATE_COLOR,
4278
4264
  CLIP_BUFSIZE: () => CLIP_BUFSIZE,
4279
- CLIP_AUDIO_BITRATE: () => CLIP_AUDIO_BITRATE,
4280
- BOX_CLIP_SANS_FONT_FILE: () => BOX_CLIP_SANS_FONT_FILE,
4281
- BOX_CLIP_FONT_FILE: () => BOX_CLIP_FONT_FILE
4265
+ CLIP_AUDIO_BITRATE: () => CLIP_AUDIO_BITRATE
4282
4266
  });
4283
4267
  import { randomUUID as randomUUID2 } from "node:crypto";
4284
- import { existsSync as existsSync5, rmSync as rmSync3, statSync as statSync4 } from "node:fs";
4268
+ import { rmSync as rmSync3, statSync as statSync4 } from "node:fs";
4285
4269
  import { tmpdir as tmpdir2 } from "node:os";
4286
4270
  import { join as join5 } from "node:path";
4287
4271
  function clipFootageKey(clipId) {
4288
4272
  return `${clipId}/footage.mp4`;
4289
4273
  }
4290
- function resolveFontFile(envVar, bakedPath) {
4291
- const explicit = process.env[envVar];
4292
- if (explicit) {
4293
- return explicit;
4294
- }
4295
- return existsSync5(bakedPath) ? bakedPath : undefined;
4296
- }
4297
- function resolveClipFontFile() {
4298
- return resolveFontFile("CLIP_FONT_FILE", BOX_CLIP_FONT_FILE);
4299
- }
4300
- function resolveClipSansFontFile() {
4301
- return resolveFontFile("CLIP_SANS_FONT_FILE", BOX_CLIP_SANS_FONT_FILE);
4302
- }
4303
4274
  function setVideoUrl(logId) {
4304
4275
  return `${FOUND_BASE3}/${encodeURIComponent(logId)}/set.mp4`;
4305
4276
  }
4306
- function escapeDrawtextValue(text) {
4307
- return text.replace(/\\/g, "\\\\").replace(/:/g, "\\:").replace(/,/g, "\\,").replace(/%/g, "\\%").replace(/'/g, "'\\''");
4308
- }
4309
- function brandDrawtext(options) {
4310
- const value = escapeDrawtextValue(options.text);
4311
- const font = options.fontFile ? `:fontfile='${options.fontFile}'` : "";
4312
- const border = options.borderw && options.borderw > 0 ? `:borderw=${options.borderw}:bordercolor=${CLIP_HALO_COLOR}` : "";
4313
- const enable = options.enable ? `:enable='${options.enable}'` : "";
4314
- return `drawtext=text='${value}'${font}:` + `fontcolor=${options.color}:fontsize=${options.size}:` + `x=${options.x}:y=${options.y}${border}${enable}`;
4315
- }
4316
- function resolveTitleLines(options, y) {
4317
- const inMs = options.inMs ?? 0;
4318
- const outMs = options.outMs ?? 0;
4319
- const clipDur = Math.max(0, (outMs - inMs) / 1000);
4320
- const base = { fontFile: options.sansFontFile, size: CLIP_TITLE_SIZE, x: CLIP_MARGIN_X, y };
4321
- const resolved = options.members && options.members.length > 0 ? resolveClipTracks({
4322
- inMs,
4323
- members: options.members,
4324
- outMs,
4325
- setDurationMs: options.setDurationMs ?? 0
4326
- }) : [];
4327
- if (resolved.length === 0) {
4328
- return [{ ...base, text: options.title }];
4329
- }
4330
- const clamp = (seconds) => Math.min(Math.max(seconds, 0), clipDur);
4331
- return resolved.map((track, index) => {
4332
- const relStart = clamp((track.startMs - inMs) / 1000);
4333
- const next = resolved[index + 1];
4334
- const relEnd = next ? clamp((next.startMs - inMs) / 1000) : clipDur;
4335
- return {
4336
- ...base,
4337
- enable: `between(t,${relStart.toFixed(3)},${relEnd.toFixed(3)})`,
4338
- text: track.label
4339
- };
4340
- });
4341
- }
4342
4277
  function clipCutFilterComplex(options) {
4343
4278
  const xOffset = Math.max(0, Math.round(options.xOffset));
4344
- const crop = `crop=ih*9/16:ih:${xOffset}:0,scale=${CLIP_WIDTH}:${CLIP_HEIGHT}`;
4345
- const logId = options.logId?.trim();
4346
- const hasCoordinate = Boolean(logId);
4347
- const coordinateBox = Math.round(CLIP_COORDINATE_SIZE * 1.18);
4348
- const titleBottomOffset = hasCoordinate ? CLIP_SAFE_BOTTOM + coordinateBox + CLIP_LINE_GAP : CLIP_SAFE_BOTTOM;
4349
- const titleY = `h-${titleBottomOffset}-th`;
4350
- const titleLines = resolveTitleLines(options, titleY);
4351
- const coordinate2 = hasCoordinate ? {
4352
- fontFile: options.oxaniumFontFile,
4353
- size: CLIP_COORDINATE_SIZE,
4354
- text: `fluncle://${logId}`,
4355
- x: CLIP_MARGIN_X,
4356
- y: `h-${CLIP_SAFE_BOTTOM}-th`
4357
- } : undefined;
4358
- const haloGlyphs = [
4359
- ...titleLines.map((line) => brandDrawtext({ ...line, color: CLIP_HALO_COLOR })),
4360
- ...coordinate2 ? [brandDrawtext({ ...coordinate2, color: CLIP_HALO_COLOR })] : []
4361
- ].join(",");
4362
- const sharpGlyphs = [
4363
- ...titleLines.map((line) => brandDrawtext({ ...line, borderw: CLIP_SHARP_BORDERW, color: CLIP_TITLE_COLOR })),
4364
- ...coordinate2 ? [
4365
- brandDrawtext({
4366
- ...coordinate2,
4367
- borderw: CLIP_SHARP_BORDERW,
4368
- color: CLIP_COORDINATE_COLOR
4369
- })
4370
- ] : []
4371
- ].join(",");
4372
- return [
4373
- `[0:v]${crop},setsar=1[base]`,
4374
- `color=c=black@0:s=${CLIP_WIDTH}x${CLIP_HEIGHT},format=rgba,${haloGlyphs}[ink]`,
4375
- `[ink]split[ink1][ink2]`,
4376
- `[ink1]gblur=sigma=${CLIP_HALO_CORE_SIGMA}[core]`,
4377
- `[ink2]gblur=sigma=${CLIP_HALO_FEATHER_SIGMA}[feather]`,
4378
- `[base][feather]overlay=0:0[b1]`,
4379
- `[b1][core]overlay=0:0[b2]`,
4380
- `[b2]${sharpGlyphs}[out]`
4381
- ].join(";");
4279
+ return `[0:v]crop=ih*9/16:ih:${xOffset}:0,scale=${CLIP_WIDTH}:${CLIP_HEIGHT},setsar=1[out]`;
4382
4280
  }
4383
4281
  function clipCutFfmpegArgs(options) {
4384
4282
  const inSeconds = (options.inMs / 1000).toFixed(3);
@@ -4442,15 +4340,7 @@ async function resolveClipSource(clip) {
4442
4340
  throw new CliError2("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
4443
4341
  }
4444
4342
  return {
4445
- logId: recording.logId,
4446
- members: recording.tracklist.map((cue) => ({
4447
- artists: cue.artists,
4448
- startMs: cue.startMs,
4449
- title: cue.title
4450
- })),
4451
- setDurationMs: recording.durationMs ?? 0,
4452
- setUrl: `${FOUND_BASE3}/${recording.r2Key.split("/").map(encodeURIComponent).join("/")}`,
4453
- title: recording.title
4343
+ setUrl: `${FOUND_BASE3}/${recording.r2Key.split("/").map(encodeURIComponent).join("/")}`
4454
4344
  };
4455
4345
  }
4456
4346
  if (!clip.mixtapeId) {
@@ -4464,13 +4354,7 @@ async function resolveClipSource(clip) {
4464
4354
  if (!mixtape.setVideoAt) {
4465
4355
  throw new CliError2("set_not_staged", `Mixtape ${mixtape.logId} has no staged set video — run \`distribute --set-video\` first`);
4466
4356
  }
4467
- return {
4468
- logId: mixtape.logId,
4469
- members: mixtape.members,
4470
- setDurationMs: mixtape.durationMs ?? 0,
4471
- setUrl: setVideoUrl(mixtape.logId),
4472
- title: mixtape.title
4473
- };
4357
+ return { setUrl: setVideoUrl(mixtape.logId) };
4474
4358
  }
4475
4359
  async function clipCutCommand(clipId, onProgress = () => {}) {
4476
4360
  const clips = await clipsListCommand();
@@ -4482,18 +4366,12 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
4482
4366
  await assertFfmpeg2();
4483
4367
  const outputPath = join5(tmpdir2(), `fluncle-clip-${randomUUID2()}.mp4`);
4484
4368
  try {
4485
- onProgress(`Clip ${clipId}: cutting [${clip.inMs}–${clip.outMs}ms]${source.logId ? ` from ${source.logId}` : ""}…`);
4369
+ onProgress(`Clip ${clipId}: cutting [${clip.inMs}–${clip.outMs}ms]…`);
4486
4370
  await runClipCut({
4487
4371
  inMs: clip.inMs,
4488
- logId: source.logId,
4489
- members: source.members,
4490
4372
  outMs: clip.outMs,
4491
4373
  outputPath,
4492
- oxaniumFontFile: resolveClipFontFile(),
4493
- sansFontFile: resolveClipSansFontFile(),
4494
- setDurationMs: source.setDurationMs,
4495
4374
  setUrl: source.setUrl,
4496
- title: source.title,
4497
4375
  xOffset: clip.xOffset
4498
4376
  });
4499
4377
  const sizeBytes = statSync4(outputPath).size;
@@ -4508,7 +4386,6 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
4508
4386
  return {
4509
4387
  clipId,
4510
4388
  key: presign.key,
4511
- logId: source.logId,
4512
4389
  sizeBytes,
4513
4390
  url: `${FOUND_BASE3}/${presign.key}`
4514
4391
  };
@@ -4552,9 +4429,8 @@ async function runClipCut(options) {
4552
4429
  throw new CliError2("ffmpeg_failed", `ffmpeg failed to cut the clip${detail ? `: ${detail}` : ""}`);
4553
4430
  }
4554
4431
  }
4555
- 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, CLIP_TITLE_COLOR = "0xf4ead7", CLIP_COORDINATE_COLOR = "0xb7ab95", CLIP_HALO_COLOR = "0x090a0b", CLIP_HALO_CORE_SIGMA = 3, CLIP_HALO_FEATHER_SIGMA = 9, CLIP_SHARP_BORDERW = 1, CLIP_MARGIN_X = 96, CLIP_SAFE_BOTTOM = 230, CLIP_LINE_GAP = 10, CLIP_TITLE_SIZE = 40, CLIP_COORDINATE_SIZE = 22, BOX_CLIP_FONT_FILE = "/opt/fonts/Oxanium-SemiBold.ttf", BOX_CLIP_SANS_FONT_FILE = "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf";
4432
+ 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;
4556
4433
  var init_clips = __esm(() => {
4557
- init_util();
4558
4434
  init_api();
4559
4435
  init_output();
4560
4436
  MAX_CLIP_BYTES = 100 * 1024 * 1024;
@@ -4571,7 +4447,7 @@ __export(exports_recordings2, {
4571
4447
  recordingDeleteCommand: () => recordingDeleteCommand2,
4572
4448
  recordingCreateCommand: () => recordingCreateCommand2
4573
4449
  });
4574
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "node:fs";
4450
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
4575
4451
  async function recordingGet2(id) {
4576
4452
  const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
4577
4453
  return response.recording;
@@ -4618,7 +4494,7 @@ async function recordingCreateCommand2(options = {}) {
4618
4494
  if (!options.video) {
4619
4495
  throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
4620
4496
  }
4621
- if (!existsSync6(options.video)) {
4497
+ if (!existsSync5(options.video)) {
4622
4498
  throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
4623
4499
  }
4624
4500
  const created = await adminApiPost("/api/admin/recordings", {
@@ -4651,7 +4527,7 @@ async function recordingUpdateCommand2(id, options = {}) {
4651
4527
  body.recordedAt = options.recordedAt;
4652
4528
  }
4653
4529
  if (options.tracklistFile !== undefined) {
4654
- if (!existsSync6(options.tracklistFile)) {
4530
+ if (!existsSync5(options.tracklistFile)) {
4655
4531
  throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
4656
4532
  }
4657
4533
  try {
@@ -4708,7 +4584,7 @@ __export(exports_newsletter, {
4708
4584
  newsletterDraftCommand: () => newsletterDraftCommand,
4709
4585
  newsletterDeleteCommand: () => newsletterDeleteCommand
4710
4586
  });
4711
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "node:fs";
4587
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
4712
4588
  function buildBody3(options, { requireContent }) {
4713
4589
  const body = {};
4714
4590
  if (options.contentFile !== undefined) {
@@ -4728,7 +4604,7 @@ function buildBody3(options, { requireContent }) {
4728
4604
  return body;
4729
4605
  }
4730
4606
  function readContentFile(filePath) {
4731
- if (!existsSync7(filePath)) {
4607
+ if (!existsSync6(filePath)) {
4732
4608
  throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
4733
4609
  }
4734
4610
  const text = readFileSync6(filePath, "utf-8");
@@ -5366,7 +5242,7 @@ var init_format2 = __esm(() => {
5366
5242
  });
5367
5243
 
5368
5244
  // src/cli.ts
5369
- import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
5245
+ import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
5370
5246
  import path2 from "path";
5371
5247
 
5372
5248
  // ../../node_modules/commander/lib/error.js
@@ -7612,6 +7488,10 @@ function addAdminCommands(program2) {
7612
7488
  const { queueCommand: queueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7613
7489
  await runAdminQueue(options, queueCommand2);
7614
7490
  });
7491
+ adminTracks.command("list").description("List findings, filterable by musical-key presence (--no-key / --has-key)").option("--limit <limit>", "Number of findings to show", "50").option("--no-key", "Only findings with NO stored musical key (the key-backfill backlog)").option("--has-key <bool>", "Filter by key presence: true (has key) or false (missing)").option("--order <order>", "Sort: asc (oldest first) or desc (newest first)", "desc").option("--json", "Print JSON", false).action(async (options) => {
7492
+ const { listCommand: listCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7493
+ await runAdminTracksList(options, listCommand2);
7494
+ });
7615
7495
  adminTracks.command("enrich").description("Enrichment worklist (pending, failed, or stuck processing) \u2014 use --queue").option("--queue", "Show the enrichment worklist, oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--json", "Print JSON", false).action(async (options) => {
7616
7496
  if (!options.queue) {
7617
7497
  console.error("`tracks enrich` is a worklist view \u2014 enrichment runs on the on-box `fluncle-enrich` cron.\nUse `tracks enrich --queue` to see findings needing (re-)enrichment.");
@@ -8075,7 +7955,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
8075
7955
  return;
8076
7956
  }
8077
7957
  const candidate = path2.join(dir, name);
8078
- return existsSync8(candidate) ? candidate : undefined;
7958
+ return existsSync7(candidate) ? candidate : undefined;
8079
7959
  };
8080
7960
  const resolveFile = (explicit, name) => {
8081
7961
  if (explicit) {
@@ -8566,6 +8446,39 @@ function parseListLimit(value) {
8566
8446
  }
8567
8447
  return limit;
8568
8448
  }
8449
+ function resolveHasKey(options) {
8450
+ if (options.key === false) {
8451
+ return false;
8452
+ }
8453
+ if (options.hasKey === "false") {
8454
+ return false;
8455
+ }
8456
+ if (options.hasKey === "true") {
8457
+ return true;
8458
+ }
8459
+ return;
8460
+ }
8461
+ async function runAdminTracksList(options, listCommand2) {
8462
+ const limit = parseListLimit(options.limit);
8463
+ const order = options.order === "asc" ? "asc" : "desc";
8464
+ const hasKey = resolveHasKey(options);
8465
+ const tracks = await listCommand2({ hasKey, limit, order });
8466
+ if (options.json) {
8467
+ printJson({ ok: true, tracks });
8468
+ return;
8469
+ }
8470
+ if (tracks.length === 0) {
8471
+ const scope2 = hasKey === false ? " missing a key" : hasKey === true ? " with a key" : "";
8472
+ console.log(`No findings${scope2}.`);
8473
+ return;
8474
+ }
8475
+ const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
8476
+ const scope = hasKey === false ? " missing a musical key" : hasKey === true ? " with a musical key" : "";
8477
+ const noun = tracks.length === 1 ? "finding" : "findings";
8478
+ console.log(`${tracks.length} ${noun}${scope}:`);
8479
+ console.log(trackRows2(tracks).join(`
8480
+ `));
8481
+ }
8569
8482
  async function runAdminQueue(options, queueCommand2) {
8570
8483
  const limit = parseListLimit(options.limit);
8571
8484
  const tracks = await queueCommand2(limit, {
package/package.json CHANGED
@@ -31,5 +31,5 @@
31
31
  "url": "git+https://github.com/mauricekleine/fluncle.git"
32
32
  },
33
33
  "type": "module",
34
- "version": "0.81.0"
34
+ "version": "0.83.0"
35
35
  }