fluncle 0.157.0 → 0.159.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 +104 -5
  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.157.0".trim() ? "0.157.0".trim() : "0.1.0";
564
+ currentVersion = "0.159.0".trim() ? "0.159.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.157.0".trim() ? "0.157.0".trim() : "0.1.0";
2192
+ currentVersion2 = "0.159.0".trim() ? "0.159.0".trim() : "0.1.0";
2193
2193
  });
2194
2194
 
2195
2195
  // ../../packages/registry/src/index.ts
@@ -2349,11 +2349,11 @@ var init_src = __esm(() => {
2349
2349
  {
2350
2350
  discoveryUrl: `${SITE}/llms.txt`,
2351
2351
  exposedContent: [
2352
- "/artist/:slug — one artist's page: every published finding from that artist, plus their identity links"
2352
+ "/artist/:slug — one artist's page: its findings, its identity links, and the rest of its catalogue"
2353
2353
  ],
2354
2354
  kind: "web_route",
2355
2355
  name: "web.artist",
2356
- operatorNotes: "Slug is real-name kebab-case (e.g. /artist/dbridge). Read-only; only artists with at least one published finding resolve (others 404). No probeConfig — the route is slug-parameterized, so there is no fixed URL to GET-probe.",
2356
+ operatorNotes: "Slug is real-name kebab-case (e.g. /artist/dbridge). Read-only; an artist earns a page on its CONTENT exactly as a label/album does — a row renders (a findings-free discovered artist included), a slug with no row 404s, and a page below the thin-content floor renders noindex + stays out of the sitemap (the ARTIST_INDEX_MIN_FINDINGS precedent). No probeConfig — the route is slug-parameterized, so there is no fixed URL to GET-probe.",
2357
2357
  route: "/artist",
2358
2358
  url: `${SITE}/artist`,
2359
2359
  weights: { ssh: "secondary", web: "secondary" }
@@ -2380,7 +2380,7 @@ var init_src = __esm(() => {
2380
2380
  ],
2381
2381
  kind: "web_route",
2382
2382
  name: "web.albums",
2383
- operatorNotes: "The album half of the graph, and the node that closes it: the album page carries the album → label edge (a link, plus `albumRelease.recordLabel` in its MusicAlbum JSON-LD). The INDEX is probeable (always 200); the slug page is not. Bounded by the archive, not the cataloguean album earns a page by carrying a finding (docs/album-entity.md).",
2383
+ operatorNotes: "The album half of the graph, and the node that closes it: the album page carries the album → label edge (a link, plus `albumRelease.recordLabel` in its MusicAlbum JSON-LD). The INDEX is probeable (always 200); the slug page is not. The `/albums` index stays archive-bounded (findings-joined), but a crawl-minted findings-free album is PUBLIC on its content a row renders its page, a below-floor page renders noindex + stays out of the sitemap, like a discovered label (docs/album-entity.md).",
2384
2384
  probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2385
2385
  route: "/albums",
2386
2386
  url: `${SITE}/albums`,
@@ -2969,6 +2969,23 @@ var init_src = __esm(() => {
2969
2969
  title: "Track ranking",
2970
2970
  weights: { status: "hidden" }
2971
2971
  },
2972
+ {
2973
+ command: "fluncle admin frontier refresh",
2974
+ exposedContent: [
2975
+ "re-mirror every crew member's Fluncle's Frontier playlist from their current recommendations"
2976
+ ],
2977
+ kind: "cron",
2978
+ name: "cron.frontier-refresh",
2979
+ operatorNotes: "weekly (Fri 07:00 Amsterdam), run by a rave-02 HOST systemd timer (docs/agents/hermes/frontier-refresh-timer/). E2, the public recommendation machine: every verified user can mint ONE public 'Fluncle's Frontier' playlist on Fluncle's OWN Spotify account (no per-user OAuth), holding THEIR recommendations (the E1 blend); this cron is the Discover-Weekly-style refresh that keeps each one current. The walk + the Spotify item replaces all happen inside the Worker; the sweep just fires one `fluncle admin frontier refresh`. It respects the DEFAULT-DENY `frontier.minting` kill switch (a closed switch touches nothing), skips playlists whose recommendation set is unchanged (a per-row URI-hash mirror guard), and creates no new public authority — every playlist it touches already exists, minted by its own owner. `refresh_frontier_playlists` is AGENT tier, so the box's existing agent-scoped token drives it: NO new secret. Zero LLM tokens. Source: docs/agents/hermes/scripts/frontier-refresh-sweep.*. See docs/the-ear.md § Fluncle's Frontier.",
2980
+ probeConfig: {
2981
+ cadenceMs: 7 * 24 * 60 * MINUTE_MS,
2982
+ cronName: "fluncle-frontier-refresh",
2983
+ kind: "cron"
2984
+ },
2985
+ statusDescription: "refreshes every crew member's Frontier playlist",
2986
+ title: "Frontier refresh",
2987
+ weights: { status: "hidden" }
2988
+ },
2972
2989
  {
2973
2990
  command: "fluncle admin backfills label-images",
2974
2991
  exposedContent: [
@@ -3073,6 +3090,19 @@ var init_src = __esm(() => {
3073
3090
  title: "Label bios",
3074
3091
  weights: { status: "hidden" }
3075
3092
  },
3093
+ {
3094
+ command: "fluncle admin albums describe --queue",
3095
+ exposedContent: [
3096
+ "auto-author the /album/<slug> voiced bio, fill-empty-only (hybrid: one claude -p call)"
3097
+ ],
3098
+ kind: "cron",
3099
+ name: "cron.album-bio",
3100
+ operatorNotes: "every 30m. Hybrid --no-agent; one claude -p authors the paragraph, its grounding assembled WORKER-side (Firecrawl facts + the album's finding titles) via the draft-bio op. Never clobbers an operator bio. Live. Source: docs/agents/hermes/scripts/{entity-bio-sweep.ts,album-bio-sweep.sh}.",
3101
+ probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-album-bio", kind: "cron" },
3102
+ statusDescription: "writes each album's voiced bio",
3103
+ title: "Album bios",
3104
+ weights: { status: "hidden" }
3105
+ },
3076
3106
  {
3077
3107
  command: "fluncle admin submissions triage",
3078
3108
  exposedContent: [
@@ -5871,6 +5901,19 @@ var init_admin_observations = __esm(() => {
5871
5901
  init_api();
5872
5902
  });
5873
5903
 
5904
+ // src/commands/admin-frontier.ts
5905
+ var exports_admin_frontier = {};
5906
+ __export(exports_admin_frontier, {
5907
+ frontierRefreshCommand: () => frontierRefreshCommand
5908
+ });
5909
+ async function frontierRefreshCommand(options) {
5910
+ const limit = options.limit ? Number.parseInt(options.limit, 10) : undefined;
5911
+ return adminApiPost("/api/admin/frontier-playlists/refresh", limit ? { limit } : {});
5912
+ }
5913
+ var init_admin_frontier = __esm(() => {
5914
+ init_api();
5915
+ });
5916
+
5874
5917
  // src/commands/admin-catalogue.ts
5875
5918
  var exports_admin_catalogue = {};
5876
5919
  __export(exports_admin_catalogue, {
@@ -6092,6 +6135,28 @@ var init_admin_covers = __esm(() => {
6092
6135
  init_api();
6093
6136
  });
6094
6137
 
6138
+ // src/commands/admin-albums.ts
6139
+ var exports_admin_albums = {};
6140
+ __export(exports_admin_albums, {
6141
+ draftAlbumBioCommand: () => draftAlbumBioCommand,
6142
+ describeAlbumCommand: () => describeAlbumCommand,
6143
+ albumsBioQueueCommand: () => albumsBioQueueCommand
6144
+ });
6145
+ async function describeAlbumCommand(slug, options) {
6146
+ return adminApiPost(`/api/admin/albums/${encodeURIComponent(slug)}/bio`, buildBioBody2(options));
6147
+ }
6148
+ async function draftAlbumBioCommand(slug) {
6149
+ return adminApiGet(`/api/admin/albums/${encodeURIComponent(slug)}/bio-draft`);
6150
+ }
6151
+ async function albumsBioQueueCommand(limit) {
6152
+ const response = await adminApiGet(`/api/admin/albums/bio-queue?limit=${limit}`);
6153
+ return response.albums;
6154
+ }
6155
+ var init_admin_albums = __esm(() => {
6156
+ init_api();
6157
+ init_admin_artists2();
6158
+ });
6159
+
6095
6160
  // src/commands/preview-migration.ts
6096
6161
  var exports_preview_migration = {};
6097
6162
  __export(exports_preview_migration, {
@@ -9076,6 +9141,23 @@ function addAdminCommands(program2) {
9076
9141
  const { observationGateCommand: observationGateCommand2 } = await Promise.resolve().then(() => (init_admin_observations(), exports_admin_observations));
9077
9142
  await runObservationGate(options, observationGateCommand2);
9078
9143
  });
9144
+ const frontier = configureCommand(admin.command("frontier").description("The Frontier playlists (per-user recommendations)"));
9145
+ frontier.action(() => {
9146
+ frontier.outputHelp();
9147
+ });
9148
+ frontier.command("refresh").description("Re-mirror every crew member's Frontier playlist from their recommendations").option("--limit <limit>", "Playlists per tick (default 500)").option("--json", "Print JSON", false).action(async (options) => {
9149
+ const { frontierRefreshCommand: frontierRefreshCommand2 } = await Promise.resolve().then(() => (init_admin_frontier(), exports_admin_frontier));
9150
+ const summary = await frontierRefreshCommand2(options);
9151
+ if (options.json) {
9152
+ printJson(summary);
9153
+ return;
9154
+ }
9155
+ if (summary.switchOff) {
9156
+ console.log("Frontier minting is paused (the kill switch is closed). Nothing refreshed.");
9157
+ return;
9158
+ }
9159
+ console.log(`Walked ${summary.total} playlist(s): ${summary.refreshed} refreshed, ${summary.unchanged} unchanged, ${summary.minted} minted, ${summary.skipped} skipped, ${summary.failed} failed.`);
9160
+ });
9079
9161
  const catalogue = configureCommand(admin.command("catalogue").description("The catalogue (uncertified tracks): the crawler + its ranking"));
9080
9162
  catalogue.action(() => {
9081
9163
  catalogue.outputHelp();
@@ -9284,6 +9366,23 @@ function addAdminCommands(program2) {
9284
9366
  const { draftLabelBioCommand: draftLabelBioCommand2 } = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
9285
9367
  await runEntityBioDraft("label", slug, options, draftLabelBioCommand2);
9286
9368
  });
9369
+ const albums = configureCommand(admin.command("albums").description("Album entity commands (the voiced bio)"));
9370
+ albums.action(() => {
9371
+ albums.outputHelp();
9372
+ });
9373
+ 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) => {
9374
+ if (options.queue) {
9375
+ const { albumsBioQueueCommand: albumsBioQueueCommand2 } = await Promise.resolve().then(() => (init_admin_albums(), exports_admin_albums));
9376
+ await runEntityBioQueue("album", options, albumsBioQueueCommand2);
9377
+ return;
9378
+ }
9379
+ const { describeAlbumCommand: describeAlbumCommand2 } = await Promise.resolve().then(() => (init_admin_albums(), exports_admin_albums));
9380
+ await runEntityDescribe("album", slug, options, describeAlbumCommand2);
9381
+ });
9382
+ albums.command("draft-bio").description("Assemble the Worker-grounded bio prompt for an album (the sweep's trigger)").argument("<slug>").option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
9383
+ const { draftAlbumBioCommand: draftAlbumBioCommand2 } = await Promise.resolve().then(() => (init_admin_albums(), exports_admin_albums));
9384
+ await runEntityBioDraft("album", slug, options, draftAlbumBioCommand2);
9385
+ });
9287
9386
  const migrations = configureCommand(admin.command("migrations").description("One-off operator data migrations"));
9288
9387
  migrations.action(() => {
9289
9388
  migrations.outputHelp();
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.157.0"
34
+ "version": "0.159.0"
35
35
  }