fluncle 0.130.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 +96 -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: [
|
|
@@ -5364,6 +5401,8 @@ var init_auth_lastfm = __esm(() => {
|
|
|
5364
5401
|
// src/commands/admin-catalogue.ts
|
|
5365
5402
|
var exports_admin_catalogue = {};
|
|
5366
5403
|
__export(exports_admin_catalogue, {
|
|
5404
|
+
crawlStatusCommand: () => crawlStatusCommand,
|
|
5405
|
+
crawlCatalogueCommand: () => crawlCatalogueCommand,
|
|
5367
5406
|
catalogueRankCommand: () => catalogueRankCommand,
|
|
5368
5407
|
catalogueListCommand: () => catalogueListCommand
|
|
5369
5408
|
});
|
|
@@ -5380,6 +5419,16 @@ async function catalogueListCommand(options) {
|
|
|
5380
5419
|
}
|
|
5381
5420
|
return adminApiGet(`/api/admin/catalogue?${params.toString()}`);
|
|
5382
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
|
+
}
|
|
5383
5432
|
var init_admin_catalogue = __esm(() => {
|
|
5384
5433
|
init_api();
|
|
5385
5434
|
});
|
|
@@ -8321,7 +8370,7 @@ function addAdminCommands(program2) {
|
|
|
8321
8370
|
const { galaxyMapWriteCommand: galaxyMapWriteCommand2 } = await Promise.resolve().then(() => (init_galaxies(), exports_galaxies));
|
|
8322
8371
|
await runGalaxyMapWrite(options, galaxyMapWriteCommand2);
|
|
8323
8372
|
});
|
|
8324
|
-
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"));
|
|
8325
8374
|
catalogue.action(() => {
|
|
8326
8375
|
catalogue.outputHelp();
|
|
8327
8376
|
});
|
|
@@ -8333,6 +8382,14 @@ function addAdminCommands(program2) {
|
|
|
8333
8382
|
const { catalogueListCommand: catalogueListCommand2 } = await Promise.resolve().then(() => (init_admin_catalogue(), exports_admin_catalogue));
|
|
8334
8383
|
await runCatalogueList(options, catalogueListCommand2);
|
|
8335
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
|
+
});
|
|
8336
8393
|
const backfill = configureCommand(admin.command("backfills").description("Backfill operator-only archives"));
|
|
8337
8394
|
backfill.action(() => {
|
|
8338
8395
|
backfill.outputHelp();
|
|
@@ -8630,6 +8687,41 @@ async function runBackfillLastfm(options, backfillLastfmCommand2) {
|
|
|
8630
8687
|
process.exitCode = 1;
|
|
8631
8688
|
}
|
|
8632
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
|
+
}
|
|
8633
8725
|
async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
|
|
8634
8726
|
const limit = parseListLimit(options.limit);
|
|
8635
8727
|
const resolved = [];
|
|
@@ -10105,6 +10197,7 @@ var stringOptions = new Set([
|
|
|
10105
10197
|
"--kind",
|
|
10106
10198
|
"--lens",
|
|
10107
10199
|
"--limit",
|
|
10200
|
+
"--max-hop",
|
|
10108
10201
|
"--metrics",
|
|
10109
10202
|
"--mime",
|
|
10110
10203
|
"--model",
|
package/package.json
CHANGED