fluncle 0.174.0 → 0.176.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 +41 -35
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -561,7 +561,7 @@ function parseVersion(version) {
561
561
  var currentVersion;
562
562
  var init_version = __esm(() => {
563
563
  init_output();
564
- currentVersion = "0.174.0".trim() ? "0.174.0".trim() : "0.1.0";
564
+ currentVersion = "0.176.0".trim() ? "0.176.0".trim() : "0.1.0";
565
565
  });
566
566
 
567
567
  // src/update-notifier.ts
@@ -2259,7 +2259,7 @@ function parseVersion2(version) {
2259
2259
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2260
2260
  var init_version2 = __esm(() => {
2261
2261
  init_output();
2262
- currentVersion2 = "0.174.0".trim() ? "0.174.0".trim() : "0.1.0";
2262
+ currentVersion2 = "0.176.0".trim() ? "0.176.0".trim() : "0.1.0";
2263
2263
  });
2264
2264
 
2265
2265
  // ../../packages/registry/src/index.ts
@@ -3119,16 +3119,16 @@ var init_src = __esm(() => {
3119
3119
  weights: { status: "hidden" }
3120
3120
  },
3121
3121
  {
3122
- command: "fluncle admin backfills apple-releases",
3122
+ command: "fluncle admin backfills label-releases",
3123
3123
  exposedContent: [
3124
- "tap Apple Music's latest releases for the operator's enabled seed labels → day-one uncertified catalogue rows"
3124
+ "tap Spotify's fresh releases for the operator's enabled seed labels → day-one uncertified catalogue rows"
3125
3125
  ],
3126
3126
  kind: "cron",
3127
- name: "cron.apple-releases",
3128
- operatorNotes: "every 24h, run by a rave-02 HOST systemd timer (docs/agents/hermes/apple-releases-timer/). The MusicKit FRESHNESS TAP (D8): MusicBrainz WALKS the graph (cron.crawl) but lags a release ~2 weeks; Apple has it day one, so this taps each ENABLED seed label's latest releases and mints METADATA-ONLY catalogue rows (a `tracks` row with no `findings` row) with their real release dates — closing the /fresh lag cliff. It certifies nothing, publishes nothing, and never widens the graph (no new labels, no artist hops). Deduped against the MB crawl from both directions (ISRC + same-album title fold). Worker-paced (the three APPLE_MUSIC_* secrets live on the Worker); a NO-OP until they are provisioned. Zero LLM tokens. Source: docs/agents/hermes/scripts/apple-releases-sweep.*. See docs/catalogue-crawler.md.",
3127
+ name: "cron.label-releases",
3128
+ operatorNotes: "every 24h, run by a rave-02 HOST systemd timer (docs/agents/hermes/label-releases-timer/). The FRESHNESS TAP (D8): MusicBrainz WALKS the graph (cron.crawl) but lags a release ~2 weeks; Spotify has it day one, so this searches each ENABLED seed label's fresh releases (`label:\"<name>\" tag:new`, copyright-filtered — the fuzzy `label:` search is post-filtered on the album's ℗/© strings) and mints METADATA-ONLY catalogue rows (a `tracks` row with no `findings` row) with their real release dates — closing the /fresh lag cliff. It certifies nothing, publishes nothing, and never widens the graph (no new labels, no artist hops). Deduped against the MB crawl from both directions (Spotify id/uri/ISRC + same-album title fold). Worker-paced, reusing the publish path's Spotify OAuth (no new secret; Apple was a measured 2/99 seed-label dead end). Zero LLM tokens. Source: docs/agents/hermes/scripts/label-releases-sweep.*. See docs/catalogue-crawler.md.",
3129
3129
  probeConfig: {
3130
3130
  cadenceMs: 24 * 60 * MINUTE_MS,
3131
- cronName: "fluncle-apple-releases",
3131
+ cronName: "fluncle-label-releases",
3132
3132
  kind: "cron"
3133
3133
  },
3134
3134
  statusDescription: "taps day-one releases from the enabled labels",
@@ -4109,8 +4109,8 @@ __export(exports_admin_tracks, {
4109
4109
  captureQueueCommand: () => captureQueueCommand,
4110
4110
  backfillRecordingMbidsCommand: () => backfillRecordingMbidsCommand,
4111
4111
  backfillLastfmCommand: () => backfillLastfmCommand,
4112
+ backfillLabelReleasesCommand: () => backfillLabelReleasesCommand,
4112
4113
  backfillDiscogsCommand: () => backfillDiscogsCommand,
4113
- backfillAppleReleasesCommand: () => backfillAppleReleasesCommand,
4114
4114
  backfillAppleMusicCommand: () => backfillAppleMusicCommand,
4115
4115
  backfillAppleCatalogueCommand: () => backfillAppleCatalogueCommand
4116
4116
  });
@@ -4296,9 +4296,9 @@ async function backfillAppleCatalogueCommand(limit, dryRun) {
4296
4296
  const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
4297
4297
  return adminApiPost(`/api/admin/backfill/apple-catalogue?${params.toString()}`);
4298
4298
  }
4299
- async function backfillAppleReleasesCommand(limit, dryRun) {
4299
+ async function backfillLabelReleasesCommand(limit, dryRun) {
4300
4300
  const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
4301
- return adminApiPost(`/api/admin/backfill/apple-releases?${params.toString()}`);
4301
+ return adminApiPost(`/api/admin/backfill/label-releases?${params.toString()}`);
4302
4302
  }
4303
4303
  async function backfillRecordingMbidsCommand(limit, dryRun, cursor) {
4304
4304
  const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
@@ -9679,9 +9679,9 @@ function addAdminCommands(program2) {
9679
9679
  const { backfillAppleCatalogueCommand: backfillAppleCatalogueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
9680
9680
  await runBackfillAppleCatalogue(options, backfillAppleCatalogueCommand2);
9681
9681
  });
9682
- backfill.command("apple-releases").description("Tap Apple's latest releases for enabled seed labels into catalogue rows").option("--dry-run", "Report the labels that would be probed without any Apple call", false).option("--limit <limit>", "Max enabled seed labels to probe per pass", "5").option("--json", "Print JSON", false).action(async (options) => {
9683
- const { backfillAppleReleasesCommand: backfillAppleReleasesCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
9684
- await runBackfillAppleReleases(options, backfillAppleReleasesCommand2);
9682
+ backfill.command("label-releases").description("Tap Spotify's fresh releases for enabled seed labels into catalogue rows").option("--dry-run", "Report the labels that would be probed without any Spotify call", false).option("--limit <limit>", "Max enabled seed labels to probe per pass", "5").option("--json", "Print JSON", false).action(async (options) => {
9683
+ const { backfillLabelReleasesCommand: backfillLabelReleasesCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
9684
+ await runBackfillLabelReleases(options, backfillLabelReleasesCommand2);
9685
9685
  });
9686
9686
  backfill.command("artists").description("Back-fill the artist entity (artists + track_artists) for existing findings").option("--dry-run", "Report which findings would be upserted without touching the DB", false).option("--limit <limit>", "Max findings to process", "50").option("--json", "Print JSON", false).action(async (options) => {
9687
9687
  const { backfillArtistsCommand: backfillArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
@@ -10376,42 +10376,43 @@ async function runBackfillAppleCatalogue(options, backfillAppleCatalogueCommand2
10376
10376
  process.exitCode = 1;
10377
10377
  }
10378
10378
  }
10379
- async function runBackfillAppleReleases(options, backfillAppleReleasesCommand2) {
10379
+ async function runBackfillLabelReleases(options, backfillLabelReleasesCommand2) {
10380
10380
  const perPass = parseListLimit(options.limit);
10381
10381
  const newTrackIds = [];
10382
- const resolvedLabels = [];
10383
- const unresolvedLabels = [];
10382
+ const labelSlugs = [];
10383
+ const failedLabels = [];
10384
10384
  let dryRun = options.dryRun;
10385
10385
  let throttled = false;
10386
- let breakerTripped = false;
10387
10386
  let configured = true;
10388
10387
  let labelsProbed = 0;
10389
10388
  let albumsSeen = 0;
10389
+ let albumsMatched = 0;
10390
10390
  let newRows = 0;
10391
10391
  let skippedKnown = 0;
10392
+ let failedFetches = 0;
10393
+ let fetchCeilingHit = false;
10392
10394
  const MAX_PASSES = 100;
10393
10395
  for (let pass = 0;pass < MAX_PASSES; pass += 1) {
10394
- const result = await backfillAppleReleasesCommand2(perPass, options.dryRun);
10396
+ const result = await backfillLabelReleasesCommand2(perPass, options.dryRun);
10395
10397
  dryRun = result.dryRun;
10396
10398
  configured = result.configured;
10397
10399
  labelsProbed += result.labelsProbed;
10398
10400
  albumsSeen += result.albumsSeen;
10401
+ albumsMatched += result.albumsMatched;
10399
10402
  newRows += result.newRows;
10400
10403
  skippedKnown += result.skippedKnown;
10404
+ failedFetches += result.failedFetches;
10405
+ fetchCeilingHit = fetchCeilingHit || result.fetchCeilingHit;
10401
10406
  newTrackIds.push(...result.newTrackIds);
10402
- resolvedLabels.push(...result.resolvedLabels);
10403
- unresolvedLabels.push(...result.unresolvedLabels);
10407
+ labelSlugs.push(...result.labelSlugs);
10408
+ failedLabels.push(...result.failedLabels);
10404
10409
  if (!options.json) {
10405
10410
  const verb2 = result.dryRun ? "would probe" : "probed";
10406
- console.log(` \u2026${verb2} ${result.dryRun ? result.resolvedLabels.length : result.labelsProbed} label(s); ${result.newRows} new; ${result.skippedKnown} known; ${result.unresolvedLabels.length} unresolved`);
10411
+ console.log(` \u2026${verb2} ${result.labelSlugs.length} label(s); ${result.albumsMatched} matched album(s); ${result.newRows} new; ${result.skippedKnown} known; ${result.failedFetches} fetch-fail; ${result.failedLabels.length} search-fail`);
10407
10412
  }
10408
10413
  if (!result.configured) {
10409
10414
  break;
10410
10415
  }
10411
- if (result.breakerTripped) {
10412
- breakerTripped = true;
10413
- break;
10414
- }
10415
10416
  if (result.rateLimited) {
10416
10417
  throttled = true;
10417
10418
  break;
@@ -10419,35 +10420,40 @@ async function runBackfillAppleReleases(options, backfillAppleReleasesCommand2)
10419
10420
  if (result.dryRun) {
10420
10421
  break;
10421
10422
  }
10422
- if (result.labelsProbed === 0 && result.resolvedLabels.length === 0) {
10423
+ if (result.labelsProbed === 0) {
10423
10424
  break;
10424
10425
  }
10425
10426
  }
10426
10427
  if (options.json) {
10427
10428
  printSweepJson({
10429
+ albumsMatched,
10428
10430
  albumsSeen,
10429
- breakerTripped,
10430
10431
  configured,
10431
10432
  dryRun,
10433
+ failedFetches,
10434
+ failedLabels,
10435
+ fetchCeilingHit,
10436
+ labelSlugs,
10432
10437
  labelsProbed,
10433
10438
  newRows,
10434
10439
  newTrackIds,
10435
10440
  rateLimited: throttled,
10436
- resolvedLabels,
10437
- skippedKnown,
10438
- unresolvedLabels
10441
+ skippedKnown
10439
10442
  }, 0);
10440
10443
  return;
10441
10444
  }
10442
10445
  if (!configured) {
10443
- console.log("Apple releases tap is not configured (the Worker's MusicKit secrets are unset) \u2014 nothing probed.");
10446
+ console.log("Label-releases tap is a no-op \u2014 the Worker's Spotify grant is gone; reconnect Spotify.");
10444
10447
  return;
10445
10448
  }
10446
10449
  const verb = dryRun ? "Would probe" : "Probed";
10447
- const probedCount = dryRun ? resolvedLabels.length : labelsProbed;
10448
- console.log(`${verb} ${probedCount} enabled seed label(s); ${newRows} new catalogue row(s); ${skippedKnown} already known; ${resolvedLabels.length} resolved; ${unresolvedLabels.length} unresolved.`);
10449
- for (const slug of unresolvedLabels) {
10450
- console.log(` no exact Apple label match: ${slug}`);
10450
+ const probedCount = dryRun ? labelSlugs.length : labelsProbed;
10451
+ console.log(`${verb} ${probedCount} enabled seed label(s); ${albumsMatched} matched album(s); ${newRows} new catalogue row(s); ${skippedKnown} already known; ${failedFetches} fetch-fail; ${failedLabels.length} search-fail.`);
10452
+ if (fetchCeilingHit) {
10453
+ console.log(" (a pass ended early on the single-fetch ceiling \u2014 resumes next tick)");
10454
+ }
10455
+ for (const slug of failedLabels) {
10456
+ console.log(` search error: ${slug}`);
10451
10457
  }
10452
10458
  }
10453
10459
  async function runBackfillArtists(options, backfillArtistsCommand2) {
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.174.0"
34
+ "version": "0.176.0"
35
35
  }