fluncle 0.96.0 → 0.98.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 +84 -4
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -489,11 +489,12 @@ function baseTitleMatches(findingTitle, candidateTitle) {
489
489
  }
490
490
  return true;
491
491
  }
492
- var VERSION_MARKER, REMIX_MARKER, VERSION_STOPWORDS;
492
+ var VERSION_MARKER, REMIX_MARKER, VERSION_STOPWORDS, TIKTOK_DRAFT_STALE_MS;
493
493
  var init_util = __esm(() => {
494
494
  VERSION_MARKER = /\b(mix|edit|version|remix|dub|vip|bootleg|rework|re-?edit|flip|refix|remaster(?:ed)?|instrumental)\b/i;
495
495
  REMIX_MARKER = /\b(remix|bootleg|vip|rework|re-?edit|flip|refix)\b/i;
496
496
  VERSION_STOPWORDS = new Set(["mix", "the", "and", "feat", "ft", "edit", "version", "remix"]);
497
+ TIKTOK_DRAFT_STALE_MS = 24 * 60 * 60 * 1000;
497
498
  });
498
499
 
499
500
  // src/output.ts
@@ -556,7 +557,7 @@ function parseVersion(version) {
556
557
  var currentVersion;
557
558
  var init_version = __esm(() => {
558
559
  init_output();
559
- currentVersion = "0.96.0".trim() ? "0.96.0".trim() : "0.1.0";
560
+ currentVersion = "0.98.0".trim() ? "0.98.0".trim() : "0.1.0";
560
561
  });
561
562
 
562
563
  // src/update-notifier.ts
@@ -2186,7 +2187,7 @@ function parseVersion2(version) {
2186
2187
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2187
2188
  var init_version2 = __esm(() => {
2188
2189
  init_output();
2189
- currentVersion2 = "0.96.0".trim() ? "0.96.0".trim() : "0.1.0";
2190
+ currentVersion2 = "0.98.0".trim() ? "0.98.0".trim() : "0.1.0";
2190
2191
  });
2191
2192
 
2192
2193
  // ../../packages/registry/src/index.ts
@@ -2736,6 +2737,17 @@ var init_src = __esm(() => {
2736
2737
  probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-enrich", kind: "cron" },
2737
2738
  weights: { status: "hidden" }
2738
2739
  },
2740
+ {
2741
+ command: "fluncle admin tracks embed --queue",
2742
+ exposedContent: [
2743
+ "MuQ-large audio embedding (1024-d) for sonic similarity + clusters (--no-agent, on-box torch)"
2744
+ ],
2745
+ kind: "cron",
2746
+ name: "cron.embed",
2747
+ operatorNotes: "every 5m. On-box MuQ (torch, ~16s/track), zero LLM tokens. Writes the vector via the agent-tier update_track. Source: docs/agents/hermes/scripts/embed-sweep.* + embed-track.py. See docs/audio-embedding-rfc.md.",
2748
+ probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-embed", kind: "cron" },
2749
+ weights: { status: "hidden" }
2750
+ },
2739
2751
  {
2740
2752
  command: "fluncle admin tracks context --queue",
2741
2753
  exposedContent: [
@@ -2835,6 +2847,16 @@ var init_src = __esm(() => {
2835
2847
  kind: "cron"
2836
2848
  },
2837
2849
  weights: { status: "secondary" }
2850
+ },
2851
+ {
2852
+ exposedContent: [
2853
+ "daily gzip dump of the prod database → a PRIVATE R2 bucket (owned off-site backup) + 30 daily / 12 monthly retention (--no-agent)"
2854
+ ],
2855
+ kind: "cron",
2856
+ name: "cron.backup",
2857
+ operatorNotes: "daily. An OWNED, off-Cloudflare backup: dumps prod Turso over the libSQL HTTP pipeline → gzip → a PRIVATE R2 bucket (never fluncle-videos, which is world-served at found.fluncle.com) + prune. Zero LLM tokens; talks to Turso + R2 directly (no fluncle CLI, no agent token). Turso's managed PITR is the belt; this is the braces. Restore is proven by apps/web/scripts/restore-drill.ts. Source: docs/agents/hermes/scripts/backup-sweep.*",
2858
+ probeConfig: { cadenceMs: 24 * 60 * MINUTE_MS, cronName: "fluncle-backup", kind: "cron" },
2859
+ weights: { status: "secondary" }
2838
2860
  }
2839
2861
  ];
2840
2862
  });
@@ -3068,6 +3090,9 @@ async function trackUpdateCommand(trackId, options) {
3068
3090
  if (options.features !== undefined) {
3069
3091
  body.features = options.features;
3070
3092
  }
3093
+ if (options.embedding !== undefined) {
3094
+ body.embedding = options.embedding;
3095
+ }
3071
3096
  if (options.note !== undefined) {
3072
3097
  body.note = options.note;
3073
3098
  }
@@ -3430,6 +3455,7 @@ __export(exports_admin_tracks, {
3430
3455
  noteQueueCommand: () => noteQueueCommand,
3431
3456
  listCommand: () => listCommand,
3432
3457
  enrichQueueCommand: () => enrichQueueCommand,
3458
+ embedQueueCommand: () => embedQueueCommand,
3433
3459
  contextQueueCommand: () => contextQueueCommand,
3434
3460
  backfillLastfmCommand: () => backfillLastfmCommand,
3435
3461
  backfillDiscogsCommand: () => backfillDiscogsCommand
@@ -3437,6 +3463,7 @@ __export(exports_admin_tracks, {
3437
3463
  async function fetchAdminTracks(options) {
3438
3464
  const {
3439
3465
  hasContext,
3466
+ hasEmbedding,
3440
3467
  hasKey,
3441
3468
  hasNote,
3442
3469
  hasObservation,
@@ -3456,6 +3483,9 @@ async function fetchAdminTracks(options) {
3456
3483
  if (hasKey !== undefined) {
3457
3484
  params.set("hasKey", String(hasKey));
3458
3485
  }
3486
+ if (hasEmbedding !== undefined) {
3487
+ params.set("hasEmbedding", String(hasEmbedding));
3488
+ }
3459
3489
  if (hasContext !== undefined) {
3460
3490
  params.set("hasContext", String(hasContext));
3461
3491
  }
@@ -3508,6 +3538,9 @@ async function queueCommand(limit, filters = {}) {
3508
3538
  async function enrichQueueCommand(limit) {
3509
3539
  return fetchAdminTracks({ max: limit, order: "asc", status: "queue" });
3510
3540
  }
3541
+ async function embedQueueCommand(limit) {
3542
+ return fetchAdminTracks({ hasEmbedding: false, max: limit, order: "asc" });
3543
+ }
3511
3544
  async function contextQueueCommand(limit, retryEmptyContext = false) {
3512
3545
  return fetchAdminTracks({ hasContext: false, max: limit, order: "asc", retryEmptyContext });
3513
3546
  }
@@ -7630,6 +7663,15 @@ function addAdminCommands(program2) {
7630
7663
  const { enrichQueueCommand: enrichQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7631
7664
  await runAdminEnrichQueue(options, enrichQueueCommand2);
7632
7665
  });
7666
+ adminTracks.command("embed").description("Audio-embedding worklist (findings with no MuQ vector yet) \u2014 use --queue").option("--queue", "Show the embedding worklist, oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--json", "Print JSON", false).action(async (options) => {
7667
+ if (!options.queue) {
7668
+ console.error("`tracks embed` is a worklist view \u2014 embedding runs on the on-box `fluncle-embed` cron.\nUse `tracks embed --queue` to see findings needing an audio embedding.");
7669
+ process.exitCode = 1;
7670
+ return;
7671
+ }
7672
+ const { embedQueueCommand: embedQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7673
+ await runAdminEmbedQueue(options, embedQueueCommand2);
7674
+ });
7633
7675
  adminTracks.command("vehicles").description("Recent video vehicles, newest first (the style ledger for diversity)").option("--limit <limit>", "Number of vehicles to show", "10").option("--json", "Print JSON", false).action(async (options) => {
7634
7676
  const { vehiclesCommand: vehiclesCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7635
7677
  await runAdminVehicles(options, vehiclesCommand2);
@@ -7639,7 +7681,7 @@ function addAdminCommands(program2) {
7639
7681
  const { trackGetAdminCommand: trackGetAdminCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
7640
7682
  await runTrackGetAdmin(idOrLogId, options, trackGetAdminCommand2);
7641
7683
  });
7642
- adminTrack.command("update").description("Certify a track into the archive").argument("[trackId]").option("--bpm <number>", "Track BPM").option("--features <json>", "Audio feature JSON").option("--json", "Print JSON", false).option("--key <key>", "Musical key").option("--note <text>", "Operator note").option("--status <status>", "Enrichment status").option("--video-url <url>", "Rendered video URL").allowExcessArguments().action(async (trackId, options) => {
7684
+ adminTrack.command("update").description("Certify a track into the archive").argument("[trackId]").option("--bpm <number>", "Track BPM").option("--embedding <json>", "MuQ audio embedding as a JSON array of 1024 floats").option("--embedding-file <file>", "Read the MuQ embedding JSON array from a file").option("--features <json>", "Audio feature JSON").option("--json", "Print JSON", false).option("--key <key>", "Musical key").option("--note <text>", "Operator note").option("--status <status>", "Enrichment status").option("--video-url <url>", "Rendered video URL").allowExcessArguments().action(async (trackId, options) => {
7643
7685
  const { trackUpdateCommand: trackUpdateCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
7644
7686
  await runTrackUpdate(trackId, options, trackUpdateCommand2);
7645
7687
  });
@@ -8249,6 +8291,21 @@ async function runTrackGetAdmin(idOrLogId, options, trackGetAdminCommand2) {
8249
8291
  console.log(`Log: ${t.logPageUrl}`);
8250
8292
  }
8251
8293
  }
8294
+ function parseEmbeddingArg(raw) {
8295
+ if (raw === undefined) {
8296
+ return;
8297
+ }
8298
+ let parsed;
8299
+ try {
8300
+ parsed = JSON.parse(raw);
8301
+ } catch {
8302
+ throw new Error("Invalid --embedding: expected a JSON array of 1024 floats");
8303
+ }
8304
+ if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "number" || !Number.isFinite(value))) {
8305
+ throw new Error("Invalid --embedding: expected a JSON array of 1024 finite numbers");
8306
+ }
8307
+ return parsed;
8308
+ }
8252
8309
  async function runTrackUpdate(trackId, options, trackUpdateCommand2) {
8253
8310
  if (!trackId) {
8254
8311
  throw new Error("Missing track id. Usage: fluncle admin tracks update <track_id>");
@@ -8257,8 +8314,11 @@ async function runTrackUpdate(trackId, options, trackUpdateCommand2) {
8257
8314
  if (bpm !== undefined && !Number.isFinite(bpm)) {
8258
8315
  throw new Error(`Invalid --bpm: ${options.bpm}`);
8259
8316
  }
8317
+ const embeddingRaw = options.embeddingFile ? readFileSync6(options.embeddingFile, "utf8") : options.embedding;
8318
+ const embedding = parseEmbeddingArg(embeddingRaw);
8260
8319
  const result = await trackUpdateCommand2(trackId, {
8261
8320
  bpm,
8321
+ embedding,
8262
8322
  features: options.features,
8263
8323
  key: options.key,
8264
8324
  note: options.note,
@@ -8721,6 +8781,26 @@ async function runAdminEnrichQueue(options, enrichQueueCommand2) {
8721
8781
  console.log(trackRows2(tracks).join(`
8722
8782
  `));
8723
8783
  }
8784
+ async function runAdminEmbedQueue(options, embedQueueCommand2) {
8785
+ const limit = parseListLimit(options.limit);
8786
+ const tracks = await embedQueueCommand2(limit);
8787
+ if (options.json) {
8788
+ printJson({
8789
+ ok: true,
8790
+ tracks
8791
+ });
8792
+ return;
8793
+ }
8794
+ if (tracks.length === 0) {
8795
+ console.log("Nothing awaiting an embedding. Every finding is embedded.");
8796
+ return;
8797
+ }
8798
+ const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
8799
+ const noun = tracks.length === 1 ? "finding" : "findings";
8800
+ console.log(`${tracks.length} ${noun} needing an audio embedding, oldest first:`);
8801
+ console.log(trackRows2(tracks).join(`
8802
+ `));
8803
+ }
8724
8804
  async function runAdminVehicles(options, vehiclesCommand2) {
8725
8805
  const limit = parseListLimit(options.limit);
8726
8806
  const vehicles = await vehiclesCommand2(limit);
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.96.0"
34
+ "version": "0.98.0"
35
35
  }