fluncle 0.162.0 → 0.164.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.
- package/bin/fluncle.mjs +99 -5
- 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.
|
|
564
|
+
currentVersion = "0.164.0".trim() ? "0.164.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.
|
|
2192
|
+
currentVersion2 = "0.164.0".trim() ? "0.164.0".trim() : "0.1.0";
|
|
2193
2193
|
});
|
|
2194
2194
|
|
|
2195
2195
|
// ../../packages/registry/src/index.ts
|
|
@@ -2513,6 +2513,18 @@ var init_src = __esm(() => {
|
|
|
2513
2513
|
url: `${SITE}/api/v1/tracks/random`,
|
|
2514
2514
|
weights: { web: "secondary" }
|
|
2515
2515
|
},
|
|
2516
|
+
{
|
|
2517
|
+
apiFormat: "application/json",
|
|
2518
|
+
discoveryUrl: `${SITE}/api/v1/openapi.json`,
|
|
2519
|
+
exposedContent: [
|
|
2520
|
+
"what just came out — newest drum & bass releases over a 30-day window, flat (limit max 100)"
|
|
2521
|
+
],
|
|
2522
|
+
kind: "api",
|
|
2523
|
+
name: "api.fresh",
|
|
2524
|
+
route: "/api/v1/tracks/fresh",
|
|
2525
|
+
url: `${SITE}/api/v1/tracks/fresh`,
|
|
2526
|
+
weights: { web: "secondary" }
|
|
2527
|
+
},
|
|
2516
2528
|
{
|
|
2517
2529
|
apiFormat: "application/json",
|
|
2518
2530
|
discoveryUrl: `${SITE}/api/v1/openapi.json`,
|
|
@@ -3024,6 +3036,19 @@ var init_src = __esm(() => {
|
|
|
3024
3036
|
title: "Label logos",
|
|
3025
3037
|
weights: { status: "hidden" }
|
|
3026
3038
|
},
|
|
3039
|
+
{
|
|
3040
|
+
command: "fluncle admin backfills recording-mbids",
|
|
3041
|
+
exposedContent: [
|
|
3042
|
+
"fill each track's canonical MusicBrainz recording MBID (crawler PK strip + ISRC resolve)"
|
|
3043
|
+
],
|
|
3044
|
+
kind: "cron",
|
|
3045
|
+
name: "cron.recording-mbids",
|
|
3046
|
+
operatorNotes: "every 60m, run by a rave-02 HOST systemd timer (docs/agents/hermes/recording-mbids-timer/). The MusicBrainz identity layer: gives every track its canonical MusicBrainz recording MBID — the one identifier that reconciles a track to the wider open music graph (MusicBrainz, Wikidata) and the anchor the `/log` MusicRecording emits as a `sameAs` + a KG `identifier`. Two fill paths: a FREE SQL strip of crawler-born rows' PK (`mb_<recording-mbid>` → the `mb_recording_id` column, no vendor call), then an ISRC→recording resolve of the findings/Spotify-born tail through the shared MusicBrainz client (`/isrc/<isrc>`). New crawler rows already carry the MBID at mint time, so this cron catches history up + drains the ISRC tail. METADATA IDENTITY ONLY — it certifies nothing and publishes nothing (agent tier, the `backfill_label_images` precedent). Worker-paced (the box holds no MusicBrainz budget): one bounded batch per tick, 1 req/s, circuit-broken on a throttle. The `tracks` row carries the durable reliability state (`mb_recording_id` + the `mb_recording_id_attempted_at` stamp, a miss stamped so it is not re-queried forever). Zero LLM tokens. Source: docs/agents/hermes/scripts/recording-mbids-sweep.*. See docs/catalogue-crawler.md.",
|
|
3047
|
+
probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-recording-mbids", kind: "cron" },
|
|
3048
|
+
statusDescription: "fills each track's MusicBrainz recording id",
|
|
3049
|
+
title: "Recording MBIDs",
|
|
3050
|
+
weights: { status: "hidden" }
|
|
3051
|
+
},
|
|
3027
3052
|
{
|
|
3028
3053
|
command: "fluncle admin backfills cover-masters",
|
|
3029
3054
|
exposedContent: [
|
|
@@ -3879,6 +3904,7 @@ __export(exports_admin_tracks, {
|
|
|
3879
3904
|
embedQueueCommand: () => embedQueueCommand,
|
|
3880
3905
|
contextQueueCommand: () => contextQueueCommand,
|
|
3881
3906
|
captureQueueCommand: () => captureQueueCommand,
|
|
3907
|
+
backfillRecordingMbidsCommand: () => backfillRecordingMbidsCommand,
|
|
3882
3908
|
backfillLastfmCommand: () => backfillLastfmCommand,
|
|
3883
3909
|
backfillDiscogsCommand: () => backfillDiscogsCommand,
|
|
3884
3910
|
backfillAppleMusicCommand: () => backfillAppleMusicCommand,
|
|
@@ -4066,6 +4092,13 @@ async function backfillAppleCatalogueCommand(limit, dryRun) {
|
|
|
4066
4092
|
const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
|
|
4067
4093
|
return adminApiPost(`/api/admin/backfill/apple-catalogue?${params.toString()}`);
|
|
4068
4094
|
}
|
|
4095
|
+
async function backfillRecordingMbidsCommand(limit, dryRun, cursor) {
|
|
4096
|
+
const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
|
|
4097
|
+
if (cursor) {
|
|
4098
|
+
params.set("cursor", cursor);
|
|
4099
|
+
}
|
|
4100
|
+
return adminApiPost(`/api/admin/backfill/recording-mbids?${params.toString()}`);
|
|
4101
|
+
}
|
|
4069
4102
|
async function vehiclesCommand(limit) {
|
|
4070
4103
|
const tracks = await fetchAdminTracks({ hasVideo: true, max: limit, order: "desc" });
|
|
4071
4104
|
return tracks.map((track) => ({
|
|
@@ -5320,7 +5353,7 @@ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:f
|
|
|
5320
5353
|
async function logbookGapsCommand(limit) {
|
|
5321
5354
|
const query = typeof limit === "number" ? `?limit=${encodeURIComponent(String(limit))}` : "";
|
|
5322
5355
|
const response = await adminApiGet(`/api/admin/logbook/gaps${query}`);
|
|
5323
|
-
return response.gaps;
|
|
5356
|
+
return { gaps: response.gaps, spent: response.spent };
|
|
5324
5357
|
}
|
|
5325
5358
|
function resolveBody(options) {
|
|
5326
5359
|
if (options.body !== undefined) {
|
|
@@ -9400,6 +9433,10 @@ function addAdminCommands(program2) {
|
|
|
9400
9433
|
const { backfillLabelImagesCommand: backfillLabelImagesCommand2 } = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
|
|
9401
9434
|
await runBackfillLabelImages(options, backfillLabelImagesCommand2);
|
|
9402
9435
|
});
|
|
9436
|
+
backfill.command("recording-mbids").description("Fill MusicBrainz recording MBIDs (crawler PK strip + ISRC resolve) over tracks").option("--dry-run", "Report the eligible worklist without any vendor call or write", false).option("--limit <limit>", "Max ISRC lookups to process", "50").option("--json", "Print JSON", false).action(async (options) => {
|
|
9437
|
+
const { backfillRecordingMbidsCommand: backfillRecordingMbidsCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
9438
|
+
await runBackfillRecordingMbids(options, backfillRecordingMbidsCommand2);
|
|
9439
|
+
});
|
|
9403
9440
|
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) => {
|
|
9404
9441
|
const { backfillCoverMastersCommand: backfillCoverMastersCommand2 } = await Promise.resolve().then(() => (init_admin_covers(), exports_admin_covers));
|
|
9405
9442
|
await runBackfillCoverMasters(options, backfillCoverMastersCommand2);
|
|
@@ -10251,6 +10288,63 @@ async function runBackfillLabelImages(options, backfillLabelImagesCommand2) {
|
|
|
10251
10288
|
process.exitCode = 1;
|
|
10252
10289
|
}
|
|
10253
10290
|
}
|
|
10291
|
+
async function runBackfillRecordingMbids(options, backfillRecordingMbidsCommand2) {
|
|
10292
|
+
const limit = parseListLimit(options.limit);
|
|
10293
|
+
const resolved = [];
|
|
10294
|
+
const missed = [];
|
|
10295
|
+
const failed = [];
|
|
10296
|
+
let cursor;
|
|
10297
|
+
let dryRun = options.dryRun;
|
|
10298
|
+
let prefixStripped = 0;
|
|
10299
|
+
let throttled = false;
|
|
10300
|
+
while (resolved.length + missed.length + failed.length < limit) {
|
|
10301
|
+
const remaining = limit - (resolved.length + missed.length + failed.length);
|
|
10302
|
+
const result = await backfillRecordingMbidsCommand2(remaining, options.dryRun, cursor);
|
|
10303
|
+
dryRun = result.dryRun;
|
|
10304
|
+
prefixStripped += result.prefixStripped;
|
|
10305
|
+
resolved.push(...result.resolved);
|
|
10306
|
+
missed.push(...result.missed);
|
|
10307
|
+
failed.push(...result.failed);
|
|
10308
|
+
if (!options.json) {
|
|
10309
|
+
const verb2 = result.dryRun ? "would resolve" : "resolved";
|
|
10310
|
+
console.log(` \u2026stripped ${result.prefixStripped} from PK; ${verb2} ${result.resolvedCount} by ISRC; ${result.missedCount} not in MusicBrainz; ${result.failedCount} failed`);
|
|
10311
|
+
}
|
|
10312
|
+
if (result.rateLimited) {
|
|
10313
|
+
throttled = true;
|
|
10314
|
+
break;
|
|
10315
|
+
}
|
|
10316
|
+
if (result.nextCursor === null) {
|
|
10317
|
+
break;
|
|
10318
|
+
}
|
|
10319
|
+
cursor = result.nextCursor;
|
|
10320
|
+
}
|
|
10321
|
+
if (options.json) {
|
|
10322
|
+
printJson({
|
|
10323
|
+
dryRun,
|
|
10324
|
+
failed,
|
|
10325
|
+
failedCount: failed.length,
|
|
10326
|
+
missed,
|
|
10327
|
+
missedCount: missed.length,
|
|
10328
|
+
ok: true,
|
|
10329
|
+
prefixStripped,
|
|
10330
|
+
rateLimited: throttled,
|
|
10331
|
+
resolved,
|
|
10332
|
+
resolvedCount: resolved.length
|
|
10333
|
+
});
|
|
10334
|
+
if (failed.length > 0) {
|
|
10335
|
+
process.exitCode = 1;
|
|
10336
|
+
}
|
|
10337
|
+
return;
|
|
10338
|
+
}
|
|
10339
|
+
const verb = dryRun ? "Would fill" : "Filled";
|
|
10340
|
+
console.log(`${verb} ${prefixStripped} recording MBID(s) from crawler PKs; ${resolved.length} by ISRC; ${missed.length} not in MusicBrainz; ${failed.length} failed.`);
|
|
10341
|
+
for (const item of failed) {
|
|
10342
|
+
console.log(` ${item.trackId}: ${item.error}`);
|
|
10343
|
+
}
|
|
10344
|
+
if (failed.length > 0) {
|
|
10345
|
+
process.exitCode = 1;
|
|
10346
|
+
}
|
|
10347
|
+
}
|
|
10254
10348
|
async function runArtistResolveQueue(options, listArtistsCommand2) {
|
|
10255
10349
|
const limit = parseListLimit(options.limit);
|
|
10256
10350
|
const result = await listArtistsCommand2(limit ?? 50);
|
|
@@ -10866,9 +10960,9 @@ async function runNewsletterList(options, newsletterListCommand2) {
|
|
|
10866
10960
|
}
|
|
10867
10961
|
async function runLogbookGaps(options, logbookGapsCommand2) {
|
|
10868
10962
|
const limit = options.limit === undefined ? undefined : Number.parseInt(options.limit, 10);
|
|
10869
|
-
const gaps = await logbookGapsCommand2(limit !== undefined && Number.isFinite(limit) ? limit : undefined);
|
|
10963
|
+
const { gaps, spent } = await logbookGapsCommand2(limit !== undefined && Number.isFinite(limit) ? limit : undefined);
|
|
10870
10964
|
if (options.json) {
|
|
10871
|
-
printJson({ gaps, ok: true });
|
|
10965
|
+
printJson({ gaps, ok: true, spent });
|
|
10872
10966
|
return;
|
|
10873
10967
|
}
|
|
10874
10968
|
if (gaps.length === 0) {
|
package/package.json
CHANGED