fluncle 0.128.0 → 0.130.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 +158 -2
  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.128.0".trim() ? "0.128.0".trim() : "0.1.0";
564
+ currentVersion = "0.130.0".trim() ? "0.130.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.128.0".trim() ? "0.128.0".trim() : "0.1.0";
2192
+ currentVersion2 = "0.130.0".trim() ? "0.130.0".trim() : "0.1.0";
2193
2193
  });
2194
2194
 
2195
2195
  // ../../packages/registry/src/index.ts
@@ -2346,6 +2346,34 @@ var init_src = __esm(() => {
2346
2346
  url: `${SITE}/artist`,
2347
2347
  weights: { ssh: "secondary", web: "secondary" }
2348
2348
  },
2349
+ {
2350
+ discoveryUrl: `${SITE}/llms.txt`,
2351
+ exposedContent: [
2352
+ "/labels — every record label Fluncle has found a banger on",
2353
+ "/label/:slug — one label: its findings, the artists on it, and the rest of its catalogue"
2354
+ ],
2355
+ kind: "web_route",
2356
+ name: "web.labels",
2357
+ operatorNotes: "The label half of the graph (log ↔ artist ↔ label ↔ album). The INDEX is probeable (always 200); the slug page is not, so the probe targets /labels. A label below the renderable-track floor renders noindex + stays out of the sitemap (the ARTIST_INDEX_MIN_FINDINGS precedent). The page is BLIND to the label's crawl seed_state — that is crawl scope, never storage (docs/label-entity.md); the operator's ruling station is /admin/labels.",
2358
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2359
+ route: "/labels",
2360
+ url: `${SITE}/labels`,
2361
+ weights: { web: "secondary" }
2362
+ },
2363
+ {
2364
+ discoveryUrl: `${SITE}/llms.txt`,
2365
+ exposedContent: [
2366
+ "/albums — every record Fluncle has found a banger on",
2367
+ "/album/:slug — one record: its findings, its artists, its label, and the rest of its tracklist"
2368
+ ],
2369
+ kind: "web_route",
2370
+ name: "web.albums",
2371
+ 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 catalogue — an album earns a page by carrying a finding (docs/album-entity.md).",
2372
+ probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
2373
+ route: "/albums",
2374
+ url: `${SITE}/albums`,
2375
+ weights: { web: "secondary" }
2376
+ },
2349
2377
  {
2350
2378
  exposedContent: [
2351
2379
  "/galaxies — the browse-by-feel lens: the archive grouped into operator-named sonic galaxies (k-means over the MuQ audio embedding space)",
@@ -2982,6 +3010,17 @@ var init_src = __esm(() => {
2982
3010
  probeConfig: { cadenceMs: 20 * MINUTE_MS, cronName: "fluncle-clip-drip", kind: "cron" },
2983
3011
  weights: { status: "hidden" }
2984
3012
  },
3013
+ {
3014
+ command: "fluncle admin publish pause",
3015
+ exposedContent: [
3016
+ "advance one freshly-rendered finding into the publish push — YouTube Short + TikTok inbox draft (--no-agent, Worker HTTP)"
3017
+ ],
3018
+ kind: "cron",
3019
+ name: "cron.publish-advance",
3020
+ operatorNotes: "every 30m. Pure HTTP trigger, zero LLM tokens. The last autonomy gap: render finishes → this pushes, with no operator beat between. Admin tier (needs the Worker's Postiz key, which the box never sees — the box only triggers; the `drip_clips` / `capture_post_urls` precedent). SHIPS DARK: the Worker's kill switch is DEFAULT-DENY (only an explicit `false` in the `publish_advance_paused` setting runs it), so the timer ticks and posts nothing until `fluncle admin publish resume`. The Worker reads the switch FIRST, then advances at most ONE ready finding — both masters finalized, 15m settled, the whole bundle served on R2, the (track, platform) row CLAIMED atomically before any Postiz call, a rolling-24h cap of 6 pushes. A failed push is left `failed` for the operator and never auto-retried. Source: docs/agents/hermes/scripts/publish-advance-sweep.sh. Probed on /status as cron.publish-advance.",
3021
+ probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-publish-advance", kind: "cron" },
3022
+ weights: { status: "hidden" }
3023
+ },
2985
3024
  {
2986
3025
  command: "fluncle admin tracks queue",
2987
3026
  exposedContent: [
@@ -3531,6 +3570,7 @@ var init_track2 = __esm(() => {
3531
3570
  var exports_admin_tracks = {};
3532
3571
  __export(exports_admin_tracks, {
3533
3572
  vehiclesCommand: () => vehiclesCommand,
3573
+ trackWorkCommand: () => trackWorkCommand,
3534
3574
  requeueAnalysisCommand: () => requeueAnalysisCommand,
3535
3575
  queueCommand: () => queueCommand,
3536
3576
  observeQueueCommand: () => observeQueueCommand,
@@ -3632,6 +3672,15 @@ async function embedQueueCommand(limit) {
3632
3672
  async function captureQueueCommand(limit) {
3633
3673
  return fetchAdminTracks({ captureQueue: true, max: limit, order: "desc" });
3634
3674
  }
3675
+ async function trackWorkCommand(options) {
3676
+ const params = new URLSearchParams({
3677
+ kind: options.kind,
3678
+ limit: String(Math.min(Math.max(1, options.limit), 200)),
3679
+ scope: options.scope
3680
+ });
3681
+ const response = await adminApiGet(`/api/admin/tracks/work?${params.toString()}`);
3682
+ return response.tracks ?? [];
3683
+ }
3635
3684
  async function requeueAnalysisCommand(options) {
3636
3685
  const findings = await fetchAdminTracks({ max: options.max, order: "asc" });
3637
3686
  const stale = findings.filter((track) => track.analyzedFrom !== "full");
@@ -4097,6 +4146,23 @@ var init_mixtape_youtube2 = __esm(() => {
4097
4146
  init_output();
4098
4147
  });
4099
4148
 
4149
+ // src/commands/publish.ts
4150
+ var exports_publish = {};
4151
+ __export(exports_publish, {
4152
+ publishAdvancePauseCommand: () => publishAdvancePauseCommand,
4153
+ publishAdvanceCommand: () => publishAdvanceCommand
4154
+ });
4155
+ async function publishAdvanceCommand() {
4156
+ return adminApiPost("/api/admin/social/publish/advance", {});
4157
+ }
4158
+ async function publishAdvancePauseCommand(paused) {
4159
+ const response = await adminApiPut("/api/admin/social/publish/advance/state", { paused });
4160
+ return response.paused;
4161
+ }
4162
+ var init_publish = __esm(() => {
4163
+ init_api();
4164
+ });
4165
+
4100
4166
  // ../../packages/contracts/src/util/multipart.ts
4101
4167
  function planMultipart(contentLength, partSize = DEFAULT_PART_SIZE) {
4102
4168
  if (!Number.isInteger(contentLength) || contentLength <= 0) {
@@ -7960,6 +8026,10 @@ function addAdminCommands(program2) {
7960
8026
  const { captureQueueCommand: captureQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7961
8027
  await runAdminCaptureQueue(options, captureQueueCommand2);
7962
8028
  });
8029
+ adminTracks.command("work").description("The audio pipeline's worklist for one stage, in capture-priority order").requiredOption("--kind <kind>", "Pipeline stage: analyze | capture | embed").option("--scope <scope>", "Which half of the archive: all | catalogue | findings", "all").option("--limit <limit>", "Rows to show", "10").option("--json", "Print JSON", false).action(async (options) => {
8030
+ const { trackWorkCommand: trackWorkCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
8031
+ await runAdminTrackWork(options, trackWorkCommand2);
8032
+ });
7963
8033
  adminTracks.command("requeue-analysis").description("Re-queue findings whose BPM/key are preview-grade (dry-run; --apply to flip)").option("--apply", "Actually flip statuses (default is a dry-run preview)", false).option("--limit <limit>", "Cap the archive walk (default: the whole archive)").option("--json", "Print JSON", false).action(async (options) => {
7964
8034
  const { requeueAnalysisCommand: requeueAnalysisCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7965
8035
  await runAdminRequeueAnalysis(options, requeueAnalysisCommand2);
@@ -8065,6 +8135,22 @@ function addAdminCommands(program2) {
8065
8135
  const { mixtapeResyncCommand: mixtapeResyncCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
8066
8136
  await runMixtapeResync(idOrLogId, options, mixtapeResyncCommand2);
8067
8137
  });
8138
+ const adminPublish = configureCommand(admin.command("publish").description("Render \u2192 publish auto-advance commands"));
8139
+ adminPublish.action(() => {
8140
+ adminPublish.outputHelp();
8141
+ });
8142
+ adminPublish.command("advance").description("Run one tick of the render \u2192 publish auto-advance (kill-switch aware)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
8143
+ const { publishAdvanceCommand: publishAdvanceCommand2 } = await Promise.resolve().then(() => (init_publish(), exports_publish));
8144
+ await runPublishAdvance(options, publishAdvanceCommand2);
8145
+ });
8146
+ adminPublish.command("pause").description("Pause the render \u2192 publish auto-advance, the kill switch (operator)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
8147
+ const { publishAdvancePauseCommand: publishAdvancePauseCommand2 } = await Promise.resolve().then(() => (init_publish(), exports_publish));
8148
+ await runPublishAdvancePause(true, options, publishAdvancePauseCommand2);
8149
+ });
8150
+ adminPublish.command("resume").description("Resume the render \u2192 publish auto-advance, clear the kill switch (operator)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
8151
+ const { publishAdvancePauseCommand: publishAdvancePauseCommand2 } = await Promise.resolve().then(() => (init_publish(), exports_publish));
8152
+ await runPublishAdvancePause(false, options, publishAdvancePauseCommand2);
8153
+ });
8068
8154
  const adminClips = configureCommand(admin.command("clips").description("Mixtape clip (Fluncle Studio) commands"));
8069
8155
  adminClips.action(() => {
8070
8156
  adminClips.outputHelp();
@@ -9116,6 +9202,39 @@ async function runClipsDripPause(paused, options, clipDripPauseCommand2) {
9116
9202
  }
9117
9203
  console.log(result ? "The drip-feed is paused." : "The drip-feed is running.");
9118
9204
  }
9205
+ async function runPublishAdvance(options, publishAdvanceCommand2) {
9206
+ const result = await publishAdvanceCommand2();
9207
+ if (options.json) {
9208
+ printJson(result);
9209
+ return;
9210
+ }
9211
+ if (result.paused) {
9212
+ console.log("Auto-advance is paused \u2014 nothing pushed.");
9213
+ return;
9214
+ }
9215
+ if (result.pushed.length === 0 && result.held.length === 0 && result.failed.length === 0) {
9216
+ console.log("Nothing ready to advance.");
9217
+ return;
9218
+ }
9219
+ for (const push of result.pushed) {
9220
+ console.log(`Pushed ${push.logId} to ${push.platform} (${push.status}).`);
9221
+ }
9222
+ for (const held of result.held) {
9223
+ const detail = held.missing?.length ? ` (${held.missing.join(", ")})` : "";
9224
+ console.log(`Held ${held.platform} for ${held.trackId}: ${held.reason}${detail}.`);
9225
+ }
9226
+ for (const failure of result.failed) {
9227
+ console.log(`Failed ${failure.platform} for ${failure.trackId} \u2014 left for the operator.`);
9228
+ }
9229
+ }
9230
+ async function runPublishAdvancePause(paused, options, publishAdvancePauseCommand2) {
9231
+ const result = await publishAdvancePauseCommand2(paused);
9232
+ if (options.json) {
9233
+ printJson({ ok: true, paused: result });
9234
+ return;
9235
+ }
9236
+ console.log(result ? "The publish auto-advance is paused." : "The publish auto-advance is running.");
9237
+ }
9119
9238
  async function runMixtapeGet(idOrLogId, options, mixtapeGetCommand2) {
9120
9239
  if (!idOrLogId) {
9121
9240
  throw new Error("Missing mixtape id or log id. Usage: fluncle admin mixtapes get <id|logId>");
@@ -9629,6 +9748,42 @@ async function runAdminCaptureQueue(options, captureQueueCommand2) {
9629
9748
  console.log(trackRows2(tracks).join(`
9630
9749
  `));
9631
9750
  }
9751
+ var TRACK_WORK_KINDS = new Set(["analyze", "capture", "embed"]);
9752
+ var TRACK_WORK_SCOPES = new Set(["all", "catalogue", "findings"]);
9753
+ async function runAdminTrackWork(options, trackWorkCommand2) {
9754
+ const kind = options.kind?.trim().toLowerCase() ?? "";
9755
+ const scope = options.scope?.trim().toLowerCase() ?? "all";
9756
+ if (!TRACK_WORK_KINDS.has(kind)) {
9757
+ console.error(`--kind must be one of: analyze, capture, embed (got "${options.kind}")`);
9758
+ process.exitCode = 1;
9759
+ return;
9760
+ }
9761
+ if (!TRACK_WORK_SCOPES.has(scope)) {
9762
+ console.error(`--scope must be one of: all, catalogue, findings (got "${options.scope}")`);
9763
+ process.exitCode = 1;
9764
+ return;
9765
+ }
9766
+ const tracks = await trackWorkCommand2({
9767
+ kind,
9768
+ limit: parseListLimit(options.limit),
9769
+ scope
9770
+ });
9771
+ if (options.json) {
9772
+ printJson({ ok: true, tracks });
9773
+ return;
9774
+ }
9775
+ if (tracks.length === 0) {
9776
+ console.log(`Nothing to ${kind}. The ${scope} queue is drained.`);
9777
+ return;
9778
+ }
9779
+ const noun = tracks.length === 1 ? "track" : "tracks";
9780
+ console.log(`${tracks.length} ${noun} to ${kind} (${scope}), in drain order:`);
9781
+ for (const track of tracks) {
9782
+ const who = track.logId ?? `${track.trackId} \xB7 catalogue`;
9783
+ const rung = track.capturePriority === null ? "" : ` \xB7 p${track.capturePriority}`;
9784
+ console.log(` ${who}${rung} \u2014 ${track.artists.join(", ")} \u2014 ${track.title}`);
9785
+ }
9786
+ }
9632
9787
  var REQUEUE_ANALYSIS_ARCHIVE_CAP = 1e6;
9633
9788
  function parseRequeueAnalysisLimit(value) {
9634
9789
  if (value === undefined) {
@@ -9970,6 +10125,7 @@ var stringOptions = new Set([
9970
10125
  "--scheduled-for",
9971
10126
  "--script",
9972
10127
  "--script-file",
10128
+ "--scope",
9973
10129
  "--seed",
9974
10130
  "--soundcloud-url",
9975
10131
  "--source",
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.128.0"
34
+ "version": "0.130.0"
35
35
  }