fluncle 0.97.0 → 0.99.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 +87 -12
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -557,7 +557,7 @@ function parseVersion(version) {
557
557
  var currentVersion;
558
558
  var init_version = __esm(() => {
559
559
  init_output();
560
- currentVersion = "0.97.0".trim() ? "0.97.0".trim() : "0.1.0";
560
+ currentVersion = "0.99.0".trim() ? "0.99.0".trim() : "0.1.0";
561
561
  });
562
562
 
563
563
  // src/update-notifier.ts
@@ -2187,7 +2187,7 @@ function parseVersion2(version) {
2187
2187
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2188
2188
  var init_version2 = __esm(() => {
2189
2189
  init_output();
2190
- currentVersion2 = "0.97.0".trim() ? "0.97.0".trim() : "0.1.0";
2190
+ currentVersion2 = "0.99.0".trim() ? "0.99.0".trim() : "0.1.0";
2191
2191
  });
2192
2192
 
2193
2193
  // ../../packages/registry/src/index.ts
@@ -2737,6 +2737,17 @@ var init_src = __esm(() => {
2737
2737
  probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-enrich", kind: "cron" },
2738
2738
  weights: { status: "hidden" }
2739
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
+ },
2740
2751
  {
2741
2752
  command: "fluncle admin tracks context --queue",
2742
2753
  exposedContent: [
@@ -3079,6 +3090,9 @@ async function trackUpdateCommand(trackId, options) {
3079
3090
  if (options.features !== undefined) {
3080
3091
  body.features = options.features;
3081
3092
  }
3093
+ if (options.embedding !== undefined) {
3094
+ body.embedding = options.embedding;
3095
+ }
3082
3096
  if (options.note !== undefined) {
3083
3097
  body.note = options.note;
3084
3098
  }
@@ -3441,6 +3455,7 @@ __export(exports_admin_tracks, {
3441
3455
  noteQueueCommand: () => noteQueueCommand,
3442
3456
  listCommand: () => listCommand,
3443
3457
  enrichQueueCommand: () => enrichQueueCommand,
3458
+ embedQueueCommand: () => embedQueueCommand,
3444
3459
  contextQueueCommand: () => contextQueueCommand,
3445
3460
  backfillLastfmCommand: () => backfillLastfmCommand,
3446
3461
  backfillDiscogsCommand: () => backfillDiscogsCommand
@@ -3448,6 +3463,7 @@ __export(exports_admin_tracks, {
3448
3463
  async function fetchAdminTracks(options) {
3449
3464
  const {
3450
3465
  hasContext,
3466
+ hasEmbedding,
3451
3467
  hasKey,
3452
3468
  hasNote,
3453
3469
  hasObservation,
@@ -3467,6 +3483,9 @@ async function fetchAdminTracks(options) {
3467
3483
  if (hasKey !== undefined) {
3468
3484
  params.set("hasKey", String(hasKey));
3469
3485
  }
3486
+ if (hasEmbedding !== undefined) {
3487
+ params.set("hasEmbedding", String(hasEmbedding));
3488
+ }
3470
3489
  if (hasContext !== undefined) {
3471
3490
  params.set("hasContext", String(hasContext));
3472
3491
  }
@@ -3519,6 +3538,9 @@ async function queueCommand(limit, filters = {}) {
3519
3538
  async function enrichQueueCommand(limit) {
3520
3539
  return fetchAdminTracks({ max: limit, order: "asc", status: "queue" });
3521
3540
  }
3541
+ async function embedQueueCommand(limit) {
3542
+ return fetchAdminTracks({ hasEmbedding: false, max: limit, order: "asc" });
3543
+ }
3522
3544
  async function contextQueueCommand(limit, retryEmptyContext = false) {
3523
3545
  return fetchAdminTracks({ hasContext: false, max: limit, order: "asc", retryEmptyContext });
3524
3546
  }
@@ -3935,14 +3957,10 @@ var init_mixtape_youtube2 = __esm(() => {
3935
3957
  init_output();
3936
3958
  });
3937
3959
 
3938
- // src/commands/mixtape-set-video.ts
3939
- import { randomUUID } from "node:crypto";
3940
- import { existsSync as existsSync2, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
3941
- import { tmpdir } from "node:os";
3942
- import { join as join5 } from "node:path";
3960
+ // ../../packages/contracts/src/util/multipart.ts
3943
3961
  function planMultipart(contentLength, partSize = DEFAULT_PART_SIZE) {
3944
3962
  if (!Number.isInteger(contentLength) || contentLength <= 0) {
3945
- throw new CliError2("invalid_size", `The rendition size must be a positive integer (got ${contentLength})`);
3963
+ throw new Error(`multipart content length must be a positive integer (got ${contentLength})`);
3946
3964
  }
3947
3965
  let effective = Math.max(partSize, MIN_PART_SIZE);
3948
3966
  if (Math.ceil(contentLength / effective) > MAX_PARTS) {
@@ -3967,6 +3985,17 @@ function buildCompleteXml(parts) {
3967
3985
  const body = ordered.map((part) => `<Part><PartNumber>${part.partNumber}</PartNumber><ETag>${escapeXml(part.etag)}</ETag></Part>`).join("");
3968
3986
  return `<CompleteMultipartUpload>${body}</CompleteMultipartUpload>`;
3969
3987
  }
3988
+ var DEFAULT_PART_SIZE, MIN_PART_SIZE, MAX_PARTS = 1e4;
3989
+ var init_multipart = __esm(() => {
3990
+ DEFAULT_PART_SIZE = 16 * 1024 * 1024;
3991
+ MIN_PART_SIZE = 5 * 1024 * 1024;
3992
+ });
3993
+
3994
+ // src/commands/mixtape-set-video.ts
3995
+ import { randomUUID } from "node:crypto";
3996
+ import { existsSync as existsSync2, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
3997
+ import { tmpdir } from "node:os";
3998
+ import { join as join5 } from "node:path";
3970
3999
  function renditionFfmpegArgs(inputPath, outputPath) {
3971
4000
  return [
3972
4001
  "-y",
@@ -4099,8 +4128,9 @@ async function deriveRendition(inputPath, outputPath) {
4099
4128
  throw new CliError2("ffmpeg_failed", `ffmpeg failed to derive the set-video rendition${detail ? `: ${detail}` : ""}`);
4100
4129
  }
4101
4130
  }
4102
- var FOUND_BASE2 = "https://found.fluncle.com", SET_VIDEO_RENDITION, DEFAULT_PART_SIZE, MIN_PART_SIZE, MAX_PARTS = 1e4, MAX_PART_ATTEMPTS = 5;
4131
+ var FOUND_BASE2 = "https://found.fluncle.com", SET_VIDEO_RENDITION, MAX_PART_ATTEMPTS = 5;
4103
4132
  var init_mixtape_set_video = __esm(() => {
4133
+ init_multipart();
4104
4134
  init_api();
4105
4135
  init_output();
4106
4136
  SET_VIDEO_RENDITION = {
@@ -4109,8 +4139,6 @@ var init_mixtape_set_video = __esm(() => {
4109
4139
  gopSeconds: 2,
4110
4140
  height: 1080
4111
4141
  };
4112
- DEFAULT_PART_SIZE = 16 * 1024 * 1024;
4113
- MIN_PART_SIZE = 5 * 1024 * 1024;
4114
4142
  });
4115
4143
 
4116
4144
  // src/commands/recordings.ts
@@ -7641,6 +7669,15 @@ function addAdminCommands(program2) {
7641
7669
  const { enrichQueueCommand: enrichQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7642
7670
  await runAdminEnrichQueue(options, enrichQueueCommand2);
7643
7671
  });
7672
+ 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) => {
7673
+ if (!options.queue) {
7674
+ 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.");
7675
+ process.exitCode = 1;
7676
+ return;
7677
+ }
7678
+ const { embedQueueCommand: embedQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7679
+ await runAdminEmbedQueue(options, embedQueueCommand2);
7680
+ });
7644
7681
  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) => {
7645
7682
  const { vehiclesCommand: vehiclesCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7646
7683
  await runAdminVehicles(options, vehiclesCommand2);
@@ -7650,7 +7687,7 @@ function addAdminCommands(program2) {
7650
7687
  const { trackGetAdminCommand: trackGetAdminCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
7651
7688
  await runTrackGetAdmin(idOrLogId, options, trackGetAdminCommand2);
7652
7689
  });
7653
- 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) => {
7690
+ 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) => {
7654
7691
  const { trackUpdateCommand: trackUpdateCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
7655
7692
  await runTrackUpdate(trackId, options, trackUpdateCommand2);
7656
7693
  });
@@ -8260,6 +8297,21 @@ async function runTrackGetAdmin(idOrLogId, options, trackGetAdminCommand2) {
8260
8297
  console.log(`Log: ${t.logPageUrl}`);
8261
8298
  }
8262
8299
  }
8300
+ function parseEmbeddingArg(raw) {
8301
+ if (raw === undefined) {
8302
+ return;
8303
+ }
8304
+ let parsed;
8305
+ try {
8306
+ parsed = JSON.parse(raw);
8307
+ } catch {
8308
+ throw new Error("Invalid --embedding: expected a JSON array of 1024 floats");
8309
+ }
8310
+ if (!Array.isArray(parsed) || parsed.some((value) => typeof value !== "number" || !Number.isFinite(value))) {
8311
+ throw new Error("Invalid --embedding: expected a JSON array of 1024 finite numbers");
8312
+ }
8313
+ return parsed;
8314
+ }
8263
8315
  async function runTrackUpdate(trackId, options, trackUpdateCommand2) {
8264
8316
  if (!trackId) {
8265
8317
  throw new Error("Missing track id. Usage: fluncle admin tracks update <track_id>");
@@ -8268,8 +8320,11 @@ async function runTrackUpdate(trackId, options, trackUpdateCommand2) {
8268
8320
  if (bpm !== undefined && !Number.isFinite(bpm)) {
8269
8321
  throw new Error(`Invalid --bpm: ${options.bpm}`);
8270
8322
  }
8323
+ const embeddingRaw = options.embeddingFile ? readFileSync6(options.embeddingFile, "utf8") : options.embedding;
8324
+ const embedding = parseEmbeddingArg(embeddingRaw);
8271
8325
  const result = await trackUpdateCommand2(trackId, {
8272
8326
  bpm,
8327
+ embedding,
8273
8328
  features: options.features,
8274
8329
  key: options.key,
8275
8330
  note: options.note,
@@ -8732,6 +8787,26 @@ async function runAdminEnrichQueue(options, enrichQueueCommand2) {
8732
8787
  console.log(trackRows2(tracks).join(`
8733
8788
  `));
8734
8789
  }
8790
+ async function runAdminEmbedQueue(options, embedQueueCommand2) {
8791
+ const limit = parseListLimit(options.limit);
8792
+ const tracks = await embedQueueCommand2(limit);
8793
+ if (options.json) {
8794
+ printJson({
8795
+ ok: true,
8796
+ tracks
8797
+ });
8798
+ return;
8799
+ }
8800
+ if (tracks.length === 0) {
8801
+ console.log("Nothing awaiting an embedding. Every finding is embedded.");
8802
+ return;
8803
+ }
8804
+ const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
8805
+ const noun = tracks.length === 1 ? "finding" : "findings";
8806
+ console.log(`${tracks.length} ${noun} needing an audio embedding, oldest first:`);
8807
+ console.log(trackRows2(tracks).join(`
8808
+ `));
8809
+ }
8735
8810
  async function runAdminVehicles(options, vehiclesCommand2) {
8736
8811
  const limit = parseListLimit(options.limit);
8737
8812
  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.97.0"
34
+ "version": "0.99.0"
35
35
  }