fluncle 0.171.0 → 0.173.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 +58 -3
  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.171.0".trim() ? "0.171.0".trim() : "0.1.0";
564
+ currentVersion = "0.173.0".trim() ? "0.173.0".trim() : "0.1.0";
565
565
  });
566
566
 
567
567
  // src/update-notifier.ts
@@ -2259,7 +2259,7 @@ function parseVersion2(version) {
2259
2259
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2260
2260
  var init_version2 = __esm(() => {
2261
2261
  init_output();
2262
- currentVersion2 = "0.171.0".trim() ? "0.171.0".trim() : "0.1.0";
2262
+ currentVersion2 = "0.173.0".trim() ? "0.173.0".trim() : "0.1.0";
2263
2263
  });
2264
2264
 
2265
2265
  // ../../packages/registry/src/index.ts
@@ -3499,6 +3499,24 @@ var init_src = __esm(() => {
3499
3499
  title: "Reach snapshot",
3500
3500
  weights: { status: "secondary" }
3501
3501
  },
3502
+ {
3503
+ command: "fluncle admin catalogue demand",
3504
+ exposedContent: [
3505
+ "nightly demand reorder — reads Simple Analytics pageviews for /artist + /label pages and reorders crawl/capture priority toward what real visitors looked at (--no-agent)"
3506
+ ],
3507
+ kind: "cron",
3508
+ name: "cron.demand",
3509
+ operatorNotes: "04:40 Amsterdam daily (just after the 04:00 reach snapshot — both daily analytics reads in one window). A bare trigger (the reach/rank shape): fires the AGENT-tier record_demand op once — the WORKER reads Simple Analytics for the /artist/<slug> + /label/<slug> pageviews over the trailing 30 days and REWRITES two derived reorder columns, tracks.demand_score (the capture queue's within-tier secondary sort) and crawl_frontier.demand_rank (the frontier pick's within-hop tiebreak). RANK-ORDER ONLY: it reorders within a tier and never overrides the capture_priority veto (a ruled-out label is never resurrected); the seed-allowlist crawl gate is untouched. Clear-then-set, so a same-window re-run is idempotent. Unprovisioned (no SIMPLE_ANALYTICS_API_KEY) it returns configured:false and is a clean no-op. Zero LLM tokens; the box's agent token drives it and the SA key lives Worker-side (no new box secret). Source: docs/agents/hermes/scripts/demand-sweep.*. See docs/catalogue-crawler.md § Demand.",
3510
+ probeConfig: {
3511
+ cadenceMs: 24 * 60 * MINUTE_MS,
3512
+ cronName: "fluncle-demand",
3513
+ kind: "cron",
3514
+ schedule: { time: "04:40", tz: "Europe/Amsterdam" }
3515
+ },
3516
+ statusDescription: "leans the catalogue toward what visitors looked at",
3517
+ title: "Demand reorder",
3518
+ weights: { status: "hidden" }
3519
+ },
3502
3520
  {
3503
3521
  exposedContent: [
3504
3522
  "daily catalogue-funnel snapshot — one row per UTC day of stage totals + queue depths + frontier counts behind /admin/funnel (--no-agent)"
@@ -6180,13 +6198,18 @@ __export(exports_admin_catalogue, {
6180
6198
  clearWrongAudioCommand: () => clearWrongAudioCommand,
6181
6199
  certifyTrackCommand: () => certifyTrackCommand,
6182
6200
  catalogueRankCommand: () => catalogueRankCommand,
6183
- catalogueListCommand: () => catalogueListCommand
6201
+ catalogueListCommand: () => catalogueListCommand,
6202
+ catalogueDemandCommand: () => catalogueDemandCommand
6184
6203
  });
6185
6204
  async function catalogueRankCommand(options) {
6186
6205
  const limit = options.limit ? Number.parseInt(options.limit, 10) : undefined;
6187
6206
  const response = await adminApiPost("/api/admin/catalogue/rank", limit ? { limit } : {});
6188
6207
  return { summary: response.summary, telescope: response.telescope };
6189
6208
  }
6209
+ async function catalogueDemandCommand() {
6210
+ const response = await adminApiPost("/api/admin/catalogue/demand", {});
6211
+ return response.summary;
6212
+ }
6190
6213
  async function catalogueListCommand(options) {
6191
6214
  const params = new URLSearchParams;
6192
6215
  params.set("lens", options.lens && CATALOGUE_LENSES.has(options.lens) ? options.lens : "ear");
@@ -6270,6 +6293,7 @@ var init_admin_reach = __esm(() => {
6270
6293
  var exports_admin_artists = {};
6271
6294
  __export(exports_admin_artists, {
6272
6295
  resolveArtistCommand: () => resolveArtistCommand,
6296
+ rankArtistsCommand: () => rankArtistsCommand,
6273
6297
  listArtistsCommand: () => listArtistsCommand,
6274
6298
  draftArtistBioCommand: () => draftArtistBioCommand,
6275
6299
  describeArtistCommand: () => describeArtistCommand,
@@ -6308,6 +6332,11 @@ async function listArtistsCommand(limit, cursor) {
6308
6332
  async function resolveArtistCommand(artistId) {
6309
6333
  return adminApiPost(`/api/admin/artists/${encodeURIComponent(artistId)}/resolve`);
6310
6334
  }
6335
+ async function rankArtistsCommand(options) {
6336
+ const limit = options.limit ? Number.parseInt(options.limit, 10) : undefined;
6337
+ const response = await adminApiPost("/api/admin/artists/rank", limit ? { limit } : {});
6338
+ return { summary: response.summary };
6339
+ }
6311
6340
  async function backfillArtistImagesCommand(limit, dryRun, cursor) {
6312
6341
  const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
6313
6342
  if (cursor) {
@@ -9470,6 +9499,19 @@ function addAdminCommands(program2) {
9470
9499
  const { catalogueRankCommand: catalogueRankCommand2 } = await Promise.resolve().then(() => (init_admin_catalogue(), exports_admin_catalogue));
9471
9500
  await runCatalogueRank(options, catalogueRankCommand2);
9472
9501
  });
9502
+ catalogue.command("demand").description("Reorder crawl/capture priority from Simple Analytics pageviews (one tick)").option("--json", "Print JSON", false).action(async (options) => {
9503
+ const { catalogueDemandCommand: catalogueDemandCommand2 } = await Promise.resolve().then(() => (init_admin_catalogue(), exports_admin_catalogue));
9504
+ const summary = await catalogueDemandCommand2();
9505
+ if (options.json) {
9506
+ printJson({ ok: true, summary });
9507
+ return;
9508
+ }
9509
+ if (!summary.configured) {
9510
+ console.log("Simple Analytics is not configured \u2014 demand left untouched (a clean no-op).");
9511
+ return;
9512
+ }
9513
+ console.log(`Read ${summary.pagesRead} page(s) over ${summary.window.start}\u2026${summary.window.end}. Demanded ${summary.demandedArtists} artist(s) + ${summary.demandedLabels} label(s) (${summary.unknownSlugs} unknown skipped); scored ${summary.tracksScored} track(s), promoted ${summary.frontierPromoted} frontier node(s).`);
9514
+ });
9473
9515
  catalogue.command("list").description("The ranked catalogue: closest to a finding (ear), next to capture, or wrong audio").option("--lens <lens>", "ear (default), capture, quarantine, unmatched, failed, or dismissed", "ear").option("--limit <limit>", "Rows (default 50, max 200)").option("--json", "Print JSON", false).action(async (options) => {
9474
9516
  const { catalogueListCommand: catalogueListCommand2 } = await Promise.resolve().then(() => (init_admin_catalogue(), exports_admin_catalogue));
9475
9517
  await runCatalogueList(options, catalogueListCommand2);
@@ -9657,6 +9699,10 @@ function addAdminCommands(program2) {
9657
9699
  const { describeArtistCommand: describeArtistCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
9658
9700
  await runEntityDescribe("artist", slug, options, describeArtistCommand2);
9659
9701
  });
9702
+ artists.command("rank").description("Run one tick of the similar-artists sweep (artist centroids + nearest neighbours)").option("--limit <limit>", "Stale artists per tick (default 100)").option("--json", "Print JSON", false).action(async (options) => {
9703
+ const { rankArtistsCommand: rankArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
9704
+ await runArtistsRank(options, rankArtistsCommand2);
9705
+ });
9660
9706
  artists.command("draft-bio").description("Assemble the Worker-grounded bio prompt for an artist (the sweep's trigger)").argument("<slug>").option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
9661
9707
  const { draftArtistBioCommand: draftArtistBioCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
9662
9708
  await runEntityBioDraft("artist", slug, options, draftArtistBioCommand2);
@@ -11485,6 +11531,15 @@ async function runGalaxies(slug, options, commands) {
11485
11531
  console.log(`${galaxy.name.padEnd(40)} ${String(galaxy.memberCount).padStart(3)} finding${galaxy.memberCount === 1 ? "" : "s"}`);
11486
11532
  }
11487
11533
  }
11534
+ async function runArtistsRank(options, rankArtistsCommand2) {
11535
+ const { summary } = await rankArtistsCommand2({ limit: options.limit });
11536
+ if (options.json) {
11537
+ printJson({ ok: true, summary });
11538
+ return;
11539
+ }
11540
+ const removed = summary.centroidsRemoved > 0 ? ` Purged ${summary.centroidsRemoved} orphan(s).` : "";
11541
+ console.log(`Ranked ${summary.centroidsComputed} artist(s); wrote ${summary.edgesWritten} edges.${removed} ${summary.remaining} still stale.`);
11542
+ }
11488
11543
  async function runCatalogueRank(options, catalogueRankCommand2) {
11489
11544
  const { summary, telescope } = await catalogueRankCommand2({ limit: options.limit });
11490
11545
  if (options.json) {
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.171.0"
34
+ "version": "0.173.0"
35
35
  }