fluncle 0.140.0 → 0.141.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 +91 -4
  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.140.0".trim() ? "0.140.0".trim() : "0.1.0";
564
+ currentVersion = "0.141.0".trim() ? "0.141.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.140.0".trim() ? "0.140.0".trim() : "0.1.0";
2192
+ currentVersion2 = "0.141.0".trim() ? "0.141.0".trim() : "0.1.0";
2193
2193
  });
2194
2194
 
2195
2195
  // ../../packages/registry/src/index.ts
@@ -2654,7 +2654,7 @@ var init_src = __esm(() => {
2654
2654
  exposedContent: ["the XML sitemap index of every public page"],
2655
2655
  kind: "discovery",
2656
2656
  name: "discovery.sitemap",
2657
- operatorNotes: "A sitemap INDEX, not a flat urlset: the URLs live in children at /sitemap/<kind>-<n>.xml (pages/findings/graph/logbook), each auto-paged under Google's 50,000-URL ceiling so a breach cannot happen rather than merely not having happened yet. robots.txt still names this one URL — a crawler discovers the children from here. Splitting by kind is also the diagnostic: Search Console reports coverage PER sitemap.",
2657
+ operatorNotes: "A sitemap INDEX, not a flat urlset: the URLs live in children at /sitemap/<kind>-<n>.xml, ONE CHILD PER ENTITY TYPE (pages/findings/artists/labels/albums/galaxies/logbook), each auto-paged under Google's 50,000-URL ceiling so a breach cannot happen rather than merely not having happened yet. robots.txt still names this one URL — a crawler discovers the children from here. Splitting per entity type is also the diagnostic: Search Console reports coverage PER sitemap, so each entity type gets its own submitted/indexed count.",
2658
2658
  probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2659
2659
  route: "/sitemap.xml",
2660
2660
  url: `${SITE}/sitemap.xml`,
@@ -2662,7 +2662,9 @@ var init_src = __esm(() => {
2662
2662
  },
2663
2663
  {
2664
2664
  apiFormat: "application/xml",
2665
- exposedContent: ["one child sitemap: the pages / findings / graph / logbook URLs, auto-paged"],
2665
+ exposedContent: [
2666
+ "one child sitemap, per entity type: the pages / findings / artists / labels / albums / galaxies / logbook URLs, auto-paged"
2667
+ ],
2666
2668
  kind: "discovery",
2667
2669
  name: "discovery.sitemap-shard",
2668
2670
  probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
@@ -2956,6 +2958,17 @@ var init_src = __esm(() => {
2956
2958
  probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-label-images", kind: "cron" },
2957
2959
  weights: { status: "hidden" }
2958
2960
  },
2961
+ {
2962
+ command: "fluncle admin backfills cover-masters",
2963
+ exposedContent: [
2964
+ "resolve each pending album/artist its OWN ≤1200² cover master (best source wins) → its own R2 image"
2965
+ ],
2966
+ kind: "cron",
2967
+ name: "cron.cover-masters",
2968
+ operatorNotes: "every 60m, run by a rave-02 HOST systemd timer (docs/agents/hermes/cover-masters-timer/). The DURABLE other half of the album/artist cover (RFC musickit-second-authority U3b): the publish path + catalogue crawl MINT albums/artists (each `image_state='pending'`) and this cron gives each its OWN ≤1200²-capped cover derivative in R2 (found.fluncle.com, `albums/<slug>.<ext>` / `artists/<slug>.<ext>`) instead of hotlinking a third party — the label-logo posture, two entities over. IMAGE ONLY — a downscaled display derivative, reversible, the REF-05-conscious 1200 line (docs/album-artwork.md); it certifies nothing and publishes nothing (agent tier, the `backfill_label_images` precedent). Worker-paced: one bounded batch of albums (Apple template → Cover Art Archive → Spotify floor), then one of artists (Spotify floor), per tick; every rung requests a ≤1200 rendition and a byte read enforces the cap before the R2 put, so no un-downscaled original is ever stored. The `albums`/`artists` row carries the durable reliability state (image_state/image_attempted_at/image_failures), so a resolved/none entity is terminal. Served via Cloudflare Images `/cdn-cgi/image/…` (decision B). Zero LLM tokens. Source: docs/agents/hermes/scripts/cover-masters-sweep.*. See docs/album-artwork.md.",
2969
+ probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-cover-masters", kind: "cron" },
2970
+ weights: { status: "hidden" }
2971
+ },
2959
2972
  {
2960
2973
  command: "fluncle admin tracks capture-audio --queue",
2961
2974
  exposedContent: [
@@ -5792,6 +5805,22 @@ var init_admin_labels = __esm(() => {
5792
5805
  init_api();
5793
5806
  });
5794
5807
 
5808
+ // src/commands/admin-covers.ts
5809
+ var exports_admin_covers = {};
5810
+ __export(exports_admin_covers, {
5811
+ backfillCoverMastersCommand: () => backfillCoverMastersCommand
5812
+ });
5813
+ async function backfillCoverMastersCommand(kind, limit, dryRun, cursor) {
5814
+ const params = new URLSearchParams({ dryRun: String(dryRun), kind, limit: String(limit) });
5815
+ if (cursor) {
5816
+ params.set("cursor", cursor);
5817
+ }
5818
+ return adminApiPost(`/api/admin/backfill/cover-masters?${params.toString()}`);
5819
+ }
5820
+ var init_admin_covers = __esm(() => {
5821
+ init_api();
5822
+ });
5823
+
5795
5824
  // src/commands/preview-migration.ts
5796
5825
  var exports_preview_migration = {};
5797
5826
  __export(exports_preview_migration, {
@@ -8829,6 +8858,10 @@ function addAdminCommands(program2) {
8829
8858
  const { backfillLabelImagesCommand: backfillLabelImagesCommand2 } = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
8830
8859
  await runBackfillLabelImages(options, backfillLabelImagesCommand2);
8831
8860
  });
8861
+ backfill.command("cover-masters").description("Resolve owned \u22641200\xB2 cover masters (album/artist) into R2").option("--kind <kind>", "Which entity to drain: album | artist", "album").option("--dry-run", "Report the eligible worklist without any fetch or write", false).option("--limit <limit>", "Max entities to process", "50").option("--json", "Print JSON", false).action(async (options) => {
8862
+ const { backfillCoverMastersCommand: backfillCoverMastersCommand2 } = await Promise.resolve().then(() => (init_admin_covers(), exports_admin_covers));
8863
+ await runBackfillCoverMasters(options, backfillCoverMastersCommand2);
8864
+ });
8832
8865
  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) => {
8833
8866
  if (options.queue) {
8834
8867
  const { listArtistsCommand: listArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
@@ -9461,6 +9494,60 @@ async function runBackfillArtistImages(options, backfillArtistImagesCommand2) {
9461
9494
  process.exitCode = 1;
9462
9495
  }
9463
9496
  }
9497
+ async function runBackfillCoverMasters(options, backfillCoverMastersCommand2) {
9498
+ const limit = parseListLimit(options.limit);
9499
+ const kind = options.kind === "artist" ? "artist" : "album";
9500
+ const resolved = [];
9501
+ const none = [];
9502
+ const failed = [];
9503
+ let cursor;
9504
+ let dryRun = options.dryRun;
9505
+ while (resolved.length + none.length + failed.length < limit) {
9506
+ const remaining = limit - (resolved.length + none.length + failed.length);
9507
+ const result = await backfillCoverMastersCommand2(kind, remaining, options.dryRun, cursor);
9508
+ dryRun = result.dryRun;
9509
+ resolved.push(...result.resolved);
9510
+ none.push(...result.none);
9511
+ failed.push(...result.failed);
9512
+ if (!options.json) {
9513
+ const verb2 = result.dryRun ? "would resolve" : "resolved";
9514
+ console.log(` \u2026${verb2} ${result.resolvedCount}; ${result.noneCount} without a source; ${result.failedCount} failed`);
9515
+ }
9516
+ if (result.nextCursor === null) {
9517
+ break;
9518
+ }
9519
+ cursor = result.nextCursor;
9520
+ }
9521
+ if (options.json) {
9522
+ printJson({
9523
+ dryRun,
9524
+ failed,
9525
+ failedCount: failed.length,
9526
+ kind,
9527
+ none,
9528
+ noneCount: none.length,
9529
+ ok: true,
9530
+ rateLimited: false,
9531
+ resolved,
9532
+ resolvedCount: resolved.length
9533
+ });
9534
+ if (failed.length > 0) {
9535
+ process.exitCode = 1;
9536
+ }
9537
+ return;
9538
+ }
9539
+ const verb = dryRun ? "Would resolve" : "Resolved";
9540
+ console.log(`${verb} ${resolved.length} ${kind} cover master(s); ${none.length} without a source; ${failed.length} failed.`);
9541
+ for (const slug of resolved) {
9542
+ console.log(` ${slug}`);
9543
+ }
9544
+ for (const item of failed) {
9545
+ console.log(` ${item.slug}: ${item.error}`);
9546
+ }
9547
+ if (failed.length > 0) {
9548
+ process.exitCode = 1;
9549
+ }
9550
+ }
9464
9551
  async function runBackfillLabelImages(options, backfillLabelImagesCommand2) {
9465
9552
  const limit = parseListLimit(options.limit);
9466
9553
  const resolved = [];
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.140.0"
34
+ "version": "0.141.0"
35
35
  }