fluncle 0.178.0 → 0.180.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 +7 -100
- 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.180.0".trim() ? "0.180.0".trim() : "0.1.0";
|
|
565
565
|
});
|
|
566
566
|
|
|
567
567
|
// src/update-notifier.ts
|
|
@@ -2260,7 +2260,7 @@ function parseVersion2(version) {
|
|
|
2260
2260
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2261
2261
|
var init_version2 = __esm(() => {
|
|
2262
2262
|
init_output();
|
|
2263
|
-
currentVersion2 = "0.
|
|
2263
|
+
currentVersion2 = "0.180.0".trim() ? "0.180.0".trim() : "0.1.0";
|
|
2264
2264
|
});
|
|
2265
2265
|
|
|
2266
2266
|
// ../../packages/registry/src/index.ts
|
|
@@ -3121,13 +3121,12 @@ var init_src = __esm(() => {
|
|
|
3121
3121
|
weights: { status: "hidden" }
|
|
3122
3122
|
},
|
|
3123
3123
|
{
|
|
3124
|
-
command: "fluncle admin backfills label-releases",
|
|
3125
3124
|
exposedContent: [
|
|
3126
|
-
"tap
|
|
3125
|
+
"tap day-one fresh releases for the operator's enabled seed labels → uncertified catalogue rows"
|
|
3127
3126
|
],
|
|
3128
3127
|
kind: "cron",
|
|
3129
3128
|
name: "cron.label-releases",
|
|
3130
|
-
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
|
|
3129
|
+
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 mints METADATA-ONLY catalogue rows (a `tracks` row with no `findings` row) for each ENABLED seed label's fresh releases with their real dates — closing the /fresh lag cliff. Runs OFF the official (dev-mode) Spotify app, which is rate-limited to death at its tier and must stay for user paths: the box reads the due seed labels (`list_label_releases_work`), runs an Apify Spotify-scraper actor per label (`label:\"<name>\" tag:new`), maps each result album (inline tracks[] + ISRCs + artists[]) to a candidate, and POSTs them to the agent-tier `backfill_label_releases` op — where the WORKER re-runs the FULL gate (the box's verdict is never trusted) and mints. The gate: artist-grounding (an album's Spotify artist already in `artists.spotify_artist_id` — the PRIMARY anchor that stops cross-genre homonym junk, always required) AND, when the actor gives a label signal (album_label/copyright — both null in albums-search mode, so grounding-only there), an exact fold-match to the seed name. It certifies nothing, publishes nothing, 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). Reuses the anchor sweep's APIFY_API_TOKEN (no new secret). Calls the oRPC HTTP endpoints directly (no CLI command the pinned box CLI would lack). Zero LLM tokens. Source: docs/agents/hermes/scripts/label-releases-sweep.*. See docs/catalogue-crawler.md.",
|
|
3131
3130
|
probeConfig: {
|
|
3132
3131
|
cadenceMs: 24 * 60 * MINUTE_MS,
|
|
3133
3132
|
cronName: "fluncle-label-releases",
|
|
@@ -3169,13 +3168,13 @@ var init_src = __esm(() => {
|
|
|
3169
3168
|
],
|
|
3170
3169
|
kind: "cron",
|
|
3171
3170
|
name: "cron.frontier-refresh",
|
|
3172
|
-
operatorNotes: "
|
|
3171
|
+
operatorNotes: "every ~15 min, run by a rave-02 HOST systemd timer (docs/agents/hermes/frontier-refresh-timer/). E2, the public recommendation machine: every verified user can mint ONE public 'Fluncle's Frontier' playlist on Fluncle's OWN Spotify account (no per-user OAuth), holding THEIR recommendations (the E1 blend); this cron keeps each one current. It is a PACED, RESUMABLE DRAIN, not a weekly burst: each tick fires one `fluncle admin frontier refresh` that processes only a small BATCH of DUE users inside the Worker (pending mints first, then users whose per-user cursor is older than ~6 days), so the whole crew refreshes ~weekly SPREAD across the day instead of one 07:00 pass that collided with Spotify's shared per-app budget and 429'd live user paths. It consults the shared Spotify budget and stops cleanly when the window is spent (`budgetPaused`), respects the DEFAULT-DENY `frontier.minting` kill switch (a closed switch touches nothing on Spotify), skips playlists whose recommendation set is unchanged (a per-row URI-hash mirror guard), and creates no new public authority — every playlist it touches already exists, minted by its own owner. `refresh_frontier_playlists` is AGENT tier, so the box's existing agent-scoped token drives it: NO new secret. Zero LLM tokens. Source: docs/agents/hermes/scripts/frontier-refresh-sweep.*. See docs/the-ear.md § Fluncle's Frontier.",
|
|
3173
3172
|
probeConfig: {
|
|
3174
|
-
cadenceMs:
|
|
3173
|
+
cadenceMs: 15 * MINUTE_MS,
|
|
3175
3174
|
cronName: "fluncle-frontier-refresh",
|
|
3176
3175
|
kind: "cron"
|
|
3177
3176
|
},
|
|
3178
|
-
statusDescription: "refreshes
|
|
3177
|
+
statusDescription: "refreshes the crew's Frontier playlists, paced",
|
|
3179
3178
|
title: "Frontier refresh",
|
|
3180
3179
|
weights: { status: "hidden" }
|
|
3181
3180
|
},
|
|
@@ -4128,7 +4127,6 @@ __export(exports_admin_tracks, {
|
|
|
4128
4127
|
captureQueueCommand: () => captureQueueCommand,
|
|
4129
4128
|
backfillRecordingMbidsCommand: () => backfillRecordingMbidsCommand,
|
|
4130
4129
|
backfillLastfmCommand: () => backfillLastfmCommand,
|
|
4131
|
-
backfillLabelReleasesCommand: () => backfillLabelReleasesCommand,
|
|
4132
4130
|
backfillDiscogsCommand: () => backfillDiscogsCommand,
|
|
4133
4131
|
backfillAppleMusicCommand: () => backfillAppleMusicCommand,
|
|
4134
4132
|
backfillAppleCatalogueCommand: () => backfillAppleCatalogueCommand
|
|
@@ -4315,10 +4313,6 @@ async function backfillAppleCatalogueCommand(limit, dryRun) {
|
|
|
4315
4313
|
const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
|
|
4316
4314
|
return adminApiPost(`/api/admin/backfill/apple-catalogue?${params.toString()}`);
|
|
4317
4315
|
}
|
|
4318
|
-
async function backfillLabelReleasesCommand(limit, dryRun) {
|
|
4319
|
-
const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
|
|
4320
|
-
return adminApiPost(`/api/admin/backfill/label-releases?${params.toString()}`);
|
|
4321
|
-
}
|
|
4322
4316
|
async function backfillRecordingMbidsCommand(limit, dryRun, cursor) {
|
|
4323
4317
|
const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
|
|
4324
4318
|
if (cursor) {
|
|
@@ -9698,10 +9692,6 @@ function addAdminCommands(program2) {
|
|
|
9698
9692
|
const { backfillAppleCatalogueCommand: backfillAppleCatalogueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
9699
9693
|
await runBackfillAppleCatalogue(options, backfillAppleCatalogueCommand2);
|
|
9700
9694
|
});
|
|
9701
|
-
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) => {
|
|
9702
|
-
const { backfillLabelReleasesCommand: backfillLabelReleasesCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
9703
|
-
await runBackfillLabelReleases(options, backfillLabelReleasesCommand2);
|
|
9704
|
-
});
|
|
9705
9695
|
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) => {
|
|
9706
9696
|
const { backfillArtistsCommand: backfillArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
|
|
9707
9697
|
await runBackfillArtists(options, backfillArtistsCommand2);
|
|
@@ -10395,89 +10385,6 @@ async function runBackfillAppleCatalogue(options, backfillAppleCatalogueCommand2
|
|
|
10395
10385
|
process.exitCode = 1;
|
|
10396
10386
|
}
|
|
10397
10387
|
}
|
|
10398
|
-
async function runBackfillLabelReleases(options, backfillLabelReleasesCommand2) {
|
|
10399
|
-
const perPass = parseListLimit(options.limit);
|
|
10400
|
-
const newTrackIds = [];
|
|
10401
|
-
const labelSlugs = [];
|
|
10402
|
-
const failedLabels = [];
|
|
10403
|
-
let dryRun = options.dryRun;
|
|
10404
|
-
let throttled = false;
|
|
10405
|
-
let configured = true;
|
|
10406
|
-
let labelsProbed = 0;
|
|
10407
|
-
let albumsSeen = 0;
|
|
10408
|
-
let albumsMatched = 0;
|
|
10409
|
-
let newRows = 0;
|
|
10410
|
-
let skippedKnown = 0;
|
|
10411
|
-
let skippedUngrounded = 0;
|
|
10412
|
-
let failedFetches = 0;
|
|
10413
|
-
let fetchCeilingHit = false;
|
|
10414
|
-
const MAX_PASSES = 100;
|
|
10415
|
-
for (let pass = 0;pass < MAX_PASSES; pass += 1) {
|
|
10416
|
-
const result = await backfillLabelReleasesCommand2(perPass, options.dryRun);
|
|
10417
|
-
dryRun = result.dryRun;
|
|
10418
|
-
configured = result.configured;
|
|
10419
|
-
labelsProbed += result.labelsProbed;
|
|
10420
|
-
albumsSeen += result.albumsSeen;
|
|
10421
|
-
albumsMatched += result.albumsMatched;
|
|
10422
|
-
newRows += result.newRows;
|
|
10423
|
-
skippedKnown += result.skippedKnown;
|
|
10424
|
-
skippedUngrounded += result.skippedUngrounded;
|
|
10425
|
-
failedFetches += result.failedFetches;
|
|
10426
|
-
fetchCeilingHit = fetchCeilingHit || result.fetchCeilingHit;
|
|
10427
|
-
newTrackIds.push(...result.newTrackIds);
|
|
10428
|
-
labelSlugs.push(...result.labelSlugs);
|
|
10429
|
-
failedLabels.push(...result.failedLabels);
|
|
10430
|
-
if (!options.json) {
|
|
10431
|
-
const verb2 = result.dryRun ? "would probe" : "probed";
|
|
10432
|
-
console.log(` \u2026${verb2} ${result.labelSlugs.length} label(s); ${result.albumsMatched} matched album(s); ${result.newRows} new; ${result.skippedKnown} known; ${result.skippedUngrounded} ungrounded; ${result.failedFetches} fetch-fail; ${result.failedLabels.length} search-fail`);
|
|
10433
|
-
}
|
|
10434
|
-
if (!result.configured) {
|
|
10435
|
-
break;
|
|
10436
|
-
}
|
|
10437
|
-
if (result.rateLimited) {
|
|
10438
|
-
throttled = true;
|
|
10439
|
-
break;
|
|
10440
|
-
}
|
|
10441
|
-
if (result.dryRun) {
|
|
10442
|
-
break;
|
|
10443
|
-
}
|
|
10444
|
-
if (result.labelsProbed === 0) {
|
|
10445
|
-
break;
|
|
10446
|
-
}
|
|
10447
|
-
}
|
|
10448
|
-
if (options.json) {
|
|
10449
|
-
printSweepJson({
|
|
10450
|
-
albumsMatched,
|
|
10451
|
-
albumsSeen,
|
|
10452
|
-
configured,
|
|
10453
|
-
dryRun,
|
|
10454
|
-
failedFetches,
|
|
10455
|
-
failedLabels,
|
|
10456
|
-
fetchCeilingHit,
|
|
10457
|
-
labelSlugs,
|
|
10458
|
-
labelsProbed,
|
|
10459
|
-
newRows,
|
|
10460
|
-
newTrackIds,
|
|
10461
|
-
rateLimited: throttled,
|
|
10462
|
-
skippedKnown,
|
|
10463
|
-
skippedUngrounded
|
|
10464
|
-
}, 0);
|
|
10465
|
-
return;
|
|
10466
|
-
}
|
|
10467
|
-
if (!configured) {
|
|
10468
|
-
console.log("Label-releases tap is a no-op \u2014 the Worker's Spotify grant is gone; reconnect Spotify.");
|
|
10469
|
-
return;
|
|
10470
|
-
}
|
|
10471
|
-
const verb = dryRun ? "Would probe" : "Probed";
|
|
10472
|
-
const probedCount = dryRun ? labelSlugs.length : labelsProbed;
|
|
10473
|
-
console.log(`${verb} ${probedCount} enabled seed label(s); ${albumsMatched} matched album(s); ${newRows} new catalogue row(s); ${skippedKnown} already known; ${skippedUngrounded} dropped for no known artist; ${failedFetches} fetch-fail; ${failedLabels.length} search-fail.`);
|
|
10474
|
-
if (fetchCeilingHit) {
|
|
10475
|
-
console.log(" (a pass ended early on the single-fetch ceiling \u2014 resumes next tick)");
|
|
10476
|
-
}
|
|
10477
|
-
for (const slug of failedLabels) {
|
|
10478
|
-
console.log(` search error: ${slug}`);
|
|
10479
|
-
}
|
|
10480
|
-
}
|
|
10481
10388
|
async function runBackfillArtists(options, backfillArtistsCommand2) {
|
|
10482
10389
|
const limit = parseListLimit(options.limit);
|
|
10483
10390
|
const upserted = [];
|
package/package.json
CHANGED