fluncle 0.129.0 → 0.131.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 +147 -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.131.0".trim() ? "0.131.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.131.0".trim() ? "0.131.0".trim() : "0.1.0";
|
|
2193
2193
|
});
|
|
2194
2194
|
|
|
2195
2195
|
// ../../packages/registry/src/index.ts
|
|
@@ -2537,6 +2537,21 @@ var init_src = __esm(() => {
|
|
|
2537
2537
|
url: `${SITE}/api/v1/search`,
|
|
2538
2538
|
weights: { web: "secondary" }
|
|
2539
2539
|
},
|
|
2540
|
+
{
|
|
2541
|
+
apiFormat: "application/json",
|
|
2542
|
+
discoveryUrl: `${SITE}/api/v1/openapi.json`,
|
|
2543
|
+
exposedContent: [
|
|
2544
|
+
"search Fluncle's archive: a coordinate (004.7.2I), an artist/label/album name, a bare word (FTS5), or a natural-language query",
|
|
2545
|
+
"sonic search — 'tracks that sound like <a real track>', ranked by MuQ embedding distance"
|
|
2546
|
+
],
|
|
2547
|
+
kind: "api",
|
|
2548
|
+
name: "api.search.archive",
|
|
2549
|
+
operatorNotes: "The public read behind the ⌘K dialog (search_archive), and the primary navigation once the archive is deep. Four resolution tiers; only the fourth reaches an LLM (OpenRouter, 3s deadline) and it emits FILTERS, never rows. With no OPENROUTER_API_KEY it degrades to full-text and still answers — so it probes green unprovisioned.",
|
|
2550
|
+
probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
|
|
2551
|
+
route: "/api/v1/search/archive",
|
|
2552
|
+
url: `${SITE}/api/v1/search/archive?q=netsky`,
|
|
2553
|
+
weights: { web: "primary" }
|
|
2554
|
+
},
|
|
2540
2555
|
{
|
|
2541
2556
|
apiFormat: "application/json",
|
|
2542
2557
|
discoveryUrl: `${SITE}/api/v1/openapi.json`,
|
|
@@ -2888,6 +2903,28 @@ var init_src = __esm(() => {
|
|
|
2888
2903
|
probeConfig: { cadenceMs: 24 * 60 * MINUTE_MS, cronName: "fluncle-cluster", kind: "cron" },
|
|
2889
2904
|
weights: { status: "hidden" }
|
|
2890
2905
|
},
|
|
2906
|
+
{
|
|
2907
|
+
command: "fluncle admin catalogue crawl",
|
|
2908
|
+
exposedContent: [
|
|
2909
|
+
"walk the MusicBrainz release graph outward from the operator's enabled seed labels → uncertified catalogue rows"
|
|
2910
|
+
],
|
|
2911
|
+
kind: "cron",
|
|
2912
|
+
name: "cron.crawl",
|
|
2913
|
+
operatorNotes: "every 10m, run by a rave-02 HOST systemd timer (docs/agents/hermes/crawl-timer/). METADATA ONLY — it writes a `tracks` row with no `findings` row, so it certifies nothing (no Log ID, no note, no video, no public surface) and it captures no audio. Worker-paced (the box holds no MusicBrainz budget): one bounded pass per tick over the durable `crawl_frontier`, so a crawl is a marathon the SCHEDULE finishes and a reboot mid-label costs one node, not one crawl. The boundary gate is the operator's seed-label allowlist + graph distance (hop 0-2), never genre inference; a label the walk discovers enters `undecided` and is NOT crawled until he rules on it. Zero LLM tokens. Source: docs/agents/hermes/scripts/crawl-sweep.*. See docs/catalogue-crawler.md.",
|
|
2914
|
+
probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-crawl", kind: "cron" },
|
|
2915
|
+
weights: { status: "hidden" }
|
|
2916
|
+
},
|
|
2917
|
+
{
|
|
2918
|
+
command: "fluncle admin catalogue rank",
|
|
2919
|
+
exposedContent: [
|
|
2920
|
+
"score each stale catalogue track against every embedded finding → its nearest finding + capture priority"
|
|
2921
|
+
],
|
|
2922
|
+
kind: "cron",
|
|
2923
|
+
name: "cron.rank",
|
|
2924
|
+
operatorNotes: "every 30m, run by a rave-02 HOST systemd timer (docs/agents/hermes/rank-timer/). THE EAR's schedule, and it lands with the crawler on purpose: a timer ranking an empty table would be a /status row that means nothing, and the crawler is what creates rows. All the vector arithmetic runs in SQL inside the Worker; the sweep DRAINS (it loops while `remaining > 0` up to a tick budget), so a crawl that just landed 700 rows is ranked by the next tick. Self-healing: staleness is a fingerprint of the finding corpus, so logging or embedding a finding re-ranks the catalogue with no invalidation call. Writes DERIVED columns on catalogue rows only — it cannot certify. Zero LLM tokens. Source: docs/agents/hermes/scripts/rank-sweep.*. See docs/the-ear.md.",
|
|
2925
|
+
probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-rank", kind: "cron" },
|
|
2926
|
+
weights: { status: "hidden" }
|
|
2927
|
+
},
|
|
2891
2928
|
{
|
|
2892
2929
|
command: "fluncle admin tracks capture-audio --queue",
|
|
2893
2930
|
exposedContent: [
|
|
@@ -3570,6 +3607,7 @@ var init_track2 = __esm(() => {
|
|
|
3570
3607
|
var exports_admin_tracks = {};
|
|
3571
3608
|
__export(exports_admin_tracks, {
|
|
3572
3609
|
vehiclesCommand: () => vehiclesCommand,
|
|
3610
|
+
trackWorkCommand: () => trackWorkCommand,
|
|
3573
3611
|
requeueAnalysisCommand: () => requeueAnalysisCommand,
|
|
3574
3612
|
queueCommand: () => queueCommand,
|
|
3575
3613
|
observeQueueCommand: () => observeQueueCommand,
|
|
@@ -3671,6 +3709,15 @@ async function embedQueueCommand(limit) {
|
|
|
3671
3709
|
async function captureQueueCommand(limit) {
|
|
3672
3710
|
return fetchAdminTracks({ captureQueue: true, max: limit, order: "desc" });
|
|
3673
3711
|
}
|
|
3712
|
+
async function trackWorkCommand(options) {
|
|
3713
|
+
const params = new URLSearchParams({
|
|
3714
|
+
kind: options.kind,
|
|
3715
|
+
limit: String(Math.min(Math.max(1, options.limit), 200)),
|
|
3716
|
+
scope: options.scope
|
|
3717
|
+
});
|
|
3718
|
+
const response = await adminApiGet(`/api/admin/tracks/work?${params.toString()}`);
|
|
3719
|
+
return response.tracks ?? [];
|
|
3720
|
+
}
|
|
3674
3721
|
async function requeueAnalysisCommand(options) {
|
|
3675
3722
|
const findings = await fetchAdminTracks({ max: options.max, order: "asc" });
|
|
3676
3723
|
const stale = findings.filter((track) => track.analyzedFrom !== "full");
|
|
@@ -5354,6 +5401,8 @@ var init_auth_lastfm = __esm(() => {
|
|
|
5354
5401
|
// src/commands/admin-catalogue.ts
|
|
5355
5402
|
var exports_admin_catalogue = {};
|
|
5356
5403
|
__export(exports_admin_catalogue, {
|
|
5404
|
+
crawlStatusCommand: () => crawlStatusCommand,
|
|
5405
|
+
crawlCatalogueCommand: () => crawlCatalogueCommand,
|
|
5357
5406
|
catalogueRankCommand: () => catalogueRankCommand,
|
|
5358
5407
|
catalogueListCommand: () => catalogueListCommand
|
|
5359
5408
|
});
|
|
@@ -5370,6 +5419,16 @@ async function catalogueListCommand(options) {
|
|
|
5370
5419
|
}
|
|
5371
5420
|
return adminApiGet(`/api/admin/catalogue?${params.toString()}`);
|
|
5372
5421
|
}
|
|
5422
|
+
async function crawlCatalogueCommand(limit, maxHop, dryRun) {
|
|
5423
|
+
const params = new URLSearchParams({ limit: String(limit), maxHop: String(maxHop) });
|
|
5424
|
+
if (dryRun) {
|
|
5425
|
+
params.set("dryRun", "true");
|
|
5426
|
+
}
|
|
5427
|
+
return adminApiPost(`/api/admin/catalogue/crawl?${params.toString()}`);
|
|
5428
|
+
}
|
|
5429
|
+
async function crawlStatusCommand() {
|
|
5430
|
+
return adminApiGet("/api/admin/catalogue/crawl");
|
|
5431
|
+
}
|
|
5373
5432
|
var init_admin_catalogue = __esm(() => {
|
|
5374
5433
|
init_api();
|
|
5375
5434
|
});
|
|
@@ -8016,6 +8075,10 @@ function addAdminCommands(program2) {
|
|
|
8016
8075
|
const { captureQueueCommand: captureQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
8017
8076
|
await runAdminCaptureQueue(options, captureQueueCommand2);
|
|
8018
8077
|
});
|
|
8078
|
+
adminTracks.command("work").description("The audio pipeline's worklist for one stage, in capture-priority order").requiredOption("--kind <kind>", "Pipeline stage: analyze | capture | embed").option("--scope <scope>", "Which half of the archive: all | catalogue | findings", "all").option("--limit <limit>", "Rows to show", "10").option("--json", "Print JSON", false).action(async (options) => {
|
|
8079
|
+
const { trackWorkCommand: trackWorkCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
8080
|
+
await runAdminTrackWork(options, trackWorkCommand2);
|
|
8081
|
+
});
|
|
8019
8082
|
adminTracks.command("requeue-analysis").description("Re-queue findings whose BPM/key are preview-grade (dry-run; --apply to flip)").option("--apply", "Actually flip statuses (default is a dry-run preview)", false).option("--limit <limit>", "Cap the archive walk (default: the whole archive)").option("--json", "Print JSON", false).action(async (options) => {
|
|
8020
8083
|
const { requeueAnalysisCommand: requeueAnalysisCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
8021
8084
|
await runAdminRequeueAnalysis(options, requeueAnalysisCommand2);
|
|
@@ -8307,7 +8370,7 @@ function addAdminCommands(program2) {
|
|
|
8307
8370
|
const { galaxyMapWriteCommand: galaxyMapWriteCommand2 } = await Promise.resolve().then(() => (init_galaxies(), exports_galaxies));
|
|
8308
8371
|
await runGalaxyMapWrite(options, galaxyMapWriteCommand2);
|
|
8309
8372
|
});
|
|
8310
|
-
const catalogue = configureCommand(admin.command("catalogue").description("The catalogue (uncertified tracks) + its ranking"));
|
|
8373
|
+
const catalogue = configureCommand(admin.command("catalogue").description("The catalogue (uncertified tracks): the crawler + its ranking"));
|
|
8311
8374
|
catalogue.action(() => {
|
|
8312
8375
|
catalogue.outputHelp();
|
|
8313
8376
|
});
|
|
@@ -8319,6 +8382,14 @@ function addAdminCommands(program2) {
|
|
|
8319
8382
|
const { catalogueListCommand: catalogueListCommand2 } = await Promise.resolve().then(() => (init_admin_catalogue(), exports_admin_catalogue));
|
|
8320
8383
|
await runCatalogueList(options, catalogueListCommand2);
|
|
8321
8384
|
});
|
|
8385
|
+
catalogue.command("crawl").description("Run one bounded, resumable pass of the catalogue crawler").option("--dry-run", "Report the seed plan and write nothing at all", false).option("--limit <limit>", "Frontier nodes to expand this pass", "10").option("--max-hop <maxHop>", "Graph distance from a seed label (0-3)", "2").option("--json", "Print JSON", false).action(async (options) => {
|
|
8386
|
+
const { crawlCatalogueCommand: crawlCatalogueCommand2 } = await Promise.resolve().then(() => (init_admin_catalogue(), exports_admin_catalogue));
|
|
8387
|
+
await runCrawlCatalogue(options, crawlCatalogueCommand2);
|
|
8388
|
+
});
|
|
8389
|
+
catalogue.command("status").description("The crawl frontier's state, the catalogue's size, and the seed set").option("--json", "Print JSON", false).action(async (options) => {
|
|
8390
|
+
const { crawlStatusCommand: crawlStatusCommand2 } = await Promise.resolve().then(() => (init_admin_catalogue(), exports_admin_catalogue));
|
|
8391
|
+
await runCrawlStatus(options, crawlStatusCommand2);
|
|
8392
|
+
});
|
|
8322
8393
|
const backfill = configureCommand(admin.command("backfills").description("Backfill operator-only archives"));
|
|
8323
8394
|
backfill.action(() => {
|
|
8324
8395
|
backfill.outputHelp();
|
|
@@ -8616,6 +8687,41 @@ async function runBackfillLastfm(options, backfillLastfmCommand2) {
|
|
|
8616
8687
|
process.exitCode = 1;
|
|
8617
8688
|
}
|
|
8618
8689
|
}
|
|
8690
|
+
async function runCrawlCatalogue(options, crawlCatalogueCommand2) {
|
|
8691
|
+
const limit = parseListLimit(options.limit);
|
|
8692
|
+
const maxHop = Number.parseInt(options.maxHop ?? "2", 10);
|
|
8693
|
+
const result = await crawlCatalogueCommand2(limit, Number.isInteger(maxHop) && maxHop >= 0 ? maxHop : 2, options.dryRun);
|
|
8694
|
+
if (options.json) {
|
|
8695
|
+
printJson(result);
|
|
8696
|
+
} else if (result.dryRun) {
|
|
8697
|
+
console.log(`Dry run: would seed ${result.seeded} enabled label(s); ${result.frontierPending} node(s) pending. Nothing written.`);
|
|
8698
|
+
} else {
|
|
8699
|
+
console.log(`Expanded ${result.expanded} node(s) at hop \u2264 ${result.maxHop}: ${result.tracksFound} track(s) found, ${result.tracksWritten} written, ${result.tracksSkipped} already held.`);
|
|
8700
|
+
console.log(` +${result.seeded} seed(s), +${result.nodesEnqueued} node(s) enqueued, ${result.frontierPending} pending, ${result.failed} failed.`);
|
|
8701
|
+
console.log(` ${result.anchorsFilled} Spotify anchor(s) filled.`);
|
|
8702
|
+
if (result.labelsDiscovered.length > 0) {
|
|
8703
|
+
console.log(` New labels to rule on: ${result.labelsDiscovered.join(", ")} (they are NOT crawled until enabled).`);
|
|
8704
|
+
}
|
|
8705
|
+
if (result.rateLimited) {
|
|
8706
|
+
console.log(" MusicBrainz throttled the pass. Stopped clean; the next tick resumes.");
|
|
8707
|
+
}
|
|
8708
|
+
}
|
|
8709
|
+
if (result.rateLimited) {
|
|
8710
|
+
process.exitCode = 1;
|
|
8711
|
+
}
|
|
8712
|
+
}
|
|
8713
|
+
async function runCrawlStatus(options, crawlStatusCommand2) {
|
|
8714
|
+
const result = await crawlStatusCommand2();
|
|
8715
|
+
if (options.json) {
|
|
8716
|
+
printJson(result);
|
|
8717
|
+
return;
|
|
8718
|
+
}
|
|
8719
|
+
const { frontier, frontierByKind } = result;
|
|
8720
|
+
console.log(`Catalogue: ${result.catalogueTracks} uncertified track(s); ${result.anchorsPending} awaiting a Spotify anchor.`);
|
|
8721
|
+
console.log(`Frontier: ${frontier.pending} pending, ${frontier.done} done, ${frontier.failed} failed, ${frontier.skipped} skipped (${frontierByKind.label} label, ${frontierByKind.artist} artist, ${frontierByKind.release} release).`);
|
|
8722
|
+
console.log(`Seeds (${result.seedLabels.length}): ${result.seedLabels.join(", ") || "none enabled"}.`);
|
|
8723
|
+
console.log(`Awaiting your ruling: ${result.labelsUndecided} label(s).`);
|
|
8724
|
+
}
|
|
8619
8725
|
async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
|
|
8620
8726
|
const limit = parseListLimit(options.limit);
|
|
8621
8727
|
const resolved = [];
|
|
@@ -9734,6 +9840,42 @@ async function runAdminCaptureQueue(options, captureQueueCommand2) {
|
|
|
9734
9840
|
console.log(trackRows2(tracks).join(`
|
|
9735
9841
|
`));
|
|
9736
9842
|
}
|
|
9843
|
+
var TRACK_WORK_KINDS = new Set(["analyze", "capture", "embed"]);
|
|
9844
|
+
var TRACK_WORK_SCOPES = new Set(["all", "catalogue", "findings"]);
|
|
9845
|
+
async function runAdminTrackWork(options, trackWorkCommand2) {
|
|
9846
|
+
const kind = options.kind?.trim().toLowerCase() ?? "";
|
|
9847
|
+
const scope = options.scope?.trim().toLowerCase() ?? "all";
|
|
9848
|
+
if (!TRACK_WORK_KINDS.has(kind)) {
|
|
9849
|
+
console.error(`--kind must be one of: analyze, capture, embed (got "${options.kind}")`);
|
|
9850
|
+
process.exitCode = 1;
|
|
9851
|
+
return;
|
|
9852
|
+
}
|
|
9853
|
+
if (!TRACK_WORK_SCOPES.has(scope)) {
|
|
9854
|
+
console.error(`--scope must be one of: all, catalogue, findings (got "${options.scope}")`);
|
|
9855
|
+
process.exitCode = 1;
|
|
9856
|
+
return;
|
|
9857
|
+
}
|
|
9858
|
+
const tracks = await trackWorkCommand2({
|
|
9859
|
+
kind,
|
|
9860
|
+
limit: parseListLimit(options.limit),
|
|
9861
|
+
scope
|
|
9862
|
+
});
|
|
9863
|
+
if (options.json) {
|
|
9864
|
+
printJson({ ok: true, tracks });
|
|
9865
|
+
return;
|
|
9866
|
+
}
|
|
9867
|
+
if (tracks.length === 0) {
|
|
9868
|
+
console.log(`Nothing to ${kind}. The ${scope} queue is drained.`);
|
|
9869
|
+
return;
|
|
9870
|
+
}
|
|
9871
|
+
const noun = tracks.length === 1 ? "track" : "tracks";
|
|
9872
|
+
console.log(`${tracks.length} ${noun} to ${kind} (${scope}), in drain order:`);
|
|
9873
|
+
for (const track of tracks) {
|
|
9874
|
+
const who = track.logId ?? `${track.trackId} \xB7 catalogue`;
|
|
9875
|
+
const rung = track.capturePriority === null ? "" : ` \xB7 p${track.capturePriority}`;
|
|
9876
|
+
console.log(` ${who}${rung} \u2014 ${track.artists.join(", ")} \u2014 ${track.title}`);
|
|
9877
|
+
}
|
|
9878
|
+
}
|
|
9737
9879
|
var REQUEUE_ANALYSIS_ARCHIVE_CAP = 1e6;
|
|
9738
9880
|
function parseRequeueAnalysisLimit(value) {
|
|
9739
9881
|
if (value === undefined) {
|
|
@@ -10055,6 +10197,7 @@ var stringOptions = new Set([
|
|
|
10055
10197
|
"--kind",
|
|
10056
10198
|
"--lens",
|
|
10057
10199
|
"--limit",
|
|
10200
|
+
"--max-hop",
|
|
10058
10201
|
"--metrics",
|
|
10059
10202
|
"--mime",
|
|
10060
10203
|
"--model",
|
|
@@ -10075,6 +10218,7 @@ var stringOptions = new Set([
|
|
|
10075
10218
|
"--scheduled-for",
|
|
10076
10219
|
"--script",
|
|
10077
10220
|
"--script-file",
|
|
10221
|
+
"--scope",
|
|
10078
10222
|
"--seed",
|
|
10079
10223
|
"--soundcloud-url",
|
|
10080
10224
|
"--source",
|
package/package.json
CHANGED