fluncle 0.179.0 → 0.181.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 +33 -97
  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.179.0".trim() ? "0.179.0".trim() : "0.1.0";
564
+ currentVersion = "0.181.0".trim() ? "0.181.0".trim() : "0.1.0";
565
565
  });
566
566
 
567
567
  // src/update-notifier.ts
@@ -2260,7 +2260,7 @@ function parseVersion2(version) {
2260
2260
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2261
2261
  var init_version2 = __esm(() => {
2262
2262
  init_output();
2263
- currentVersion2 = "0.179.0".trim() ? "0.179.0".trim() : "0.1.0";
2263
+ currentVersion2 = "0.181.0".trim() ? "0.181.0".trim() : "0.1.0";
2264
2264
  });
2265
2265
 
2266
2266
  // ../../packages/registry/src/index.ts
@@ -2504,6 +2504,14 @@ var init_src = __esm(() => {
2504
2504
  url: `${SITE}/privacy`,
2505
2505
  weights: { web: "tertiary" }
2506
2506
  },
2507
+ {
2508
+ exposedContent: ["the terms of use"],
2509
+ kind: "web_route",
2510
+ name: "web.terms",
2511
+ route: "/terms",
2512
+ url: `${SITE}/terms`,
2513
+ weights: { web: "tertiary" }
2514
+ },
2507
2515
  {
2508
2516
  exposedContent: ["the Galaxy game's front door (root rewrites to /galaxy)"],
2509
2517
  kind: "subdomain",
@@ -3121,13 +3129,12 @@ var init_src = __esm(() => {
3121
3129
  weights: { status: "hidden" }
3122
3130
  },
3123
3131
  {
3124
- command: "fluncle admin backfills label-releases",
3125
3132
  exposedContent: [
3126
- "tap Spotify's fresh releases for the operator's enabled seed labels → day-one uncertified catalogue rows"
3133
+ "tap day-one fresh releases for the operator's enabled seed labels → uncertified catalogue rows"
3127
3134
  ],
3128
3135
  kind: "cron",
3129
3136
  name: "cron.label-releases",
3130
- operatorNotes: "every 24h, run by a rave-02 HOST systemd timer (docs/agents/hermes/label-releases-timer/). The FRESHNESS TAP (D8): MusicBrainz WALKS the graph (cron.crawl) but lags a release ~2 weeks; Spotify has it day one, so this searches each ENABLED seed label's fresh releases (`label:\"<name>\" tag:new`) and mints METADATA-ONLY catalogue rows (a `tracks` row with no `findings` row) with their real release dates closing the /fresh lag cliff. The fuzzy `label:` search is gated on TWO signals before an album mints onto public /fresh: an album's Spotify artist must already be in our archive (`artists.spotify_artist_id` — the primary identity/genre anchor that stops cross-genre homonym junk) AND the album's ℗/© copyright must EXACTLY match the seed label (a brand-new artist's debut is skipped until the MB crawl backfills them). It certifies nothing, publishes nothing, and never widens the graph (no new labels, no artist hops). Deduped against the MB crawl from both directions (Spotify id/uri/ISRC + same-album title fold). Worker-paced, reusing the publish path's Spotify OAuth (no new secret; Apple was a measured 2/99 seed-label dead end). Zero LLM tokens. Source: docs/agents/hermes/scripts/label-releases-sweep.*. See docs/catalogue-crawler.md.",
3137
+ operatorNotes: "every 24h, run by a rave-02 HOST systemd timer (docs/agents/hermes/label-releases-timer/). The FRESHNESS TAP (D8): MusicBrainz WALKS the graph (cron.crawl) but lags a release ~2 weeks; Spotify has it day one, so this mints METADATA-ONLY catalogue rows (a `tracks` row with no `findings` row) for each ENABLED seed label's fresh releases with their real dates — closing the /fresh lag cliff. Runs OFF the official (dev-mode) Spotify app, which is rate-limited to death at its tier and must stay for user paths: the box reads the due seed labels (`list_label_releases_work`), runs an Apify Spotify-scraper actor per label (`label:\"<name>\" tag:new`), maps each result album (inline tracks[] + ISRCs + artists[]) to a candidate, and POSTs them to the agent-tier `backfill_label_releases` op where the WORKER re-runs the FULL gate (the box's verdict is never trusted) and mints. The gate: artist-grounding (an album's Spotify artist already in `artists.spotify_artist_id` — the PRIMARY anchor that stops cross-genre homonym junk, always required) AND, when the actor gives a label signal (album_label/copyright both null in albums-search mode, so grounding-only there), an exact fold-match to the seed name. It certifies nothing, publishes nothing, never widens the graph (no new labels, no artist hops). Deduped against the MB crawl from both directions (Spotify id/uri/ISRC + same-album title fold). Reuses the anchor sweep's APIFY_API_TOKEN (no new secret). Calls the oRPC HTTP endpoints directly (no CLI command the pinned box CLI would lack). Zero LLM tokens. Source: docs/agents/hermes/scripts/label-releases-sweep.*. See docs/catalogue-crawler.md.",
3131
3138
  probeConfig: {
3132
3139
  cadenceMs: 24 * 60 * MINUTE_MS,
3133
3140
  cronName: "fluncle-label-releases",
@@ -4128,7 +4135,6 @@ __export(exports_admin_tracks, {
4128
4135
  captureQueueCommand: () => captureQueueCommand,
4129
4136
  backfillRecordingMbidsCommand: () => backfillRecordingMbidsCommand,
4130
4137
  backfillLastfmCommand: () => backfillLastfmCommand,
4131
- backfillLabelReleasesCommand: () => backfillLabelReleasesCommand,
4132
4138
  backfillDiscogsCommand: () => backfillDiscogsCommand,
4133
4139
  backfillAppleMusicCommand: () => backfillAppleMusicCommand,
4134
4140
  backfillAppleCatalogueCommand: () => backfillAppleCatalogueCommand
@@ -4315,10 +4321,6 @@ async function backfillAppleCatalogueCommand(limit, dryRun) {
4315
4321
  const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
4316
4322
  return adminApiPost(`/api/admin/backfill/apple-catalogue?${params.toString()}`);
4317
4323
  }
4318
- async function backfillLabelReleasesCommand(limit, dryRun) {
4319
- const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
4320
- return adminApiPost(`/api/admin/backfill/label-releases?${params.toString()}`);
4321
- }
4322
4324
  async function backfillRecordingMbidsCommand(limit, dryRun, cursor) {
4323
4325
  const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
4324
4326
  if (cursor) {
@@ -6114,6 +6116,23 @@ var init_mixtape_mixcloud2 = __esm(() => {
6114
6116
  PICTURE_MAX_BYTES2 = 10 * 1024 * 1024;
6115
6117
  });
6116
6118
 
6119
+ // src/commands/auth-tiktok.ts
6120
+ var exports_auth_tiktok = {};
6121
+ __export(exports_auth_tiktok, {
6122
+ authTikTokCommand: () => authTikTokCommand
6123
+ });
6124
+ async function authTikTokCommand() {
6125
+ const response = await adminApiGet("/api/admin/tiktok/auth/start");
6126
+ console.log(`Open this TikTok authorization URL:
6127
+
6128
+ ${response.authUrl}
6129
+
6130
+ After approving access, TikTok returns to the Fluncle admin callback and stores the refresh token server-side.`);
6131
+ }
6132
+ var init_auth_tiktok = __esm(() => {
6133
+ init_api();
6134
+ });
6135
+
6117
6136
  // src/commands/auth-lastfm.ts
6118
6137
  var exports_auth_lastfm = {};
6119
6138
  __export(exports_auth_lastfm, {
@@ -9439,6 +9458,10 @@ function addAdminCommands(program2) {
9439
9458
  const { authMixcloudCommand: authMixcloudCommand3 } = await Promise.resolve().then(() => (init_mixtape_mixcloud2(), exports_mixtape_mixcloud2));
9440
9459
  await authMixcloudCommand3();
9441
9460
  });
9461
+ auth.command("tiktok").description("Authorize TikTok access (Display API per-video metrics)").action(async () => {
9462
+ const { authTikTokCommand: authTikTokCommand2 } = await Promise.resolve().then(() => (init_auth_tiktok(), exports_auth_tiktok));
9463
+ await authTikTokCommand2();
9464
+ });
9442
9465
  auth.command("lastfm").description("Authorize Last.fm access (love-on-add). Run once for the URL, then again with --token").option("--token <token>", "The approved request token, to mint the session key").action(async (options) => {
9443
9466
  const { authLastfmCommand: authLastfmCommand2 } = await Promise.resolve().then(() => (init_auth_lastfm(), exports_auth_lastfm));
9444
9467
  await authLastfmCommand2(options);
@@ -9698,10 +9721,6 @@ function addAdminCommands(program2) {
9698
9721
  const { backfillAppleCatalogueCommand: backfillAppleCatalogueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
9699
9722
  await runBackfillAppleCatalogue(options, backfillAppleCatalogueCommand2);
9700
9723
  });
9701
- backfill.command("label-releases").description("Tap Spotify's fresh releases for enabled seed labels into catalogue rows").option("--dry-run", "Report the labels that would be probed without any Spotify call", false).option("--limit <limit>", "Max enabled seed labels to probe per pass", "5").option("--json", "Print JSON", false).action(async (options) => {
9702
- const { backfillLabelReleasesCommand: backfillLabelReleasesCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
9703
- await runBackfillLabelReleases(options, backfillLabelReleasesCommand2);
9704
- });
9705
9724
  backfill.command("artists").description("Back-fill the artist entity (artists + track_artists) for existing findings").option("--dry-run", "Report which findings would be upserted without touching the DB", false).option("--limit <limit>", "Max findings to process", "50").option("--json", "Print JSON", false).action(async (options) => {
9706
9725
  const { backfillArtistsCommand: backfillArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
9707
9726
  await runBackfillArtists(options, backfillArtistsCommand2);
@@ -10395,89 +10414,6 @@ async function runBackfillAppleCatalogue(options, backfillAppleCatalogueCommand2
10395
10414
  process.exitCode = 1;
10396
10415
  }
10397
10416
  }
10398
- async function runBackfillLabelReleases(options, backfillLabelReleasesCommand2) {
10399
- const perPass = parseListLimit(options.limit);
10400
- const newTrackIds = [];
10401
- const labelSlugs = [];
10402
- const failedLabels = [];
10403
- let dryRun = options.dryRun;
10404
- let throttled = false;
10405
- let configured = true;
10406
- let labelsProbed = 0;
10407
- let albumsSeen = 0;
10408
- let albumsMatched = 0;
10409
- let newRows = 0;
10410
- let skippedKnown = 0;
10411
- let skippedUngrounded = 0;
10412
- let failedFetches = 0;
10413
- let fetchCeilingHit = false;
10414
- const MAX_PASSES = 100;
10415
- for (let pass = 0;pass < MAX_PASSES; pass += 1) {
10416
- const result = await backfillLabelReleasesCommand2(perPass, options.dryRun);
10417
- dryRun = result.dryRun;
10418
- configured = result.configured;
10419
- labelsProbed += result.labelsProbed;
10420
- albumsSeen += result.albumsSeen;
10421
- albumsMatched += result.albumsMatched;
10422
- newRows += result.newRows;
10423
- skippedKnown += result.skippedKnown;
10424
- skippedUngrounded += result.skippedUngrounded;
10425
- failedFetches += result.failedFetches;
10426
- fetchCeilingHit = fetchCeilingHit || result.fetchCeilingHit;
10427
- newTrackIds.push(...result.newTrackIds);
10428
- labelSlugs.push(...result.labelSlugs);
10429
- failedLabels.push(...result.failedLabels);
10430
- if (!options.json) {
10431
- const verb2 = result.dryRun ? "would probe" : "probed";
10432
- console.log(` \u2026${verb2} ${result.labelSlugs.length} label(s); ${result.albumsMatched} matched album(s); ${result.newRows} new; ${result.skippedKnown} known; ${result.skippedUngrounded} ungrounded; ${result.failedFetches} fetch-fail; ${result.failedLabels.length} search-fail`);
10433
- }
10434
- if (!result.configured) {
10435
- break;
10436
- }
10437
- if (result.rateLimited) {
10438
- throttled = true;
10439
- break;
10440
- }
10441
- if (result.dryRun) {
10442
- break;
10443
- }
10444
- if (result.labelsProbed === 0) {
10445
- break;
10446
- }
10447
- }
10448
- if (options.json) {
10449
- printSweepJson({
10450
- albumsMatched,
10451
- albumsSeen,
10452
- configured,
10453
- dryRun,
10454
- failedFetches,
10455
- failedLabels,
10456
- fetchCeilingHit,
10457
- labelSlugs,
10458
- labelsProbed,
10459
- newRows,
10460
- newTrackIds,
10461
- rateLimited: throttled,
10462
- skippedKnown,
10463
- skippedUngrounded
10464
- }, 0);
10465
- return;
10466
- }
10467
- if (!configured) {
10468
- console.log("Label-releases tap is a no-op \u2014 the Worker's Spotify grant is gone; reconnect Spotify.");
10469
- return;
10470
- }
10471
- const verb = dryRun ? "Would probe" : "Probed";
10472
- const probedCount = dryRun ? labelSlugs.length : labelsProbed;
10473
- console.log(`${verb} ${probedCount} enabled seed label(s); ${albumsMatched} matched album(s); ${newRows} new catalogue row(s); ${skippedKnown} already known; ${skippedUngrounded} dropped for no known artist; ${failedFetches} fetch-fail; ${failedLabels.length} search-fail.`);
10474
- if (fetchCeilingHit) {
10475
- console.log(" (a pass ended early on the single-fetch ceiling \u2014 resumes next tick)");
10476
- }
10477
- for (const slug of failedLabels) {
10478
- console.log(` search error: ${slug}`);
10479
- }
10480
- }
10481
10417
  async function runBackfillArtists(options, backfillArtistsCommand2) {
10482
10418
  const limit = parseListLimit(options.limit);
10483
10419
  const upserted = [];
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.179.0"
34
+ "version": "0.181.0"
35
35
  }