fluncle 0.161.0 → 0.162.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.
Files changed (2) hide show
  1. package/bin/fluncle.mjs +44 -23
  2. 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.161.0".trim() ? "0.161.0".trim() : "0.1.0";
564
+ currentVersion = "0.162.0".trim() ? "0.162.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.161.0".trim() ? "0.161.0".trim() : "0.1.0";
2192
+ currentVersion2 = "0.162.0".trim() ? "0.162.0".trim() : "0.1.0";
2193
2193
  });
2194
2194
 
2195
2195
  // ../../packages/registry/src/index.ts
@@ -2386,6 +2386,19 @@ var init_src = __esm(() => {
2386
2386
  url: `${SITE}/albums`,
2387
2387
  weights: { web: "secondary" }
2388
2388
  },
2389
+ {
2390
+ discoveryUrl: `${SITE}/llms.txt`,
2391
+ exposedContent: [
2392
+ "/fresh — what just came out: every drum & bass track released in the trailing 30-day window, freshest first, findings in full voice and the quieter rows in the unlit register"
2393
+ ],
2394
+ kind: "web_route",
2395
+ name: "web.fresh",
2396
+ operatorNotes: "The new-releases lens over the whole archive — the SEO answer to 'new dnb releases', a weekly-refreshed query. Orders by tracks.release_date (when a tune CAME OUT), never findings.added_at (when Fluncle FOUND it) — the two are unrelated, and the copy never claims he found these. A HUB, so it is always indexable + listed unconditionally in the sitemap (like /albums), never the per-detail thin-content gate. The window read rides the tracks_release_date_idx btree so it stays a bounded range scan as the catalogue grows (lib/server/fresh.ts). The INDEX is always-200, so it is HTTP-probeable.",
2397
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2398
+ route: "/fresh",
2399
+ url: `${SITE}/fresh`,
2400
+ weights: { web: "secondary" }
2401
+ },
2389
2402
  {
2390
2403
  exposedContent: [
2391
2404
  "/galaxies — the browse-by-feel lens: the archive grouped into operator-named sonic galaxies (k-means over the MuQ audio embedding space)",
@@ -2969,6 +2982,18 @@ var init_src = __esm(() => {
2969
2982
  title: "Track ranking",
2970
2983
  weights: { status: "hidden" }
2971
2984
  },
2985
+ {
2986
+ exposedContent: [
2987
+ "find each un-anchored catalogue row's Spotify track via Apify → its spotify_uri/spotify_url anchor"
2988
+ ],
2989
+ kind: "cron",
2990
+ name: "cron.anchor",
2991
+ operatorNotes: "hourly, run by a rave-02 HOST systemd timer (docs/agents/hermes/anchor-timer/). Fills the catalogue Spotify anchor OFF the official (dev-mode) Spotify app, which starved under 429s at catalogue scale and must stay for user-facing paths: the box runs an Apify Spotify-scraper actor to find candidates for each un-anchored catalogue row and POSTs them to the agent-tier `anchor_track` op, where the WORKER re-runs verification (exact ISRC, else the folded artist+title+±2s search triple — the box's verdict is never trusted) and writes the anchor on a hit. Every attempt stamps a 14-day re-ask backoff so a miss is not re-billed. The ONE new secret is APIFY_API_TOKEN; each row is a billed Apify search (~$0.015), so the default 15 rows/hour ≈ $5-6/day while the backlog drains — pause by stopping the timer, burn attended with `--limit N`. Calls the oRPC HTTP endpoints directly (no new CLI command the pinned box CLI would lack). Source: docs/agents/hermes/scripts/anchor-sweep.*. See docs/catalogue-crawler.md § the anchor.",
2992
+ probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-anchor", kind: "cron" },
2993
+ statusDescription: "finds each catalogue track's Spotify link",
2994
+ title: "Spotify anchors",
2995
+ weights: { status: "hidden" }
2996
+ },
2972
2997
  {
2973
2998
  command: "fluncle admin frontier refresh",
2974
2999
  exposedContent: [
@@ -3332,6 +3357,23 @@ var init_src = __esm(() => {
3332
3357
  statusDescription: "reviews + merges the nightly audit PR",
3333
3358
  title: "Audit reviewer",
3334
3359
  weights: { status: "secondary" }
3360
+ },
3361
+ {
3362
+ exposedContent: [
3363
+ "nightly Sentry triage — reads the day's unresolved production errors and opens a fix PR for each straightforward one, files the rest (claude -p, subscription auth)"
3364
+ ],
3365
+ kind: "cron",
3366
+ name: "cron.sentry-triage",
3367
+ operatorNotes: "03:30 Amsterdam, a rave-02 host systemd timer (docs/agents/hermes/sentry-triage-timer/). Its OWN cron, not the audit rotation — checks Sentry every night. A full agentic claude -p session: reconciles merged fixes (resolves their issues), pulls new unresolved issues from both Sentry projects, fixes the straightforward ones (one PR each, `Sentry-Issue:` refs so a merge resolves the issue), files the rest to docs/sentry-backlog.md. The deterministic Sentry API work lives in sentry-triage-sweep.ts so the Sentry token never enters claude. Subscription auth (CLAUDE_CODE_OAUTH_TOKEN), zero OpenRouter tokens. Operator-gated on SENTRY_TRIAGE_TOKEN (skips cleanly until set). Source: docs/agents/hermes/scripts/sentry-triage-sweep.sh. Probed on /status as service `cron.sentry-triage`.",
3368
+ probeConfig: {
3369
+ cadenceMs: 24 * 60 * MINUTE_MS,
3370
+ cronName: "fluncle-sentry-triage",
3371
+ kind: "cron",
3372
+ schedule: { time: "03:30", tz: "Europe/Amsterdam" }
3373
+ },
3374
+ statusDescription: "nightly triage of production errors → fix PRs",
3375
+ title: "Sentry triage",
3376
+ weights: { status: "secondary" }
3335
3377
  }
3336
3378
  ];
3337
3379
  });
@@ -9779,18 +9821,6 @@ async function runBackfillLastfm(options, backfillLastfmCommand2) {
9779
9821
  process.exitCode = 1;
9780
9822
  }
9781
9823
  }
9782
- function anchorOutcomeSuffix(outcome) {
9783
- switch (outcome) {
9784
- case "throttled":
9785
- return " (Spotify throttled the fill; the anchor breaker is pacing it)";
9786
- case "unauthorized":
9787
- return " (Spotify grant is gone \u2014 reconnect Spotify from /admin to resume anchoring)";
9788
- case "breaker_open":
9789
- return " (anchor fill PAUSED on its breaker; it resumes after the cooldown)";
9790
- default:
9791
- return "";
9792
- }
9793
- }
9794
9824
  async function runCrawlCatalogue(options, crawlCatalogueCommand2) {
9795
9825
  const limit = parseListLimit(options.limit);
9796
9826
  const maxHop = Number.parseInt(options.maxHop ?? "2", 10);
@@ -9802,7 +9832,6 @@ async function runCrawlCatalogue(options, crawlCatalogueCommand2) {
9802
9832
  } else {
9803
9833
  console.log(`Expanded ${result.expanded} node(s) at hop \u2264 ${result.maxHop}: ${result.tracksFound} track(s) found, ${result.tracksWritten} written, ${result.tracksSkipped} already held.`);
9804
9834
  console.log(` +${result.seeded} seed(s), +${result.nodesEnqueued} node(s) enqueued, ${result.frontierPending} pending, ${result.failed} failed.`);
9805
- console.log(` ${result.anchorsFilled} Spotify anchor(s) filled${anchorOutcomeSuffix(result.anchorOutcome)}.`);
9806
9835
  if (result.seedsRearmed > 0) {
9807
9836
  console.log(` Re-armed ${result.seedsRearmed} enabled seed label(s) to re-check for new releases.`);
9808
9837
  }
@@ -9825,14 +9854,6 @@ async function runCrawlStatus(options, crawlStatusCommand2) {
9825
9854
  }
9826
9855
  const { frontier, frontierByKind } = result;
9827
9856
  console.log(`Catalogue: ${result.catalogueTracks} uncertified track(s); ${result.anchorsPending} awaiting a Spotify anchor.`);
9828
- const { spotifyAnchor } = result;
9829
- if (spotifyAnchor.tripped) {
9830
- const mins = Math.ceil(spotifyAnchor.cooldownRemainingMs / 60000);
9831
- 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";
9832
- console.log(` Spotify anchor PAUSED (${why}); breaker cools in ~${mins} min.`);
9833
- } else if (spotifyAnchor.consecutiveFailures > 0) {
9834
- console.log(` Spotify anchor wobbling (${spotifyAnchor.consecutiveFailures} failing pass(es); reason: ${spotifyAnchor.reason ?? "unknown"}).`);
9835
- }
9836
9857
  console.log(`Frontier: ${frontier.pending} pending, ${frontier.done} done, ${frontier.failed} failed, ${frontier.skipped} skipped (${frontierByKind.label} label, ${frontierByKind.artist} artist, ${frontierByKind.release} release).`);
9837
9858
  console.log(`Seeds (${result.seedLabels.length}): ${result.seedLabels.join(", ") || "none enabled"}.`);
9838
9859
  console.log(`Awaiting your ruling: ${result.labelsUndecided} label(s).`);
package/package.json CHANGED
@@ -31,5 +31,5 @@
31
31
  "url": "git+https://github.com/mauricekleine/fluncle.git"
32
32
  },
33
33
  "type": "module",
34
- "version": "0.161.0"
34
+ "version": "0.162.0"
35
35
  }