fluncle 0.86.0 → 0.87.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 +61 -308
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -538,7 +538,7 @@ function parseVersion(version) {
538
538
  var currentVersion;
539
539
  var init_version = __esm(() => {
540
540
  init_output();
541
- currentVersion = "0.86.0".trim() ? "0.86.0".trim() : "0.1.0";
541
+ currentVersion = "0.87.0".trim() ? "0.87.0".trim() : "0.1.0";
542
542
  });
543
543
 
544
544
  // src/update-notifier.ts
@@ -2251,7 +2251,7 @@ function parseVersion2(version) {
2251
2251
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2252
2252
  var init_version2 = __esm(() => {
2253
2253
  init_output();
2254
- currentVersion2 = "0.86.0".trim() ? "0.86.0".trim() : "0.1.0";
2254
+ currentVersion2 = "0.87.0".trim() ? "0.87.0".trim() : "0.1.0";
2255
2255
  });
2256
2256
 
2257
2257
  // ../../packages/registry/src/index.ts
@@ -4014,238 +4014,6 @@ var init_recordings = __esm(() => {
4014
4014
  init_mixtape_set_video();
4015
4015
  });
4016
4016
 
4017
- // src/commands/mixtapes.ts
4018
- var exports_mixtapes2 = {};
4019
- __export(exports_mixtapes2, {
4020
- mixtapesCommand: () => mixtapesCommand2,
4021
- mixtapeUpdateCommand: () => mixtapeUpdateCommand2,
4022
- mixtapeResyncCommand: () => mixtapeResyncCommand2,
4023
- mixtapePublishCommand: () => mixtapePublishCommand2,
4024
- mixtapeMembersCommand: () => mixtapeMembersCommand2,
4025
- mixtapeListCommand: () => mixtapeListCommand,
4026
- mixtapeGetCommand: () => mixtapeGetCommand,
4027
- mixtapeDistributeCommand: () => mixtapeDistributeCommand2,
4028
- mixtapeDeleteCommand: () => mixtapeDeleteCommand2,
4029
- mixtapeCreateCommand: () => mixtapeCreateCommand2
4030
- });
4031
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
4032
- async function mixtapesCommand2() {
4033
- const response = await publicApiGet("/api/mixtapes");
4034
- return response.mixtapes;
4035
- }
4036
- async function mixtapeCreateCommand2(options) {
4037
- return adminApiPost("/api/admin/mixtapes", buildBody2(options));
4038
- }
4039
- async function mixtapeUpdateCommand2(id, options) {
4040
- return adminApiPatch(`/api/admin/mixtapes/${encodeURIComponent(id)}`, buildBody2(options));
4041
- }
4042
- async function mixtapeMembersCommand2(id, refs, options) {
4043
- const members = refs.map((ref) => ({ ref }));
4044
- if (options.from) {
4045
- members.push(...parseCueFile2(options.from));
4046
- }
4047
- return adminApiPut(`/api/admin/mixtapes/${encodeURIComponent(id)}/members`, { members });
4048
- }
4049
- async function mixtapePublishCommand2(id) {
4050
- return adminApiPost(`/api/admin/mixtapes/${encodeURIComponent(id)}/publish`);
4051
- }
4052
- async function mixtapeDeleteCommand2(id) {
4053
- return adminApiDelete(`/api/admin/mixtapes/${encodeURIComponent(id)}`);
4054
- }
4055
- async function mixtapeDistributeCommand2(idOrLogId, options, onProgress = () => {}) {
4056
- const mixtape = await mixtapeGetCommand(idOrLogId);
4057
- if (!mixtape.id) {
4058
- throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
4059
- }
4060
- if (mixtape.status === "draft") {
4061
- throw new CliError2("mixtape_not_promoted", `${mixtape.id} is still a draft — promote its recording first:
4062
- ` + " fluncle admin recordings promote <recordingId>");
4063
- }
4064
- const both = !options.youtube && !options.mixcloud;
4065
- const doYoutube = both || Boolean(options.youtube);
4066
- const doMixcloud = both || Boolean(options.mixcloud);
4067
- if (doYoutube && !options.video) {
4068
- throw new CliError2("missing_video", "YouTube distribution needs --video <mp4>");
4069
- }
4070
- if (doMixcloud && !options.audio) {
4071
- throw new CliError2("missing_audio", "Mixcloud distribution needs --audio <file>");
4072
- }
4073
- const mixtapeId = mixtape.id;
4074
- const logId = mixtape.logId;
4075
- if (!logId) {
4076
- throw new CliError2("missing_log_id", "The mixtape has no Log ID — was it promoted successfully?");
4077
- }
4078
- if (!mixtape.durationMs) {
4079
- const source = options.audio ?? options.video;
4080
- const durationMs = source ? await probeDurationMs2(source) : undefined;
4081
- if (durationMs) {
4082
- await mixtapeUpdateCommand2(mixtapeId, { durationMs: String(durationMs), json: false });
4083
- onProgress(`Duration: ${Math.round(durationMs / 60000)} min (from the upload).`);
4084
- }
4085
- }
4086
- if (mixtape.status === "published") {
4087
- onProgress(`Already published (${logId}); re-distributing.`);
4088
- } else {
4089
- onProgress(`Distributing ${logId} (promoted — coordinate already minted).`);
4090
- }
4091
- const results = [];
4092
- if (doYoutube) {
4093
- if (!options.video) {
4094
- throw new CliError2("missing_video", "YouTube distribution needs --video <mp4>");
4095
- }
4096
- onProgress("YouTube: uploading video…");
4097
- const { distributeYoutube: distributeYoutube3 } = await Promise.resolve().then(() => (init_mixtape_youtube(), exports_mixtape_youtube));
4098
- const result = await distributeYoutube3(mixtapeId, options.video, onProgress);
4099
- results.push({ platform: "youtube", url: result.url });
4100
- onProgress(`YouTube: ${result.url}`);
4101
- }
4102
- if (doMixcloud) {
4103
- if (!options.audio) {
4104
- throw new CliError2("missing_audio", "Mixcloud distribution needs --audio <file>");
4105
- }
4106
- onProgress("Mixcloud: uploading audio…");
4107
- const { distributeMixcloud: distributeMixcloud2 } = await Promise.resolve().then(() => (init_mixtape_mixcloud(), exports_mixtape_mixcloud));
4108
- const result = await distributeMixcloud2(mixtapeId, options.audio, onProgress, options.unlisted);
4109
- results.push({ platform: "mixcloud", url: result.url });
4110
- onProgress(`Mixcloud: ${result.url}`);
4111
- }
4112
- return { logId, mixtapeId, results };
4113
- }
4114
- async function mixtapeResyncCommand2(idOrLogId, options, onProgress = () => {}) {
4115
- const mixtape = await mixtapeGetCommand(idOrLogId);
4116
- if (!mixtape.id) {
4117
- throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
4118
- }
4119
- if (!mixtape.logId) {
4120
- throw new CliError2("mixtape_no_log_id", "The mixtape isn't published yet — distribute it before re-syncing.");
4121
- }
4122
- const mixtapeId = mixtape.id;
4123
- const explicit = Boolean(options.youtube) || Boolean(options.mixcloud);
4124
- let doYoutube = Boolean(options.youtube);
4125
- let doMixcloud = Boolean(options.mixcloud);
4126
- if (!explicit) {
4127
- const social = await adminApiGet(`/api/admin/mixtapes/${encodeURIComponent(mixtapeId)}/social`);
4128
- const platforms = new Set(social.posts.map((post) => post.platform));
4129
- doYoutube = platforms.has("youtube");
4130
- doMixcloud = platforms.has("mixcloud");
4131
- if (!doYoutube && !doMixcloud) {
4132
- throw new CliError2("mixtape_not_distributed", "The mixtape has no YouTube or Mixcloud link to re-sync.");
4133
- }
4134
- }
4135
- const results = [];
4136
- if (doYoutube) {
4137
- onProgress("YouTube: re-syncing description + chapters…");
4138
- const { resyncYoutube: resyncYoutube3 } = await Promise.resolve().then(() => (init_mixtape_youtube(), exports_mixtape_youtube));
4139
- const result = await resyncYoutube3(mixtapeId);
4140
- results.push({ platform: "youtube", url: result.url });
4141
- onProgress(`YouTube: ${result.url}`);
4142
- }
4143
- if (doMixcloud) {
4144
- onProgress("Mixcloud: re-syncing sections…");
4145
- const { resyncMixcloud: resyncMixcloud2 } = await Promise.resolve().then(() => (init_mixtape_mixcloud(), exports_mixtape_mixcloud));
4146
- const result = await resyncMixcloud2(mixtapeId);
4147
- results.push({ platform: "mixcloud", url: result.url });
4148
- onProgress(`Mixcloud: ${result.url}`);
4149
- }
4150
- return { logId: mixtape.logId, mixtapeId, results };
4151
- }
4152
- async function probeDurationMs2(filePath) {
4153
- try {
4154
- const proc = Bun.spawn(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "csv=p=0", filePath], { stderr: "ignore", stdout: "pipe" });
4155
- const out = await new Response(proc.stdout).text();
4156
- await proc.exited;
4157
- const seconds = Number.parseFloat(out.trim());
4158
- return Number.isFinite(seconds) && seconds > 0 ? Math.round(seconds * 1000) : undefined;
4159
- } catch {
4160
- return;
4161
- }
4162
- }
4163
- function buildBody2(options) {
4164
- const body = {};
4165
- if (options.note !== undefined) {
4166
- body.note = options.note;
4167
- }
4168
- if (options.recordedAt !== undefined) {
4169
- body.recordedAt = options.recordedAt;
4170
- }
4171
- if (options.soundcloudUrl !== undefined) {
4172
- body.soundcloudUrl = options.soundcloudUrl;
4173
- }
4174
- if (options.durationMs !== undefined) {
4175
- const parsed = parseDuration(options.durationMs);
4176
- if (parsed === null) {
4177
- throw new CliError2("invalid_duration", "Duration must be mm:ss or h:mm:ss, or a millisecond count");
4178
- }
4179
- body.durationMs = parsed;
4180
- }
4181
- return body;
4182
- }
4183
- function parseCueFile2(filePath) {
4184
- if (!existsSync4(filePath)) {
4185
- throw new CliError2("file_not_found", `Cue file not found: ${filePath}`);
4186
- }
4187
- const text = readFileSync4(filePath, "utf-8");
4188
- const trimmed = text.trim();
4189
- if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
4190
- try {
4191
- const parsed = JSON.parse(trimmed);
4192
- if (!Array.isArray(parsed)) {
4193
- throw new Error("not an array");
4194
- }
4195
- return parsed.map((entry, index) => {
4196
- if (typeof entry === "string") {
4197
- return { ref: entry.trim() };
4198
- }
4199
- const obj = entry;
4200
- if (typeof obj?.ref !== "string") {
4201
- throw new CliError2("invalid_cue_json", `Entry ${index + 1} missing "ref" string`);
4202
- }
4203
- const cue = { ref: obj.ref.trim() };
4204
- if (typeof obj.startMs === "number" && Number.isInteger(obj.startMs) && obj.startMs >= 0) {
4205
- cue.startMs = obj.startMs;
4206
- }
4207
- return cue;
4208
- });
4209
- } catch (error) {
4210
- if (error instanceof CliError2) {
4211
- throw error;
4212
- }
4213
- throw new CliError2("invalid_cue_json", `Cue JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
4214
- }
4215
- }
4216
- return parseCueSheet2(text);
4217
- }
4218
- function parseCueSheet2(text) {
4219
- const entries = [];
4220
- for (const line of text.split(/\r?\n/)) {
4221
- const trimmed = line.trim();
4222
- if (!trimmed || trimmed.startsWith("#")) {
4223
- continue;
4224
- }
4225
- const match = trimmed.match(/^(\d{1,2}:\d{2}(?::\d{2})?)\s+(.+)$/);
4226
- if (match) {
4227
- const [, time, ref] = match;
4228
- if (time === undefined || ref === undefined) {
4229
- continue;
4230
- }
4231
- const startMs = parseDuration(time);
4232
- if (startMs === null) {
4233
- continue;
4234
- }
4235
- entries.push({ ref: ref.trim(), startMs });
4236
- } else {
4237
- entries.push({ ref: trimmed });
4238
- }
4239
- }
4240
- return entries;
4241
- }
4242
- var init_mixtapes2 = __esm(() => {
4243
- init_util();
4244
- init_api();
4245
- init_mixtape_api();
4246
- init_output();
4247
- });
4248
-
4249
4017
  // src/commands/clips.ts
4250
4018
  var exports_clips = {};
4251
4019
  __export(exports_clips, {
@@ -4321,9 +4089,6 @@ async function clipsListCommand(filter = {}) {
4321
4089
  if (filter.recordingId) {
4322
4090
  params.set("recordingId", filter.recordingId);
4323
4091
  }
4324
- if (filter.mixtapeId) {
4325
- params.set("mixtapeId", filter.mixtapeId);
4326
- }
4327
4092
  if (filter.status) {
4328
4093
  params.set("status", filter.status);
4329
4094
  }
@@ -4332,28 +4097,17 @@ async function clipsListCommand(filter = {}) {
4332
4097
  return response.clips;
4333
4098
  }
4334
4099
  async function resolveClipSource(clip) {
4335
- if (clip.recordingId) {
4336
- const { recordingGet: recordingGet2 } = await Promise.resolve().then(() => (init_recordings(), exports_recordings));
4337
- const recording = await recordingGet2(clip.recordingId);
4338
- if (!recording.r2Key) {
4339
- throw new CliError2("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
4340
- }
4341
- return {
4342
- setUrl: `${FOUND_BASE3}/${recording.r2Key.split("/").map(encodeURIComponent).join("/")}`
4343
- };
4344
- }
4345
- if (!clip.mixtapeId) {
4346
- throw new CliError2("clip_unlinked", `Clip ${clip.id} is linked to neither a recording nor a mixtape`);
4100
+ if (!clip.recordingId) {
4101
+ throw new CliError2("clip_unlinked", `Clip ${clip.id} is linked to no recording`);
4347
4102
  }
4348
- const { mixtapeGetCommand: mixtapeGetCommand2 } = await Promise.resolve().then(() => (init_mixtapes2(), exports_mixtapes2));
4349
- const mixtape = await mixtapeGetCommand2(clip.mixtapeId);
4350
- if (!mixtape.logId) {
4351
- throw new CliError2("mixtape_no_log_id", `Mixtape ${clip.mixtapeId} has no committed Log ID`);
4103
+ const { recordingGet: recordingGet2 } = await Promise.resolve().then(() => (init_recordings(), exports_recordings));
4104
+ const recording = await recordingGet2(clip.recordingId);
4105
+ if (!recording.r2Key) {
4106
+ throw new CliError2("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
4352
4107
  }
4353
- if (!mixtape.setVideoAt) {
4354
- throw new CliError2("set_not_staged", `Mixtape ${mixtape.logId} has no staged set video — run \`recordings promote <recordingId>\` first`);
4355
- }
4356
- return { setUrl: setVideoUrl(mixtape.logId) };
4108
+ return {
4109
+ setUrl: `${FOUND_BASE3}/${recording.r2Key.split("/").map(encodeURIComponent).join("/")}`
4110
+ };
4357
4111
  }
4358
4112
  async function clipCutCommand(clipId, onProgress = () => {}) {
4359
4113
  const clips = await clipsListCommand();
@@ -4447,7 +4201,7 @@ __export(exports_recordings2, {
4447
4201
  recordingDeleteCommand: () => recordingDeleteCommand2,
4448
4202
  recordingCreateCommand: () => recordingCreateCommand2
4449
4203
  });
4450
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
4204
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
4451
4205
  async function recordingGet2(id) {
4452
4206
  const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
4453
4207
  return response.recording;
@@ -4516,7 +4270,7 @@ async function recordingCreateCommand2(options = {}) {
4516
4270
  if (!options.video) {
4517
4271
  throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
4518
4272
  }
4519
- if (!existsSync5(options.video)) {
4273
+ if (!existsSync4(options.video)) {
4520
4274
  throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
4521
4275
  }
4522
4276
  const created = await adminApiPost("/api/admin/recordings", {
@@ -4552,11 +4306,11 @@ async function recordingUpdateCommand2(id, options = {}) {
4552
4306
  body.parentId = options.parentId;
4553
4307
  }
4554
4308
  if (options.tracklistFile !== undefined) {
4555
- if (!existsSync5(options.tracklistFile)) {
4309
+ if (!existsSync4(options.tracklistFile)) {
4556
4310
  throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
4557
4311
  }
4558
4312
  try {
4559
- body.tracklistJson = JSON.parse(readFileSync5(options.tracklistFile, "utf8"));
4313
+ body.tracklistJson = JSON.parse(readFileSync4(options.tracklistFile, "utf8"));
4560
4314
  } catch {
4561
4315
  throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
4562
4316
  }
@@ -4601,12 +4355,12 @@ async function recordingReplaceCuesCommand2(id, options = {}) {
4601
4355
  if (!options.cuesFile) {
4602
4356
  throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
4603
4357
  }
4604
- if (!existsSync5(options.cuesFile)) {
4358
+ if (!existsSync4(options.cuesFile)) {
4605
4359
  throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
4606
4360
  }
4607
4361
  let cues;
4608
4362
  try {
4609
- cues = JSON.parse(readFileSync5(options.cuesFile, "utf8"));
4363
+ cues = JSON.parse(readFileSync4(options.cuesFile, "utf8"));
4610
4364
  } catch {
4611
4365
  throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
4612
4366
  }
@@ -4632,8 +4386,8 @@ __export(exports_newsletter, {
4632
4386
  newsletterDraftCommand: () => newsletterDraftCommand,
4633
4387
  newsletterDeleteCommand: () => newsletterDeleteCommand
4634
4388
  });
4635
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
4636
- function buildBody3(options, { requireContent }) {
4389
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
4390
+ function buildBody2(options, { requireContent }) {
4637
4391
  const body = {};
4638
4392
  if (options.contentFile !== undefined) {
4639
4393
  body.contentJson = readContentFile(options.contentFile);
@@ -4652,10 +4406,10 @@ function buildBody3(options, { requireContent }) {
4652
4406
  return body;
4653
4407
  }
4654
4408
  function readContentFile(filePath) {
4655
- if (!existsSync6(filePath)) {
4409
+ if (!existsSync5(filePath)) {
4656
4410
  throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
4657
4411
  }
4658
- const text = readFileSync6(filePath, "utf-8");
4412
+ const text = readFileSync5(filePath, "utf-8");
4659
4413
  try {
4660
4414
  return JSON.parse(text);
4661
4415
  } catch (error) {
@@ -4663,10 +4417,10 @@ function readContentFile(filePath) {
4663
4417
  }
4664
4418
  }
4665
4419
  async function newsletterDraftCommand(options) {
4666
- return adminApiPost("/api/admin/newsletter/editions", buildBody3(options, { requireContent: true }));
4420
+ return adminApiPost("/api/admin/newsletter/editions", buildBody2(options, { requireContent: true }));
4667
4421
  }
4668
4422
  async function newsletterUpdateCommand(id, options) {
4669
- return adminApiPatch(`/api/admin/newsletter/editions/${encodeURIComponent(id)}`, buildBody3(options, { requireContent: false }));
4423
+ return adminApiPatch(`/api/admin/newsletter/editions/${encodeURIComponent(id)}`, buildBody2(options, { requireContent: false }));
4670
4424
  }
4671
4425
  async function newsletterSendCommand(id) {
4672
4426
  return adminApiPost(`/api/admin/newsletter/editions/${encodeURIComponent(id)}/send`);
@@ -5290,7 +5044,7 @@ var init_format2 = __esm(() => {
5290
5044
  });
5291
5045
 
5292
5046
  // src/cli.ts
5293
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
5047
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
5294
5048
  import path2 from "path";
5295
5049
 
5296
5050
  // ../../node_modules/commander/lib/error.js
@@ -7456,8 +7210,8 @@ function addListenCommands(program2) {
7456
7210
  await runRecent(options, recentCommand3);
7457
7211
  });
7458
7212
  program2.command("mixtapes").description("Fluncle's checkpoint sets").option("--json", "Print JSON", false).action(async (options) => {
7459
- const { mixtapesCommand: mixtapesCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7460
- await runMixtapes(options, mixtapesCommand3);
7213
+ const { mixtapesCommand: mixtapesCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7214
+ await runMixtapes(options, mixtapesCommand2);
7461
7215
  });
7462
7216
  program2.command("open").description("Pick a track, open it in Spotify").argument("[target]").option("--app", "Open in the native app", false).option("--browser", "Open in the browser", false).option("--limit <limit>", "Number of recent tracks to choose from", "20").allowExcessArguments().action(async (target, options) => {
7463
7217
  const openCommands = await Promise.resolve().then(() => (init_open(), exports_open));
@@ -7619,24 +7373,24 @@ function addAdminCommands(program2) {
7619
7373
  adminMixtapes.outputHelp();
7620
7374
  });
7621
7375
  adminMixtapes.command("create").description("Log a new mixtape draft").option("--duration-ms <duration>", "Duration (mm:ss, h:mm:ss, or ms)").option("--json", "Print JSON", false).option("--note <text>", "Operator note").option("--recorded-at <date>", "Recorded date (ISO)").option("--soundcloud-url <url>", "SoundCloud URL (manual; YouTube + Mixcloud come from distribute)").allowExcessArguments().action(async (options) => {
7622
- const { mixtapeCreateCommand: mixtapeCreateCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7623
- await runMixtapeCreate(options, mixtapeCreateCommand3);
7376
+ const { mixtapeCreateCommand: mixtapeCreateCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7377
+ await runMixtapeCreate(options, mixtapeCreateCommand2);
7624
7378
  });
7625
7379
  adminMixtapes.command("update").description("Update a mixtape's fields").argument("[id]").option("--duration-ms <duration>", "Duration (mm:ss, h:mm:ss, or ms)").option("--json", "Print JSON", false).option("--note <text>", "Operator note").option("--recorded-at <date>", "Recorded date (ISO)").option("--soundcloud-url <url>", "SoundCloud URL (manual; YouTube + Mixcloud come from distribute)").allowExcessArguments().action(async (id, options) => {
7626
- const { mixtapeUpdateCommand: mixtapeUpdateCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7627
- await runMixtapeUpdate(id, options, mixtapeUpdateCommand3);
7380
+ const { mixtapeUpdateCommand: mixtapeUpdateCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7381
+ await runMixtapeUpdate(id, options, mixtapeUpdateCommand2);
7628
7382
  });
7629
7383
  adminMixtapes.command("members").description("Set a mixtape's tracklist (refs and/or a cue-sheet file)").argument("[id]").argument("[refs...]").option("--from <file>", "Cue-sheet or JSON file with members").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, refs, options) => {
7630
- const { mixtapeMembersCommand: mixtapeMembersCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7631
- await runMixtapeMembers(id, refs, options, mixtapeMembersCommand3);
7384
+ const { mixtapeMembersCommand: mixtapeMembersCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7385
+ await runMixtapeMembers(id, refs, options, mixtapeMembersCommand2);
7632
7386
  });
7633
7387
  adminMixtapes.command("publish").description("Publish a mixtape draft").argument("[id]").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
7634
- const { mixtapePublishCommand: mixtapePublishCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7635
- await runMixtapePublish(id, options, mixtapePublishCommand3);
7388
+ const { mixtapePublishCommand: mixtapePublishCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7389
+ await runMixtapePublish(id, options, mixtapePublishCommand2);
7636
7390
  });
7637
7391
  adminMixtapes.command("delete").description("Discard a mixtape draft (published mixtapes can't be deleted)").argument("[id]").option("--json", "Print JSON", false).option("--yes", "Skip confirmation", false).allowExcessArguments().action(async (id, options) => {
7638
- const { mixtapeDeleteCommand: mixtapeDeleteCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7639
- await runMixtapeDelete(id, options, mixtapeDeleteCommand3);
7392
+ const { mixtapeDeleteCommand: mixtapeDeleteCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7393
+ await runMixtapeDelete(id, options, mixtapeDeleteCommand2);
7640
7394
  });
7641
7395
  adminMixtapes.command("list").description("List all mixtapes (including drafts)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
7642
7396
  const { mixtapeListCommand: mixtapeListCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
@@ -7647,22 +7401,22 @@ function addAdminCommands(program2) {
7647
7401
  await runMixtapeGet(idOrLogId, options, mixtapeGetCommand2);
7648
7402
  });
7649
7403
  adminMixtapes.command("distribute").description("Push a promoted mixtape to YouTube (video) and Mixcloud (audio). The mixtape must already be promoted (`recordings promote`) \u2014 distribute is push-only.").argument("[idOrLogId]").option("--video <file>", "Video file for YouTube").option("--audio <file>", "Audio file for Mixcloud").option("--youtube", "Only distribute to YouTube").option("--mixcloud", "Only distribute to Mixcloud").option("--unlisted", "Keep Mixcloud private too (YouTube is always unlisted until publish-youtube)").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
7650
- const { mixtapeDistributeCommand: mixtapeDistributeCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7651
- await runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand3);
7404
+ const { mixtapeDistributeCommand: mixtapeDistributeCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7405
+ await runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand2);
7652
7406
  });
7653
7407
  adminMixtapes.command("publish-youtube").description("Flip a distributed mixtape's YouTube video from unlisted to public").argument("[idOrLogId]").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
7654
7408
  const { publishYoutubeCommand: publishYoutubeCommand3 } = await Promise.resolve().then(() => (init_mixtape_youtube2(), exports_mixtape_youtube2));
7655
7409
  await runMixtapePublishYoutube(idOrLogId, options, publishYoutubeCommand3);
7656
7410
  });
7657
7411
  adminMixtapes.command("resync").description("Re-push a published mixtape's YouTube chapters + Mixcloud sections from its current cues (no re-upload)").argument("[idOrLogId]").option("--youtube", "Only re-sync YouTube").option("--mixcloud", "Only re-sync Mixcloud").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
7658
- const { mixtapeResyncCommand: mixtapeResyncCommand3 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7659
- await runMixtapeResync(idOrLogId, options, mixtapeResyncCommand3);
7412
+ const { mixtapeResyncCommand: mixtapeResyncCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7413
+ await runMixtapeResync(idOrLogId, options, mixtapeResyncCommand2);
7660
7414
  });
7661
7415
  const adminClips = configureCommand(admin.command("clips").description("Mixtape clip (Fluncle Studio) commands"));
7662
7416
  adminClips.action(() => {
7663
7417
  adminClips.outputHelp();
7664
7418
  });
7665
- adminClips.command("list").description("List clips (filter by --status pending|done, --recording <id>, and/or --mixtape <id>)").option("--status <status>", "Filter by cut status (pending|done)").option("--recording <id>", "Filter by recording id").option("--mixtape <id>", "Filter by mixtape id").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
7419
+ adminClips.command("list").description("List clips (filter by --status pending|done and/or --recording <id>)").option("--status <status>", "Filter by cut status (pending|done)").option("--recording <id>", "Filter by recording id").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
7666
7420
  const { clipsListCommand: clipsListCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
7667
7421
  await runClipsList(options, clipsListCommand2);
7668
7422
  });
@@ -7805,7 +7559,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
7805
7559
  console.log(` mime: ${result.mime}`);
7806
7560
  }
7807
7561
  async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
7808
- const script = options.scriptFile ? readFileSync7(options.scriptFile, "utf8") : options.script;
7562
+ const script = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
7809
7563
  if (!idOrLogId || !script || !script.trim()) {
7810
7564
  throw new Error("Usage: fluncle admin tracks observe <track_id|log_id> (--script <text> | --script-file <file>) [--voice-id <id>] [--duration-ms <ms>] [--context-note <text>] [--json]");
7811
7565
  }
@@ -7861,7 +7615,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
7861
7615
  }
7862
7616
  }
7863
7617
  async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
7864
- const note = options.scriptFile ? readFileSync7(options.scriptFile, "utf8") : options.script;
7618
+ const note = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
7865
7619
  if (!idOrLogId || !note || !note.trim()) {
7866
7620
  throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--json]");
7867
7621
  }
@@ -8007,7 +7761,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
8007
7761
  return;
8008
7762
  }
8009
7763
  const candidate = path2.join(dir, name);
8010
- return existsSync7(candidate) ? candidate : undefined;
7764
+ return existsSync6(candidate) ? candidate : undefined;
8011
7765
  };
8012
7766
  const resolveFile = (explicit, name) => {
8013
7767
  if (explicit) {
@@ -8184,56 +7938,56 @@ async function runTrackPurgeVideo(idOrLogId, options, trackPurgeVideoCommand2) {
8184
7938
  }
8185
7939
  console.log(result.noVideo ? `${result.logId} has no video \u2014 nothing to purge.` : `Purging the stale renditions for ${result.logId} from the edge. The next play picks up the fresh render.`);
8186
7940
  }
8187
- async function runMixtapeCreate(options, mixtapeCreateCommand3) {
8188
- const result = await mixtapeCreateCommand3(options);
7941
+ async function runMixtapeCreate(options, mixtapeCreateCommand2) {
7942
+ const result = await mixtapeCreateCommand2(options);
8189
7943
  if (options.json) {
8190
7944
  printJson(result);
8191
7945
  return;
8192
7946
  }
8193
7947
  console.log(`Logged draft ${result.mixtape.id}. It stays a draft until you publish it.`);
8194
7948
  }
8195
- async function runMixtapeUpdate(id, options, mixtapeUpdateCommand3) {
7949
+ async function runMixtapeUpdate(id, options, mixtapeUpdateCommand2) {
8196
7950
  if (!id) {
8197
7951
  throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes update <id>");
8198
7952
  }
8199
- const result = await mixtapeUpdateCommand3(id, options);
7953
+ const result = await mixtapeUpdateCommand2(id, options);
8200
7954
  if (options.json) {
8201
7955
  printJson(result);
8202
7956
  return;
8203
7957
  }
8204
7958
  console.log(`Saved ${result.mixtape.id}.`);
8205
7959
  }
8206
- async function runMixtapeMembers(id, refs, options, mixtapeMembersCommand3) {
7960
+ async function runMixtapeMembers(id, refs, options, mixtapeMembersCommand2) {
8207
7961
  if (!id) {
8208
7962
  throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes members <id> [refs...]");
8209
7963
  }
8210
7964
  if (refs.length === 0 && !options.from) {
8211
7965
  throw new Error("Provide refs as arguments or a cue-sheet file with --from");
8212
7966
  }
8213
- const result = await mixtapeMembersCommand3(id, refs, options);
7967
+ const result = await mixtapeMembersCommand2(id, refs, options);
8214
7968
  if (options.json) {
8215
7969
  printJson(result);
8216
7970
  return;
8217
7971
  }
8218
7972
  console.log(`Saved the tracklist: ${result.mixtape.memberCount} bangers on ${result.mixtape.id}.`);
8219
7973
  }
8220
- async function runMixtapePublish(id, options, mixtapePublishCommand3) {
7974
+ async function runMixtapePublish(id, options, mixtapePublishCommand2) {
8221
7975
  if (!id) {
8222
7976
  throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes publish <id>");
8223
7977
  }
8224
- const result = await mixtapePublishCommand3(id);
7978
+ const result = await mixtapePublishCommand2(id);
8225
7979
  if (options.json) {
8226
7980
  printJson(result);
8227
7981
  return;
8228
7982
  }
8229
7983
  console.log(`Minted ${result.mixtape.logId} (fluncle://${result.mixtape.logId}) \u2014 distributing. ` + "Run `distribute` to push it to the platforms.");
8230
7984
  }
8231
- async function runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand3) {
7985
+ async function runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand2) {
8232
7986
  if (!idOrLogId) {
8233
7987
  throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes distribute <idOrLogId> --video <mp4> --audio <file>");
8234
7988
  }
8235
7989
  const onProgress = options.json ? () => {} : (message) => console.log(message);
8236
- const result = await mixtapeDistributeCommand3(idOrLogId, options, onProgress);
7990
+ const result = await mixtapeDistributeCommand2(idOrLogId, options, onProgress);
8237
7991
  if (options.json) {
8238
7992
  printJson(result);
8239
7993
  return;
@@ -8254,12 +8008,12 @@ async function runMixtapePublishYoutube(idOrLogId, options, publishYoutubeComman
8254
8008
  }
8255
8009
  console.log(`YouTube video is now public: ${result.url}`);
8256
8010
  }
8257
- async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand3) {
8011
+ async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand2) {
8258
8012
  if (!idOrLogId) {
8259
8013
  throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes resync <idOrLogId>");
8260
8014
  }
8261
8015
  const onProgress = options.json ? () => {} : (message) => console.log(message);
8262
- const result = await mixtapeResyncCommand3(idOrLogId, options, onProgress);
8016
+ const result = await mixtapeResyncCommand2(idOrLogId, options, onProgress);
8263
8017
  if (options.json) {
8264
8018
  printJson(result);
8265
8019
  return;
@@ -8269,14 +8023,14 @@ async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand3) {
8269
8023
  console.log(`Re-synced ${result.logId} (fluncle://${result.logId}).
8270
8024
  ${links}`);
8271
8025
  }
8272
- async function runMixtapeDelete(id, options, mixtapeDeleteCommand3) {
8026
+ async function runMixtapeDelete(id, options, mixtapeDeleteCommand2) {
8273
8027
  if (!id) {
8274
8028
  throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes delete <id> --yes");
8275
8029
  }
8276
8030
  if (!options.yes) {
8277
8031
  throw new Error("Pass --yes to confirm the discard. Published mixtapes can't be deleted.");
8278
8032
  }
8279
- await mixtapeDeleteCommand3(id);
8033
+ await mixtapeDeleteCommand2(id);
8280
8034
  if (options.json) {
8281
8035
  printJson({ id, ok: true });
8282
8036
  return;
@@ -8301,7 +8055,6 @@ async function runMixtapeList(options, mixtapeListCommand2) {
8301
8055
  }
8302
8056
  async function runClipsList(options, clipsListCommand2) {
8303
8057
  const clips = await clipsListCommand2({
8304
- mixtapeId: options.mixtape,
8305
8058
  recordingId: options.recording,
8306
8059
  status: options.status
8307
8060
  });
@@ -8314,7 +8067,7 @@ async function runClipsList(options, clipsListCommand2) {
8314
8067
  return;
8315
8068
  }
8316
8069
  for (const clip of clips) {
8317
- const source = clip.recordingId ?? clip.mixtapeId ?? "\u2014";
8070
+ const source = clip.recordingId ?? "\u2014";
8318
8071
  console.log(`${clip.id} ${clip.status} ${source} ${clip.inMs}-${clip.outMs}ms x=${clip.xOffset}`);
8319
8072
  }
8320
8073
  }
@@ -8477,8 +8230,8 @@ async function runRecent(options, recentCommand3) {
8477
8230
  console.log(trackRows2(tracks).join(`
8478
8231
  `));
8479
8232
  }
8480
- async function runMixtapes(options, mixtapesCommand3) {
8481
- const mixtapes = await mixtapesCommand3();
8233
+ async function runMixtapes(options, mixtapesCommand2) {
8234
+ const mixtapes = await mixtapesCommand2();
8482
8235
  if (options.json) {
8483
8236
  printJson({ mixtapes, ok: true });
8484
8237
  return;
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.86.0"
34
+ "version": "0.87.0"
35
35
  }