fluncle 0.206.0 → 0.208.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 +40 -12
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -610,7 +610,7 @@ function parseVersion(version) {
610
610
  var currentVersion;
611
611
  var init_version = __esm(() => {
612
612
  init_output();
613
- currentVersion = "0.206.0".trim() ? "0.206.0".trim() : "0.1.0";
613
+ currentVersion = "0.208.0".trim() ? "0.208.0".trim() : "0.1.0";
614
614
  });
615
615
 
616
616
  // src/update-notifier.ts
@@ -2452,7 +2452,7 @@ function parseVersion2(version) {
2452
2452
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2453
2453
  var init_version2 = __esm(() => {
2454
2454
  init_output();
2455
- currentVersion2 = "0.206.0".trim() ? "0.206.0".trim() : "0.1.0";
2455
+ currentVersion2 = "0.208.0".trim() ? "0.208.0".trim() : "0.1.0";
2456
2456
  });
2457
2457
 
2458
2458
  // ../../packages/registry/src/index.ts
@@ -2677,6 +2677,20 @@ var init_src = __esm(() => {
2677
2677
  url: `${SITE}/galaxies`,
2678
2678
  weights: { ssh: "secondary", web: "secondary" }
2679
2679
  },
2680
+ {
2681
+ discoveryUrl: `${SITE}/llms.txt`,
2682
+ exposedContent: [
2683
+ "/identity — look a recording up by ISRC, MusicBrainz recording id, or Log ID",
2684
+ "/identity/:key — one recording's identifiers and platform links, each carrying whether Fluncle found it, looked and found nothing, will not look, or hands out no such link"
2685
+ ],
2686
+ kind: "web_route",
2687
+ name: "web.identity",
2688
+ operatorNotes: "The public face of the identity envelope (lib/server/identity-envelope.ts); the machine twin is `get_track`'s identity projection (?identity= / ?isrc= / ?mbid=), and both read the same module so page and API cannot diverge — except on Apple Music, the one audience-scoped field: the page reads `first-party` and renders an Apple link as /log does, the API answers `unsupported` because Apple's terms bar passing those links on. The DOOR is probeable (always 200, and a bare GET with no ?key= renders the explainer); the keyed page is not, and it renders `noindex, follow` on purpose — one recording is reachable under up to three identifiers, so indexing them would put three near-identical URLs in front of one answer at catalogue scale. Only the door is in the sitemap. Both the page's server fn and the op charge the SAME two dials (identity-dials.ts: 30/min + 1,000/day per caller), so the meter cannot be dodged by switching doors; a spent dial renders as a calm page state, never a fault. The keyed page answers 200 with an honest 'nothing under this identifier' rather than 404ing, because an unknown key is a real question with a real answer.",
2689
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2690
+ route: "/identity",
2691
+ url: `${SITE}/identity`,
2692
+ weights: { web: "tertiary" }
2693
+ },
2680
2694
  {
2681
2695
  exposedContent: [
2682
2696
  "the signed-in account door: a listener's Galaxy progress, the findings and sets they saved, the tracks they submitted, and their account settings"
@@ -3439,9 +3453,8 @@ var init_src = __esm(() => {
3439
3453
  ],
3440
3454
  kind: "app",
3441
3455
  name: "app.ios",
3442
- operatorNotes: "Fluncle for iOS (apps/mobile). PENDING App Store review DARK until approval, then flip `pending` + set the assigned listing URL. No probeConfig: a vendor store listing is not one of our own health-probeable endpoints (the box prober walks web/r2/dns/ssh + the crons, never an external GET), and Apple would bot-block or redirect a bare GET and read back as a false 'down' — the extension.lens ruling, which dropped its probeConfig on going live for the same reason.",
3443
- pending: true,
3444
- url: "https://apps.apple.com/search?term=fluncle",
3456
+ operatorNotes: "Fluncle for iOS (apps/mobile), LIVE on the App Store (approved 2026-07-29). Store listing reachability is Apple's, not ours, so it is not on the /status board the extension.lens ruling: a vendor store would bot-block or redirect a bare GET and read back as a false 'down'.",
3457
+ url: "https://apps.apple.com/app/id6790080540",
3445
3458
  weights: { web: "secondary" }
3446
3459
  },
3447
3460
  {
@@ -3768,13 +3781,15 @@ var init_src = __esm(() => {
3768
3781
  weights: { status: "hidden" }
3769
3782
  },
3770
3783
  {
3771
- command: "fluncle admin backfills discogs && fluncle admin backfills lastfm",
3772
- exposedContent: ["Discogs id + Last.fm love catalogue repair (--no-agent, Worker HTTP)"],
3784
+ command: "fluncle admin backfills discogs && fluncle admin backfills lastfm && fluncle admin backfills apple-music && fluncle admin backfills apple-catalogue",
3785
+ exposedContent: [
3786
+ "Discogs id + Last.fm love + Apple Music link repair, findings then catalogue (--no-agent, Worker HTTP)"
3787
+ ],
3773
3788
  kind: "cron",
3774
3789
  name: "cron.backfill",
3775
3790
  operatorNotes: "every 30m. Pure HTTP driving, zero LLM tokens. Agent tier.",
3776
3791
  probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-backfill", kind: "cron" },
3777
- statusDescription: "repairs Discogs ids and Last.fm loves",
3792
+ statusDescription: "repairs Discogs ids, Last.fm loves, and Apple Music links",
3778
3793
  title: "Metadata backfill",
3779
3794
  weights: { status: "hidden" }
3780
3795
  },
@@ -6836,6 +6851,9 @@ function buildBioBody(options) {
6836
6851
  if (options.dryRun) {
6837
6852
  body.dryRun = true;
6838
6853
  }
6854
+ if (options.finalAttempt) {
6855
+ body.finalAttempt = true;
6856
+ }
6839
6857
  if (typeof options.promptVersion === "number") {
6840
6858
  body.promptVersion = options.promptVersion;
6841
6859
  }
@@ -6890,6 +6908,9 @@ function buildBioBody2(options) {
6890
6908
  if (options.dryRun) {
6891
6909
  body.dryRun = true;
6892
6910
  }
6911
+ if (options.finalAttempt) {
6912
+ body.finalAttempt = true;
6913
+ }
6893
6914
  if (typeof options.promptVersion === "number") {
6894
6915
  body.promptVersion = options.promptVersion;
6895
6916
  }
@@ -10325,7 +10346,7 @@ function addAdminCommands(program2) {
10325
10346
  const { resolveArtistCommand: resolveArtistCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
10326
10347
  await runArtistResolve(artistId, options, resolveArtistCommand2);
10327
10348
  });
10328
- artists.command("describe").description("Author the voiced bio for an artist (fills an empty bio only)").argument("[slug]").option("--queue", "Show the bio worklist (artists with findings but no bio), oldest first", false).option("--limit <limit>", "Number of artists to show with --queue", "50").option("--bio <text>", "The voice-gated bio paragraph").option("--bio-file <file>", "Read the bio from a file").option("--prompt-version <n>", "The prompt-registry version that authored the bio (the sweep sends this; it is the bio's provenance)").option("--dry-run", "Run the voice gate and report the verdict without storing anything", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
10349
+ artists.command("describe").description("Author the voiced bio for an artist (fills an empty bio only)").argument("[slug]").option("--queue", "Show the bio worklist (artists with findings but no bio), oldest first", false).option("--limit <limit>", "Number of artists to show with --queue", "50").option("--bio <text>", "The voice-gated bio paragraph").option("--bio-file <file>", "Read the bio from a file").option("--prompt-version <n>", "The prompt-registry version that authored the bio (the sweep sends this; it is the bio's provenance)").option("--dry-run", "Run the voice gate and report the verdict without storing anything", false).option("--final-attempt", "The sweep's third and last authoring pass: store the draft even if the voice scan refuses it (the length bounds still apply, and the write is flagged for review)", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
10329
10350
  if (options.queue) {
10330
10351
  const { artistsBioQueueCommand: artistsBioQueueCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
10331
10352
  await runEntityBioQueue("artist", options, artistsBioQueueCommand2);
@@ -10346,7 +10367,7 @@ function addAdminCommands(program2) {
10346
10367
  labels.action(() => {
10347
10368
  labels.outputHelp();
10348
10369
  });
10349
- labels.command("describe").description("Author the voiced bio for a label (fills an empty bio only)").argument("[slug]").option("--queue", "Show the bio worklist (labels with findings but no bio), oldest first", false).option("--limit <limit>", "Number of labels to show with --queue", "50").option("--bio <text>", "The voice-gated bio paragraph").option("--bio-file <file>", "Read the bio from a file").option("--prompt-version <n>", "The prompt-registry version that authored the bio (the sweep sends this; it is the bio's provenance)").option("--dry-run", "Run the voice gate and report the verdict without storing anything", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
10370
+ labels.command("describe").description("Author the voiced bio for a label (fills an empty bio only)").argument("[slug]").option("--queue", "Show the bio worklist (labels with findings but no bio), oldest first", false).option("--limit <limit>", "Number of labels to show with --queue", "50").option("--bio <text>", "The voice-gated bio paragraph").option("--bio-file <file>", "Read the bio from a file").option("--prompt-version <n>", "The prompt-registry version that authored the bio (the sweep sends this; it is the bio's provenance)").option("--dry-run", "Run the voice gate and report the verdict without storing anything", false).option("--final-attempt", "The sweep's third and last authoring pass: store the draft even if the voice scan refuses it (the length bounds still apply, and the write is flagged for review)", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
10350
10371
  if (options.queue) {
10351
10372
  const { labelsBioQueueCommand: labelsBioQueueCommand2 } = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
10352
10373
  await runEntityBioQueue("label", options, labelsBioQueueCommand2);
@@ -10398,7 +10419,7 @@ function addAdminCommands(program2) {
10398
10419
  albums.action(() => {
10399
10420
  albums.outputHelp();
10400
10421
  });
10401
- albums.command("describe").description("Author the voiced bio for an album (fills an empty bio only)").argument("[slug]").option("--queue", "Show the bio worklist (albums with findings but no bio), oldest first", false).option("--limit <limit>", "Number of albums to show with --queue", "50").option("--bio <text>", "The voice-gated bio paragraph").option("--bio-file <file>", "Read the bio from a file").option("--prompt-version <n>", "The prompt-registry version that authored the bio (the sweep sends this; it is the bio's provenance)").option("--dry-run", "Run the voice gate and report the verdict without storing anything", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
10422
+ albums.command("describe").description("Author the voiced bio for an album (fills an empty bio only)").argument("[slug]").option("--queue", "Show the bio worklist (albums with findings but no bio), oldest first", false).option("--limit <limit>", "Number of albums to show with --queue", "50").option("--bio <text>", "The voice-gated bio paragraph").option("--bio-file <file>", "Read the bio from a file").option("--prompt-version <n>", "The prompt-registry version that authored the bio (the sweep sends this; it is the bio's provenance)").option("--dry-run", "Run the voice gate and report the verdict without storing anything", false).option("--final-attempt", "The sweep's third and last authoring pass: store the draft even if the voice scan refuses it (the length bounds still apply, and the write is flagged for review)", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
10402
10423
  if (options.queue) {
10403
10424
  const { albumsBioQueueCommand: albumsBioQueueCommand2 } = await Promise.resolve().then(() => (init_admin_albums(), exports_admin_albums));
10404
10425
  await runEntityBioQueue("album", options, albumsBioQueueCommand2);
@@ -10535,11 +10556,12 @@ async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
10535
10556
  async function runEntityDescribe(kind, slug, options, describeCommand) {
10536
10557
  const bio = options.bioFile ? readFileSync7(options.bioFile, "utf8") : options.bio;
10537
10558
  if (!slug || !bio || !bio.trim()) {
10538
- throw new Error(`Usage: fluncle admin ${kind}s describe <slug> (--bio <text> | --bio-file <file>) [--dry-run] [--json]`);
10559
+ throw new Error(`Usage: fluncle admin ${kind}s describe <slug> (--bio <text> | --bio-file <file>) [--dry-run] [--final-attempt] [--json]`);
10539
10560
  }
10540
10561
  const result = await describeCommand(slug, {
10541
10562
  bio: bio.trim(),
10542
10563
  dryRun: options.dryRun,
10564
+ finalAttempt: options.finalAttempt,
10543
10565
  promptVersion: parsePromptVersion(options.promptVersion)
10544
10566
  });
10545
10567
  if (options.json) {
@@ -10557,6 +10579,12 @@ async function runEntityDescribe(kind, slug, options, describeCommand) {
10557
10579
  }
10558
10580
  console.log(`Authored the bio for ${result.slug}:`);
10559
10581
  console.log(` ${result.bio}`);
10582
+ if (result.gateBypassed) {
10583
+ console.log(" FINAL ATTEMPT \u2014 the voice gate refused this bio and it was stored anyway:");
10584
+ for (const violation of result.voiceViolations ?? []) {
10585
+ console.log(` - ${violation}`);
10586
+ }
10587
+ }
10560
10588
  }
10561
10589
  async function runEntityBioQueue(kind, options, queueCommand2) {
10562
10590
  const limit = options.limit ? Number.parseInt(options.limit, 10) : 50;
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.206.0"
34
+ "version": "0.208.0"
35
35
  }