fluncle 0.109.0 → 0.110.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 +115 -5
  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.109.0".trim() ? "0.109.0".trim() : "0.1.0";
560
+ currentVersion = "0.110.0".trim() ? "0.110.0".trim() : "0.1.0";
561
561
  });
562
562
 
563
563
  // src/update-notifier.ts
@@ -2207,7 +2207,7 @@ function parseVersion2(version) {
2207
2207
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2208
2208
  var init_version2 = __esm(() => {
2209
2209
  init_output();
2210
- currentVersion2 = "0.109.0".trim() ? "0.109.0".trim() : "0.1.0";
2210
+ currentVersion2 = "0.110.0".trim() ? "0.110.0".trim() : "0.1.0";
2211
2211
  });
2212
2212
 
2213
2213
  // ../../packages/registry/src/index.ts
@@ -2679,7 +2679,7 @@ var init_src = __esm(() => {
2679
2679
  {
2680
2680
  command: "ssh rave.fluncle.com",
2681
2681
  exposedContent: [
2682
- "the rave terminal TUI: Enter the Galaxy, Latest bangers, Mixtape archive, Submit, Subscribe, Install CLI, About",
2682
+ "the rave terminal TUI: Latest bangers, Mixtape archive, Submit, Subscribe, Install CLI, About",
2683
2683
  "deep-register one-shots: `ssh rave.fluncle.com latest|random`"
2684
2684
  ],
2685
2685
  kind: "ssh",
@@ -2797,7 +2797,7 @@ var init_src = __esm(() => {
2797
2797
  ],
2798
2798
  kind: "cron",
2799
2799
  name: "cron.embed",
2800
- 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/rfcs/audio-embedding-rfc.md.",
2800
+ 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/track-lifecycle.md.",
2801
2801
  probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-embed", kind: "cron" },
2802
2802
  weights: { status: "hidden" }
2803
2803
  },
@@ -2808,7 +2808,7 @@ var init_src = __esm(() => {
2808
2808
  ],
2809
2809
  kind: "cron",
2810
2810
  name: "cron.capture",
2811
- operatorNotes: "every 5m, run by a rave-02 HOST systemd timer (docs/agents/hermes/capture-timer/) — NOT a gateway cron: a proxied yt-dlp fetch has an unbounded tail that would starve the 5-min enrich/context/note sweeps on the shared serial runner. A NON-BLOCKING side-channel (never gates enrich/embed). Runs yt-dlp through a residential proxy on a per-track sticky session, duration-guards the match, stores the full song in the PRIVATE fluncle-source-audio bucket (S3-direct), and writes back via the agent-tier update_track (with per-finding backoff). yt-dlp + ffprobe are a box deploy prereq. Newest-first so a fresh add jumps the backfill. Source: docs/agents/hermes/scripts/capture-sweep.*. See docs/rfcs/full-audio-rfc.md § Unit 1.",
2811
+ operatorNotes: "every 5m, run by a rave-02 HOST systemd timer (docs/agents/hermes/capture-timer/) — NOT a gateway cron: a proxied yt-dlp fetch has an unbounded tail that would starve the 5-min enrich/context/note sweeps on the shared serial runner. A NON-BLOCKING side-channel (never gates enrich/embed). Runs yt-dlp through a residential proxy on a per-track sticky session, duration-guards the match, stores the full song in the PRIVATE fluncle-source-audio bucket (S3-direct), and writes back via the agent-tier update_track (with per-finding backoff). yt-dlp + ffprobe are a box deploy prereq. Newest-first so a fresh add jumps the backfill. Source: docs/agents/hermes/scripts/capture-sweep.*. See docs/track-lifecycle.md.",
2812
2812
  probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-capture", kind: "cron" },
2813
2813
  weights: { status: "hidden" }
2814
2814
  },
@@ -5013,6 +5013,26 @@ var init_admin_artists = __esm(() => {
5013
5013
  init_api();
5014
5014
  });
5015
5015
 
5016
+ // src/commands/preview-migration.ts
5017
+ var exports_preview_migration = {};
5018
+ __export(exports_preview_migration, {
5019
+ migratePreviewArchiveCommand: () => migratePreviewArchiveCommand
5020
+ });
5021
+ async function migratePreviewArchiveCommand(args) {
5022
+ const params = new URLSearchParams({
5023
+ dryRun: String(args.dryRun),
5024
+ limit: String(args.limit),
5025
+ mode: args.mode
5026
+ });
5027
+ if (args.cursor) {
5028
+ params.set("cursor", args.cursor);
5029
+ }
5030
+ return adminApiPost(`/api/admin/migrations/preview-archive?${params.toString()}`);
5031
+ }
5032
+ var init_preview_migration = __esm(() => {
5033
+ init_api();
5034
+ });
5035
+
5016
5036
  // src/interactive.ts
5017
5037
  var exports_interactive = {};
5018
5038
  __export(exports_interactive, {
@@ -7844,6 +7864,14 @@ function addAdminCommands(program2) {
7844
7864
  const { resolveArtistCommand: resolveArtistCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
7845
7865
  await runArtistResolve(artistId, options, resolveArtistCommand2);
7846
7866
  });
7867
+ const migrations = configureCommand(admin.command("migrations").description("One-off operator data migrations"));
7868
+ migrations.action(() => {
7869
+ migrations.outputHelp();
7870
+ });
7871
+ migrations.command("preview-archive").description("Move archived 30s previews from the public bucket to the private one (REF-05)").option("--execute", "Actually perform the migration (default is a dry run)", false).option("--delete-public", "Delete phase: sweep the whole public analysis/previews/ prefix (run only after a full copy)", false).option("--verify", "Read-only: count the objects still under the public analysis/previews/ prefix", false).option("--limit <limit>", "Max findings/objects to process per pass", "50").option("--json", "Print JSON", false).action(async (options) => {
7872
+ const { migratePreviewArchiveCommand: migratePreviewArchiveCommand2 } = await Promise.resolve().then(() => (init_preview_migration(), exports_preview_migration));
7873
+ await runMigratePreviewArchive(options, migratePreviewArchiveCommand2);
7874
+ });
7847
7875
  }
7848
7876
  async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCommand2) {
7849
7877
  if (!idOrLogId || !options.file || !options.source || !options.mime) {
@@ -7936,6 +7964,88 @@ async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
7936
7964
  console.log(`Authored the note for ${result.logId}:`);
7937
7965
  console.log(` ${result.note}`);
7938
7966
  }
7967
+ async function runMigratePreviewArchive(options, migratePreviewArchiveCommand2) {
7968
+ const limit = options.limit === undefined ? 50 : Number.parseInt(options.limit, 10);
7969
+ if (!Number.isInteger(limit) || limit < 1) {
7970
+ throw new Error("Limit must be a positive integer");
7971
+ }
7972
+ const mode = options.verify ? "verify" : options.deletePublic ? "delete" : "copy";
7973
+ const dryRun = mode === "verify" ? true : !options.execute;
7974
+ if (mode === "verify") {
7975
+ const result = await migratePreviewArchiveCommand2({ dryRun: true, limit, mode });
7976
+ if (options.json) {
7977
+ printJson({ mode, ok: true, remaining: result.remaining, sampleKeys: result.sampleKeys });
7978
+ return;
7979
+ }
7980
+ console.log(`${result.remaining} object(s) remain under the public analysis/previews/ prefix.`);
7981
+ for (const key of result.sampleKeys) {
7982
+ console.log(` ${key}`);
7983
+ }
7984
+ return;
7985
+ }
7986
+ const copied = [];
7987
+ const deleted = [];
7988
+ const skipped = [];
7989
+ const failed = [];
7990
+ let cursor;
7991
+ let remaining = 0;
7992
+ let blocked = null;
7993
+ for (;; ) {
7994
+ const result = await migratePreviewArchiveCommand2({ cursor, dryRun, limit, mode });
7995
+ if (result.blocked) {
7996
+ blocked = result.blocked;
7997
+ remaining = result.remaining;
7998
+ break;
7999
+ }
8000
+ copied.push(...result.copied);
8001
+ deleted.push(...result.deleted);
8002
+ skipped.push(...result.skipped);
8003
+ failed.push(...result.failed);
8004
+ remaining = result.remaining;
8005
+ if (!options.json) {
8006
+ const verb = dryRun ? "would" : "did";
8007
+ console.log(` \u2026${mode} pass (${verb}): +${result.copiedCount} copied, +${result.deletedCount} deleted, ${result.skippedCount} skipped, ${result.failedCount} failed; ${result.remaining} remaining`);
8008
+ }
8009
+ if (result.nextCursor === null) {
8010
+ break;
8011
+ }
8012
+ cursor = result.nextCursor;
8013
+ }
8014
+ if (options.json) {
8015
+ printJson({
8016
+ blocked,
8017
+ copied,
8018
+ copiedCount: copied.length,
8019
+ deleted,
8020
+ deletedCount: deleted.length,
8021
+ dryRun,
8022
+ failed,
8023
+ failedCount: failed.length,
8024
+ mode,
8025
+ ok: true,
8026
+ remaining,
8027
+ skipped,
8028
+ skippedCount: skipped.length
8029
+ });
8030
+ return;
8031
+ }
8032
+ if (blocked) {
8033
+ console.log(`REFUSED (${blocked}): ${remaining} legacy row(s) still need copying first.`);
8034
+ return;
8035
+ }
8036
+ const done = mode === "delete" ? deleted.length : copied.length;
8037
+ const noun = mode === "delete" ? "public object(s) deleted" : "preview(s) copied to the private bucket";
8038
+ const prefix = dryRun ? "DRY RUN \u2014 would have: " : "";
8039
+ console.log(`${prefix}${done} ${noun}; ${skipped.length} skipped; ${failed.length} failed; ${remaining} still under the prefix.`);
8040
+ for (const item of skipped) {
8041
+ if (item.reason === "private_copy_absent") {
8042
+ console.log(` LEFT (no private copy) ${item.trackId}`);
8043
+ }
8044
+ }
8045
+ for (const item of failed) {
8046
+ console.log(` FAILED ${item.trackId}: ${item.error}`);
8047
+ }
8048
+ }
7939
8049
  async function runPreviewArchiveBackfill(options, previewArchiveBackfillCommand2) {
7940
8050
  const limit = options.limit === undefined ? undefined : Number.parseInt(options.limit, 10);
7941
8051
  if (limit !== undefined && (!Number.isInteger(limit) || limit < 1)) {
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.109.0"
34
+ "version": "0.110.0"
35
35
  }