fluncle 0.136.0 → 0.138.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 +160 -5
  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.136.0".trim() ? "0.136.0".trim() : "0.1.0";
564
+ currentVersion = "0.138.0".trim() ? "0.138.0".trim() : "0.1.0";
565
565
  });
566
566
 
567
567
  // src/update-notifier.ts
@@ -2189,7 +2189,7 @@ function parseVersion2(version) {
2189
2189
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2190
2190
  var init_version2 = __esm(() => {
2191
2191
  init_output();
2192
- currentVersion2 = "0.136.0".trim() ? "0.136.0".trim() : "0.1.0";
2192
+ currentVersion2 = "0.138.0".trim() ? "0.138.0".trim() : "0.1.0";
2193
2193
  });
2194
2194
 
2195
2195
  // ../../packages/registry/src/index.ts
@@ -2247,11 +2247,11 @@ var init_src = __esm(() => {
2247
2247
  },
2248
2248
  {
2249
2249
  exposedContent: [
2250
- "the mixability set-builderchain a set from Fluncle's findings that mixes clean, share it as a link"
2250
+ "the free mixing tool name artists you like, and Fluncle ranks what mixes in clean next from the whole archive; chain a set and share it as a link"
2251
2251
  ],
2252
2252
  kind: "web_route",
2253
2253
  name: "web.mix",
2254
- operatorNotes: "ADMIN-GATED at launch (RFC mixability-engine, Decision 1): the full /mix set-builder ships behind admin auth until the archive clears the ~250-finding floor. Registered here as `pending` (dark to every public consumer — the dev-row, llms.txt, the sitemap, /status) so going public is one flip: remove `pending` + set a web weight + announce. The `list_mixable_tracks` API op already ships at its final public tier, so the flip needs no rebuild. No probeConfig — the admin gate 302s a bare GET to /admin/login, which would read as a false 'down'.",
2254
+ operatorNotes: "PUBLIC-CAPABLE, gated by a SELF-LIFTING DEPTH MEASUREMENT (not admin auth). The old ~250-finding / admin gate is gone: `/mix` now measures the live archive on every load (`getMixChainDepth` — can the median track reach a full set + rail by a named harmonic move?) and opens to the world on its own the day the catalogue lands enough keyed depth. Until then a stranger is redirected home and the operator still gets in to dogfood. This entry stays `pending` (dark to the dev-row, llms.txt, the sitemap, /status) ONLY so we do not advertise a URL that still redirects the flip is: confirm the depth gate has opened in prod, remove `pending`, announce. The web weight is pre-set, so the flip needs no other change. No probeConfig — a closed gate 302s a bare GET, which would read as a false 'down'.",
2255
2255
  pending: true,
2256
2256
  route: "/mix",
2257
2257
  url: `${SITE}/mix`,
@@ -3654,7 +3654,8 @@ __export(exports_admin_tracks, {
3654
3654
  contextQueueCommand: () => contextQueueCommand,
3655
3655
  captureQueueCommand: () => captureQueueCommand,
3656
3656
  backfillLastfmCommand: () => backfillLastfmCommand,
3657
- backfillDiscogsCommand: () => backfillDiscogsCommand
3657
+ backfillDiscogsCommand: () => backfillDiscogsCommand,
3658
+ backfillAppleMusicCommand: () => backfillAppleMusicCommand
3658
3659
  });
3659
3660
  async function fetchAdminTracks(options) {
3660
3661
  const {
@@ -3827,6 +3828,13 @@ async function backfillDiscogsCommand(limit, dryRun, cursor) {
3827
3828
  }
3828
3829
  return adminApiPost(`/api/admin/backfill/discogs?${params.toString()}`);
3829
3830
  }
3831
+ async function backfillAppleMusicCommand(limit, dryRun, cursor) {
3832
+ const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
3833
+ if (cursor) {
3834
+ params.set("cursor", cursor);
3835
+ }
3836
+ return adminApiPost(`/api/admin/backfill/apple-music?${params.toString()}`);
3837
+ }
3830
3838
  async function vehiclesCommand(limit) {
3831
3839
  const tracks = await fetchAdminTracks({ hasVideo: true, max: limit, order: "desc" });
3832
3840
  return tracks.map((track) => ({
@@ -5746,6 +5754,22 @@ var init_admin_artists = __esm(() => {
5746
5754
  init_api();
5747
5755
  });
5748
5756
 
5757
+ // src/commands/admin-labels.ts
5758
+ var exports_admin_labels = {};
5759
+ __export(exports_admin_labels, {
5760
+ backfillLabelImagesCommand: () => backfillLabelImagesCommand
5761
+ });
5762
+ async function backfillLabelImagesCommand(limit, dryRun, cursor) {
5763
+ const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
5764
+ if (cursor) {
5765
+ params.set("cursor", cursor);
5766
+ }
5767
+ return adminApiPost(`/api/admin/backfill/label-images?${params.toString()}`);
5768
+ }
5769
+ var init_admin_labels = __esm(() => {
5770
+ init_api();
5771
+ });
5772
+
5749
5773
  // src/commands/preview-migration.ts
5750
5774
  var exports_preview_migration = {};
5751
5775
  __export(exports_preview_migration, {
@@ -8754,6 +8778,10 @@ function addAdminCommands(program2) {
8754
8778
  const { backfillDiscogsCommand: backfillDiscogsCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
8755
8779
  await runBackfillDiscogs(options, backfillDiscogsCommand2);
8756
8780
  });
8781
+ backfill.command("apple-music").description("Resolve missing Apple Music URLs for published findings (exact ISRC match)").option("--dry-run", "Report the eligible set without resolving or writing", false).option("--limit <limit>", "Max findings to resolve", "50").option("--json", "Print JSON", false).action(async (options) => {
8782
+ const { backfillAppleMusicCommand: backfillAppleMusicCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
8783
+ await runBackfillAppleMusic(options, backfillAppleMusicCommand2);
8784
+ });
8757
8785
  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) => {
8758
8786
  const { backfillArtistsCommand: backfillArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
8759
8787
  await runBackfillArtists(options, backfillArtistsCommand2);
@@ -8762,6 +8790,10 @@ function addAdminCommands(program2) {
8762
8790
  const { backfillArtistImagesCommand: backfillArtistImagesCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
8763
8791
  await runBackfillArtistImages(options, backfillArtistImagesCommand2);
8764
8792
  });
8793
+ backfill.command("label-images").description("Resolve label logos (Discogs \u2192 Wikidata) into R2 for existing labels").option("--dry-run", "Report the eligible worklist without any vendor call or write", false).option("--limit <limit>", "Max labels to process", "50").option("--json", "Print JSON", false).action(async (options) => {
8794
+ const { backfillLabelImagesCommand: backfillLabelImagesCommand2 } = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
8795
+ await runBackfillLabelImages(options, backfillLabelImagesCommand2);
8796
+ });
8765
8797
  artists.command("resolve").description("Resolve an artist's social identity (MB url-rels + Firecrawl gap-fill)").argument("[artistId]").option("--queue", "Show the resolve worklist (artists awaiting resolution), oldest first", false).option("--limit <limit>", "Number of artists to show with --queue", "50").option("--json", "Print JSON", false).allowExcessArguments().action(async (artistId, options) => {
8766
8798
  if (options.queue) {
8767
8799
  const { listArtistsCommand: listArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
@@ -9161,6 +9193,72 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
9161
9193
  console.log(` ${item.logId}: release ${item.releaseId}${master}`);
9162
9194
  }
9163
9195
  }
9196
+ async function runBackfillAppleMusic(options, backfillAppleMusicCommand2) {
9197
+ const limit = parseListLimit(options.limit);
9198
+ const resolved = [];
9199
+ const unresolved = [];
9200
+ const failed = [];
9201
+ const skipped = [];
9202
+ let cursor;
9203
+ let dryRun = options.dryRun;
9204
+ let throttled = false;
9205
+ let configured = true;
9206
+ while (resolved.length + unresolved.length + failed.length < limit) {
9207
+ const remaining = limit - (resolved.length + unresolved.length + failed.length);
9208
+ const result = await backfillAppleMusicCommand2(remaining, options.dryRun, cursor);
9209
+ dryRun = result.dryRun;
9210
+ configured = result.configured;
9211
+ resolved.push(...result.resolved);
9212
+ unresolved.push(...result.unresolved);
9213
+ failed.push(...result.failed);
9214
+ skipped.push(...result.skipped);
9215
+ if (!options.json) {
9216
+ const verb2 = result.dryRun ? "would resolve" : "resolved";
9217
+ console.log(` \u2026${verb2} ${result.resolvedCount}; ${result.unresolvedCount} unresolved; ${result.failedCount} failed; ${result.skippedCount} skipped`);
9218
+ }
9219
+ if (!result.configured) {
9220
+ break;
9221
+ }
9222
+ if (result.rateLimited) {
9223
+ throttled = true;
9224
+ break;
9225
+ }
9226
+ if (result.nextCursor === null) {
9227
+ break;
9228
+ }
9229
+ cursor = result.nextCursor;
9230
+ }
9231
+ if (options.json) {
9232
+ printSweepJson({
9233
+ configured,
9234
+ dryRun,
9235
+ failed,
9236
+ rateLimited: throttled,
9237
+ resolved,
9238
+ resolvedCount: resolved.length,
9239
+ skipped,
9240
+ skippedCount: skipped.length,
9241
+ unresolved,
9242
+ unresolvedCount: unresolved.length
9243
+ }, failed.length);
9244
+ return;
9245
+ }
9246
+ if (!configured) {
9247
+ console.log("Apple Music backfill is not configured (the Worker's MusicKit secrets are unset) \u2014 nothing resolved.");
9248
+ return;
9249
+ }
9250
+ const verb = dryRun ? "Would resolve" : "Resolved";
9251
+ console.log(`${verb} ${resolved.length} Apple Music URL(s); ${unresolved.length} unresolved; ${failed.length} failed; ${skipped.length} skipped.`);
9252
+ for (const item of resolved) {
9253
+ console.log(` ${item.logId}: ${item.url}`);
9254
+ }
9255
+ for (const item of failed) {
9256
+ console.log(` ${item.logId}: ${item.error}`);
9257
+ }
9258
+ if (failed.length > 0) {
9259
+ process.exitCode = 1;
9260
+ }
9261
+ }
9164
9262
  async function runBackfillArtists(options, backfillArtistsCommand2) {
9165
9263
  const limit = parseListLimit(options.limit);
9166
9264
  const upserted = [];
@@ -9253,6 +9351,63 @@ async function runBackfillArtistImages(options, backfillArtistImagesCommand2) {
9253
9351
  process.exitCode = 1;
9254
9352
  }
9255
9353
  }
9354
+ async function runBackfillLabelImages(options, backfillLabelImagesCommand2) {
9355
+ const limit = parseListLimit(options.limit);
9356
+ const resolved = [];
9357
+ const none = [];
9358
+ const failed = [];
9359
+ let cursor;
9360
+ let dryRun = options.dryRun;
9361
+ let throttled = false;
9362
+ while (resolved.length + none.length + failed.length < limit) {
9363
+ const remaining = limit - (resolved.length + none.length + failed.length);
9364
+ const result = await backfillLabelImagesCommand2(remaining, options.dryRun, cursor);
9365
+ dryRun = result.dryRun;
9366
+ resolved.push(...result.resolved);
9367
+ none.push(...result.none);
9368
+ failed.push(...result.failed);
9369
+ if (!options.json) {
9370
+ const verb2 = result.dryRun ? "would resolve" : "resolved";
9371
+ console.log(` \u2026${verb2} ${result.resolvedCount}; ${result.noneCount} without an image; ${result.failedCount} failed`);
9372
+ }
9373
+ if (result.rateLimited) {
9374
+ throttled = true;
9375
+ break;
9376
+ }
9377
+ if (result.nextCursor === null) {
9378
+ break;
9379
+ }
9380
+ cursor = result.nextCursor;
9381
+ }
9382
+ if (options.json) {
9383
+ printJson({
9384
+ dryRun,
9385
+ failed,
9386
+ failedCount: failed.length,
9387
+ none,
9388
+ noneCount: none.length,
9389
+ ok: true,
9390
+ rateLimited: throttled,
9391
+ resolved,
9392
+ resolvedCount: resolved.length
9393
+ });
9394
+ if (failed.length > 0) {
9395
+ process.exitCode = 1;
9396
+ }
9397
+ return;
9398
+ }
9399
+ const verb = dryRun ? "Would resolve" : "Resolved";
9400
+ console.log(`${verb} ${resolved.length} label logo(s); ${none.length} without an image; ${failed.length} failed.`);
9401
+ for (const slug of resolved) {
9402
+ console.log(` ${slug}`);
9403
+ }
9404
+ for (const item of failed) {
9405
+ console.log(` ${item.slug}: ${item.error}`);
9406
+ }
9407
+ if (failed.length > 0) {
9408
+ process.exitCode = 1;
9409
+ }
9410
+ }
9256
9411
  async function runArtistResolveQueue(options, listArtistsCommand2) {
9257
9412
  const limit = parseListLimit(options.limit);
9258
9413
  const result = await listArtistsCommand2(limit ?? 50);
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.136.0"
34
+ "version": "0.138.0"
35
35
  }