fluncle 0.152.0 → 0.154.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 +103 -3
- 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.154.0".trim() ? "0.154.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.154.0".trim() ? "0.154.0".trim() : "0.1.0";
|
|
2193
2193
|
});
|
|
2194
2194
|
|
|
2195
2195
|
// ../../packages/registry/src/index.ts
|
|
@@ -3626,6 +3626,7 @@ var init_admin_attention = __esm(() => {
|
|
|
3626
3626
|
"label-review": "label",
|
|
3627
3627
|
newsletter: "newsletter",
|
|
3628
3628
|
"note-rejected": "held note",
|
|
3629
|
+
"observation-rejected": "held observation",
|
|
3629
3630
|
"post-tiktok": "tiktok",
|
|
3630
3631
|
"post-youtube": "youtube",
|
|
3631
3632
|
submission: "submission",
|
|
@@ -5761,6 +5762,35 @@ var init_admin_notes = __esm(() => {
|
|
|
5761
5762
|
init_api();
|
|
5762
5763
|
});
|
|
5763
5764
|
|
|
5765
|
+
// src/commands/admin-observations.ts
|
|
5766
|
+
var exports_admin_observations = {};
|
|
5767
|
+
__export(exports_admin_observations, {
|
|
5768
|
+
observationHeldCommand: () => observationHeldCommand,
|
|
5769
|
+
observationGateCommand: () => observationGateCommand
|
|
5770
|
+
});
|
|
5771
|
+
async function observationHeldCommand(options) {
|
|
5772
|
+
const query = options.settled ? "?open=false" : "";
|
|
5773
|
+
return adminApiGet(`/api/admin/observation-rejections${query}`);
|
|
5774
|
+
}
|
|
5775
|
+
async function observationGateCommand(options) {
|
|
5776
|
+
const patch = {};
|
|
5777
|
+
if (options.minPhraseWords !== undefined) {
|
|
5778
|
+
patch.minPhraseWords = options.minPhraseWords;
|
|
5779
|
+
}
|
|
5780
|
+
if (options.maxOverlap !== undefined) {
|
|
5781
|
+
patch.maxOverlap = options.maxOverlap;
|
|
5782
|
+
}
|
|
5783
|
+
if (Object.keys(patch).length === 0) {
|
|
5784
|
+
const response2 = await adminApiGet("/api/admin/observation-rejections");
|
|
5785
|
+
return response2.gate;
|
|
5786
|
+
}
|
|
5787
|
+
const response = await adminApiPatch("/api/admin/observation-gate", patch);
|
|
5788
|
+
return response.gate;
|
|
5789
|
+
}
|
|
5790
|
+
var init_admin_observations = __esm(() => {
|
|
5791
|
+
init_api();
|
|
5792
|
+
});
|
|
5793
|
+
|
|
5764
5794
|
// src/commands/admin-catalogue.ts
|
|
5765
5795
|
var exports_admin_catalogue = {};
|
|
5766
5796
|
__export(exports_admin_catalogue, {
|
|
@@ -8901,6 +8931,18 @@ function addAdminCommands(program2) {
|
|
|
8901
8931
|
const { noteGateCommand: noteGateCommand2 } = await Promise.resolve().then(() => (init_admin_notes(), exports_admin_notes));
|
|
8902
8932
|
await runNoteGate(options, noteGateCommand2);
|
|
8903
8933
|
});
|
|
8934
|
+
const observations = configureCommand(admin.command("observations").description("The observation echo gate: held scripts + dials"));
|
|
8935
|
+
observations.action(() => {
|
|
8936
|
+
observations.outputHelp();
|
|
8937
|
+
});
|
|
8938
|
+
observations.command("held").description("The observation scripts the echo gate held back (and why)").option("--settled", "Read the ones already ruled on instead (the retune evidence)", false).option("--json", "Print JSON", false).action(async (options) => {
|
|
8939
|
+
const { observationHeldCommand: observationHeldCommand2 } = await Promise.resolve().then(() => (init_admin_observations(), exports_admin_observations));
|
|
8940
|
+
await runObservationHeld(options, observationHeldCommand2);
|
|
8941
|
+
});
|
|
8942
|
+
observations.command("gate").description("Read or retune the observation echo gate's thresholds (a flip, not a deploy)").option("--min-phrase-words <n>", "A shared run this long is a lift (2-20; default 4)").option("--max-overlap <x>", "Content-word overlap at/above this is an echo (0.05-1; default 0.3)").option("--json", "Print JSON", false).action(async (options) => {
|
|
8943
|
+
const { observationGateCommand: observationGateCommand2 } = await Promise.resolve().then(() => (init_admin_observations(), exports_admin_observations));
|
|
8944
|
+
await runObservationGate(options, observationGateCommand2);
|
|
8945
|
+
});
|
|
8904
8946
|
const catalogue = configureCommand(admin.command("catalogue").description("The catalogue (uncertified tracks): the crawler + its ranking"));
|
|
8905
8947
|
catalogue.action(() => {
|
|
8906
8948
|
catalogue.outputHelp();
|
|
@@ -9385,6 +9427,18 @@ async function runBackfillLastfm(options, backfillLastfmCommand2) {
|
|
|
9385
9427
|
process.exitCode = 1;
|
|
9386
9428
|
}
|
|
9387
9429
|
}
|
|
9430
|
+
function anchorOutcomeSuffix(outcome) {
|
|
9431
|
+
switch (outcome) {
|
|
9432
|
+
case "throttled":
|
|
9433
|
+
return " (Spotify throttled the fill; the anchor breaker is pacing it)";
|
|
9434
|
+
case "unauthorized":
|
|
9435
|
+
return " (Spotify grant is gone \u2014 reconnect Spotify from /admin to resume anchoring)";
|
|
9436
|
+
case "breaker_open":
|
|
9437
|
+
return " (anchor fill PAUSED on its breaker; it resumes after the cooldown)";
|
|
9438
|
+
default:
|
|
9439
|
+
return "";
|
|
9440
|
+
}
|
|
9441
|
+
}
|
|
9388
9442
|
async function runCrawlCatalogue(options, crawlCatalogueCommand2) {
|
|
9389
9443
|
const limit = parseListLimit(options.limit);
|
|
9390
9444
|
const maxHop = Number.parseInt(options.maxHop ?? "2", 10);
|
|
@@ -9396,7 +9450,7 @@ async function runCrawlCatalogue(options, crawlCatalogueCommand2) {
|
|
|
9396
9450
|
} else {
|
|
9397
9451
|
console.log(`Expanded ${result.expanded} node(s) at hop \u2264 ${result.maxHop}: ${result.tracksFound} track(s) found, ${result.tracksWritten} written, ${result.tracksSkipped} already held.`);
|
|
9398
9452
|
console.log(` +${result.seeded} seed(s), +${result.nodesEnqueued} node(s) enqueued, ${result.frontierPending} pending, ${result.failed} failed.`);
|
|
9399
|
-
console.log(` ${result.anchorsFilled} Spotify anchor(s) filled.`);
|
|
9453
|
+
console.log(` ${result.anchorsFilled} Spotify anchor(s) filled${anchorOutcomeSuffix(result.anchorOutcome)}.`);
|
|
9400
9454
|
if (result.labelsDiscovered.length > 0) {
|
|
9401
9455
|
console.log(` New labels to rule on: ${result.labelsDiscovered.join(", ")} (they are NOT crawled until enabled).`);
|
|
9402
9456
|
}
|
|
@@ -9416,6 +9470,14 @@ async function runCrawlStatus(options, crawlStatusCommand2) {
|
|
|
9416
9470
|
}
|
|
9417
9471
|
const { frontier, frontierByKind } = result;
|
|
9418
9472
|
console.log(`Catalogue: ${result.catalogueTracks} uncertified track(s); ${result.anchorsPending} awaiting a Spotify anchor.`);
|
|
9473
|
+
const { spotifyAnchor } = result;
|
|
9474
|
+
if (spotifyAnchor.tripped) {
|
|
9475
|
+
const mins = Math.ceil(spotifyAnchor.cooldownRemainingMs / 60000);
|
|
9476
|
+
const why = spotifyAnchor.reason === "unauthorized" ? "the Spotify grant is gone \u2014 reconnect Spotify from /admin to resume anchoring" : "Spotify is throttling the app \u2014 it self-heals when the throttle lifts";
|
|
9477
|
+
console.log(` Spotify anchor PAUSED (${why}); breaker cools in ~${mins} min.`);
|
|
9478
|
+
} else if (spotifyAnchor.consecutiveFailures > 0) {
|
|
9479
|
+
console.log(` Spotify anchor wobbling (${spotifyAnchor.consecutiveFailures} failing pass(es); reason: ${spotifyAnchor.reason ?? "unknown"}).`);
|
|
9480
|
+
}
|
|
9419
9481
|
console.log(`Frontier: ${frontier.pending} pending, ${frontier.done} done, ${frontier.failed} failed, ${frontier.skipped} skipped (${frontierByKind.label} label, ${frontierByKind.artist} artist, ${frontierByKind.release} release).`);
|
|
9420
9482
|
console.log(`Seeds (${result.seedLabels.length}): ${result.seedLabels.join(", ") || "none enabled"}.`);
|
|
9421
9483
|
console.log(`Awaiting your ruling: ${result.labelsUndecided} label(s).`);
|
|
@@ -10751,6 +10813,44 @@ async function runNoteGate(options, noteGateCommand2) {
|
|
|
10751
10813
|
}
|
|
10752
10814
|
console.log(`The echo gate: a lift is ${gate.minPhraseWords}+ shared words; an echo is ${Math.round(gate.maxOverlap * 100)}%+ of a neighbour's words.`);
|
|
10753
10815
|
}
|
|
10816
|
+
async function runObservationHeld(options, observationHeldCommand2) {
|
|
10817
|
+
const { gate, rejections } = await observationHeldCommand2({ settled: options.settled === true });
|
|
10818
|
+
if (options.json) {
|
|
10819
|
+
printJson({ gate, ok: true, rejections });
|
|
10820
|
+
return;
|
|
10821
|
+
}
|
|
10822
|
+
if (rejections.length === 0) {
|
|
10823
|
+
console.log(options.settled ? "Nothing ruled on yet." : "Nothing held back. The gate is quiet.");
|
|
10824
|
+
return;
|
|
10825
|
+
}
|
|
10826
|
+
console.log(`The gate sits at ${gate.minPhraseWords} lifted words / ${Math.round(gate.maxOverlap * 100)}% overlap.`);
|
|
10827
|
+
for (const held of rejections) {
|
|
10828
|
+
const ruled = held.resolution ? ` [${held.resolution}]` : "";
|
|
10829
|
+
const bounced = held.attempts > 1 ? ` (bounced ${held.attempts}x)` : "";
|
|
10830
|
+
console.log("");
|
|
10831
|
+
console.log(`${held.logId ?? held.trackId} ${held.title}${bounced}${ruled}`);
|
|
10832
|
+
console.log(` wrote: ${held.script}`);
|
|
10833
|
+
if (held.phrase) {
|
|
10834
|
+
console.log(` lifted: "${held.phrase}" from ${held.neighborLogId ?? "a neighbour"}`);
|
|
10835
|
+
} else {
|
|
10836
|
+
console.log(` reuses: ${Math.round(held.overlap * 100)}% of ${held.neighborLogId ?? "a neighbour"}'s words`);
|
|
10837
|
+
}
|
|
10838
|
+
console.log(` judged at: ${held.minPhraseWords} words / ${Math.round(held.maxOverlap * 100)}%`);
|
|
10839
|
+
}
|
|
10840
|
+
}
|
|
10841
|
+
async function runObservationGate(options, observationGateCommand2) {
|
|
10842
|
+
const minPhraseWords = options.minPhraseWords === undefined ? undefined : Number(options.minPhraseWords);
|
|
10843
|
+
const maxOverlap = options.maxOverlap === undefined ? undefined : Number(options.maxOverlap);
|
|
10844
|
+
const gate = await observationGateCommand2({
|
|
10845
|
+
...maxOverlap === undefined ? {} : { maxOverlap },
|
|
10846
|
+
...minPhraseWords === undefined ? {} : { minPhraseWords }
|
|
10847
|
+
});
|
|
10848
|
+
if (options.json) {
|
|
10849
|
+
printJson({ gate, ok: true });
|
|
10850
|
+
return;
|
|
10851
|
+
}
|
|
10852
|
+
console.log(`The observation echo gate: a lift is ${gate.minPhraseWords}+ shared words; an echo is ${Math.round(gate.maxOverlap * 100)}%+ of a neighbour's words.`);
|
|
10853
|
+
}
|
|
10754
10854
|
async function runGalaxyMapRead(options, galaxyMapReadCommand2) {
|
|
10755
10855
|
const galaxies = await galaxyMapReadCommand2();
|
|
10756
10856
|
if (options.json) {
|
package/package.json
CHANGED