fluncle 0.86.0 → 0.88.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 +62 -455
  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.88.0".trim() ? "0.88.0".trim() : "0.1.0";
542
542
  });
543
543
 
544
544
  // src/update-notifier.ts
@@ -1335,47 +1335,22 @@ __export(exports_mixtapes, {
1335
1335
  mixtapesCommand: () => mixtapesCommand,
1336
1336
  mixtapeUpdateCommand: () => mixtapeUpdateCommand,
1337
1337
  mixtapeResyncCommand: () => mixtapeResyncCommand,
1338
- mixtapePublishCommand: () => mixtapePublishCommand,
1339
- mixtapeMembersCommand: () => mixtapeMembersCommand,
1340
1338
  mixtapeListCommand: () => mixtapeListCommand,
1341
1339
  mixtapeGetCommand: () => mixtapeGetCommand,
1342
- mixtapeDistributeCommand: () => mixtapeDistributeCommand,
1343
- mixtapeDeleteCommand: () => mixtapeDeleteCommand,
1344
- mixtapeCreateCommand: () => mixtapeCreateCommand
1340
+ mixtapeDistributeCommand: () => mixtapeDistributeCommand
1345
1341
  });
1346
- import { existsSync, readFileSync as readFileSync2 } from "node:fs";
1347
1342
  async function mixtapesCommand() {
1348
1343
  const response = await publicApiGet("/api/mixtapes");
1349
1344
  return response.mixtapes;
1350
1345
  }
1351
- async function mixtapeCreateCommand(options) {
1352
- return adminApiPost("/api/admin/mixtapes", buildBody(options));
1353
- }
1354
1346
  async function mixtapeUpdateCommand(id, options) {
1355
1347
  return adminApiPatch(`/api/admin/mixtapes/${encodeURIComponent(id)}`, buildBody(options));
1356
1348
  }
1357
- async function mixtapeMembersCommand(id, refs, options) {
1358
- const members = refs.map((ref) => ({ ref }));
1359
- if (options.from) {
1360
- members.push(...parseCueFile(options.from));
1361
- }
1362
- return adminApiPut(`/api/admin/mixtapes/${encodeURIComponent(id)}/members`, { members });
1363
- }
1364
- async function mixtapePublishCommand(id) {
1365
- return adminApiPost(`/api/admin/mixtapes/${encodeURIComponent(id)}/publish`);
1366
- }
1367
- async function mixtapeDeleteCommand(id) {
1368
- return adminApiDelete(`/api/admin/mixtapes/${encodeURIComponent(id)}`);
1369
- }
1370
1349
  async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {}) {
1371
1350
  const mixtape = await mixtapeGetCommand(idOrLogId);
1372
1351
  if (!mixtape.id) {
1373
1352
  throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
1374
1353
  }
1375
- if (mixtape.status === "draft") {
1376
- throw new CliError2("mixtape_not_promoted", `${mixtape.id} is still a draft — promote its recording first:
1377
- ` + " fluncle admin recordings promote <recordingId>");
1378
- }
1379
1354
  const both = !options.youtube && !options.mixcloud;
1380
1355
  const doYoutube = both || Boolean(options.youtube);
1381
1356
  const doMixcloud = both || Boolean(options.mixcloud);
@@ -1388,7 +1363,8 @@ async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {
1388
1363
  const mixtapeId = mixtape.id;
1389
1364
  const logId = mixtape.logId;
1390
1365
  if (!logId) {
1391
- throw new CliError2("missing_log_id", "The mixtape has no Log IDwas it promoted successfully?");
1366
+ throw new CliError2("mixtape_not_promoted", `${mixtapeId} has no coordinate yetpromote its recording first:
1367
+ ` + " fluncle admin recordings promote <recordingId>");
1392
1368
  }
1393
1369
  if (!mixtape.durationMs) {
1394
1370
  const source = options.audio ?? options.video;
@@ -1495,65 +1471,6 @@ function buildBody(options) {
1495
1471
  }
1496
1472
  return body;
1497
1473
  }
1498
- function parseCueFile(filePath) {
1499
- if (!existsSync(filePath)) {
1500
- throw new CliError2("file_not_found", `Cue file not found: ${filePath}`);
1501
- }
1502
- const text = readFileSync2(filePath, "utf-8");
1503
- const trimmed = text.trim();
1504
- if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
1505
- try {
1506
- const parsed = JSON.parse(trimmed);
1507
- if (!Array.isArray(parsed)) {
1508
- throw new Error("not an array");
1509
- }
1510
- return parsed.map((entry, index) => {
1511
- if (typeof entry === "string") {
1512
- return { ref: entry.trim() };
1513
- }
1514
- const obj = entry;
1515
- if (typeof obj?.ref !== "string") {
1516
- throw new CliError2("invalid_cue_json", `Entry ${index + 1} missing "ref" string`);
1517
- }
1518
- const cue = { ref: obj.ref.trim() };
1519
- if (typeof obj.startMs === "number" && Number.isInteger(obj.startMs) && obj.startMs >= 0) {
1520
- cue.startMs = obj.startMs;
1521
- }
1522
- return cue;
1523
- });
1524
- } catch (error) {
1525
- if (error instanceof CliError2) {
1526
- throw error;
1527
- }
1528
- throw new CliError2("invalid_cue_json", `Cue JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
1529
- }
1530
- }
1531
- return parseCueSheet(text);
1532
- }
1533
- function parseCueSheet(text) {
1534
- const entries = [];
1535
- for (const line of text.split(/\r?\n/)) {
1536
- const trimmed = line.trim();
1537
- if (!trimmed || trimmed.startsWith("#")) {
1538
- continue;
1539
- }
1540
- const match = trimmed.match(/^(\d{1,2}:\d{2}(?::\d{2})?)\s+(.+)$/);
1541
- if (match) {
1542
- const [, time, ref] = match;
1543
- if (time === undefined || ref === undefined) {
1544
- continue;
1545
- }
1546
- const startMs = parseDuration(time);
1547
- if (startMs === null) {
1548
- continue;
1549
- }
1550
- entries.push({ ref: ref.trim(), startMs });
1551
- } else {
1552
- entries.push({ ref: trimmed });
1553
- }
1554
- }
1555
- return entries;
1556
- }
1557
1474
  var init_mixtapes = __esm(() => {
1558
1475
  init_util();
1559
1476
  init_api();
@@ -2251,7 +2168,7 @@ function parseVersion2(version) {
2251
2168
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2252
2169
  var init_version2 = __esm(() => {
2253
2170
  init_output();
2254
- currentVersion2 = "0.86.0".trim() ? "0.86.0".trim() : "0.1.0";
2171
+ currentVersion2 = "0.88.0".trim() ? "0.88.0".trim() : "0.1.0";
2255
2172
  });
2256
2173
 
2257
2174
  // ../../packages/registry/src/index.ts
@@ -2773,7 +2690,7 @@ var init_src = __esm(() => {
2773
2690
  {
2774
2691
  command: "fluncle admin",
2775
2692
  exposedContent: [
2776
- "the operator/agent command group (hidden): tracks publish|update|enrich|video|draft|social|preview|observe|context|note, mixtapes create|update|members|publish|delete, newsletter draft|update|send|list, backfills, auth"
2693
+ "the operator/agent command group (hidden): tracks publish|update|enrich|video|draft|social|preview|observe|context|note, recordings create|promote, mixtapes update|distribute|resync, newsletter draft|update|send|list, backfills, auth"
2777
2694
  ],
2778
2695
  kind: "cli",
2779
2696
  name: "cli.admin",
@@ -3668,7 +3585,7 @@ var init_mixtape_youtube2 = __esm(() => {
3668
3585
 
3669
3586
  // src/commands/mixtape-set-video.ts
3670
3587
  import { randomUUID } from "node:crypto";
3671
- import { existsSync as existsSync2, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
3588
+ import { existsSync, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
3672
3589
  import { tmpdir } from "node:os";
3673
3590
  import { join as join4 } from "node:path";
3674
3591
  function planMultipart(contentLength, partSize = DEFAULT_PART_SIZE) {
@@ -3725,7 +3642,7 @@ function renditionFfmpegArgs(inputPath, outputPath) {
3725
3642
  ];
3726
3643
  }
3727
3644
  async function uploadRenditionMultipart(masterPath, presignPath, onProgress = () => {}) {
3728
- if (!existsSync2(masterPath)) {
3645
+ if (!existsSync(masterPath)) {
3729
3646
  throw new CliError2("file_not_found", `Set-video master not found: ${masterPath}`);
3730
3647
  }
3731
3648
  await assertFfmpeg();
@@ -3838,7 +3755,7 @@ __export(exports_recordings, {
3838
3755
  recordingDeleteCommand: () => recordingDeleteCommand,
3839
3756
  recordingCreateCommand: () => recordingCreateCommand
3840
3757
  });
3841
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
3758
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
3842
3759
  async function recordingGet(id) {
3843
3760
  const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
3844
3761
  return response.recording;
@@ -3907,7 +3824,7 @@ async function recordingCreateCommand(options = {}) {
3907
3824
  if (!options.video) {
3908
3825
  throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
3909
3826
  }
3910
- if (!existsSync3(options.video)) {
3827
+ if (!existsSync2(options.video)) {
3911
3828
  throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
3912
3829
  }
3913
3830
  const created = await adminApiPost("/api/admin/recordings", {
@@ -3943,11 +3860,11 @@ async function recordingUpdateCommand(id, options = {}) {
3943
3860
  body.parentId = options.parentId;
3944
3861
  }
3945
3862
  if (options.tracklistFile !== undefined) {
3946
- if (!existsSync3(options.tracklistFile)) {
3863
+ if (!existsSync2(options.tracklistFile)) {
3947
3864
  throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
3948
3865
  }
3949
3866
  try {
3950
- body.tracklistJson = JSON.parse(readFileSync3(options.tracklistFile, "utf8"));
3867
+ body.tracklistJson = JSON.parse(readFileSync2(options.tracklistFile, "utf8"));
3951
3868
  } catch {
3952
3869
  throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
3953
3870
  }
@@ -3992,12 +3909,12 @@ async function recordingReplaceCuesCommand(id, options = {}) {
3992
3909
  if (!options.cuesFile) {
3993
3910
  throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
3994
3911
  }
3995
- if (!existsSync3(options.cuesFile)) {
3912
+ if (!existsSync2(options.cuesFile)) {
3996
3913
  throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
3997
3914
  }
3998
3915
  let cues;
3999
3916
  try {
4000
- cues = JSON.parse(readFileSync3(options.cuesFile, "utf8"));
3917
+ cues = JSON.parse(readFileSync2(options.cuesFile, "utf8"));
4001
3918
  } catch {
4002
3919
  throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
4003
3920
  }
@@ -4014,238 +3931,6 @@ var init_recordings = __esm(() => {
4014
3931
  init_mixtape_set_video();
4015
3932
  });
4016
3933
 
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
3934
  // src/commands/clips.ts
4250
3935
  var exports_clips = {};
4251
3936
  __export(exports_clips, {
@@ -4321,9 +4006,6 @@ async function clipsListCommand(filter = {}) {
4321
4006
  if (filter.recordingId) {
4322
4007
  params.set("recordingId", filter.recordingId);
4323
4008
  }
4324
- if (filter.mixtapeId) {
4325
- params.set("mixtapeId", filter.mixtapeId);
4326
- }
4327
4009
  if (filter.status) {
4328
4010
  params.set("status", filter.status);
4329
4011
  }
@@ -4332,28 +4014,17 @@ async function clipsListCommand(filter = {}) {
4332
4014
  return response.clips;
4333
4015
  }
4334
4016
  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
- };
4017
+ if (!clip.recordingId) {
4018
+ throw new CliError2("clip_unlinked", `Clip ${clip.id} is linked to no recording`);
4344
4019
  }
4345
- if (!clip.mixtapeId) {
4346
- throw new CliError2("clip_unlinked", `Clip ${clip.id} is linked to neither a recording nor a mixtape`);
4020
+ const { recordingGet: recordingGet2 } = await Promise.resolve().then(() => (init_recordings(), exports_recordings));
4021
+ const recording = await recordingGet2(clip.recordingId);
4022
+ if (!recording.r2Key) {
4023
+ throw new CliError2("recording_not_staged", `Recording ${clip.recordingId} has no staged set video`);
4347
4024
  }
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`);
4352
- }
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) };
4025
+ return {
4026
+ setUrl: `${FOUND_BASE3}/${recording.r2Key.split("/").map(encodeURIComponent).join("/")}`
4027
+ };
4357
4028
  }
4358
4029
  async function clipCutCommand(clipId, onProgress = () => {}) {
4359
4030
  const clips = await clipsListCommand();
@@ -4447,7 +4118,7 @@ __export(exports_recordings2, {
4447
4118
  recordingDeleteCommand: () => recordingDeleteCommand2,
4448
4119
  recordingCreateCommand: () => recordingCreateCommand2
4449
4120
  });
4450
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
4121
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
4451
4122
  async function recordingGet2(id) {
4452
4123
  const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
4453
4124
  return response.recording;
@@ -4516,7 +4187,7 @@ async function recordingCreateCommand2(options = {}) {
4516
4187
  if (!options.video) {
4517
4188
  throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
4518
4189
  }
4519
- if (!existsSync5(options.video)) {
4190
+ if (!existsSync3(options.video)) {
4520
4191
  throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
4521
4192
  }
4522
4193
  const created = await adminApiPost("/api/admin/recordings", {
@@ -4552,11 +4223,11 @@ async function recordingUpdateCommand2(id, options = {}) {
4552
4223
  body.parentId = options.parentId;
4553
4224
  }
4554
4225
  if (options.tracklistFile !== undefined) {
4555
- if (!existsSync5(options.tracklistFile)) {
4226
+ if (!existsSync3(options.tracklistFile)) {
4556
4227
  throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
4557
4228
  }
4558
4229
  try {
4559
- body.tracklistJson = JSON.parse(readFileSync5(options.tracklistFile, "utf8"));
4230
+ body.tracklistJson = JSON.parse(readFileSync3(options.tracklistFile, "utf8"));
4560
4231
  } catch {
4561
4232
  throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
4562
4233
  }
@@ -4601,12 +4272,12 @@ async function recordingReplaceCuesCommand2(id, options = {}) {
4601
4272
  if (!options.cuesFile) {
4602
4273
  throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
4603
4274
  }
4604
- if (!existsSync5(options.cuesFile)) {
4275
+ if (!existsSync3(options.cuesFile)) {
4605
4276
  throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
4606
4277
  }
4607
4278
  let cues;
4608
4279
  try {
4609
- cues = JSON.parse(readFileSync5(options.cuesFile, "utf8"));
4280
+ cues = JSON.parse(readFileSync3(options.cuesFile, "utf8"));
4610
4281
  } catch {
4611
4282
  throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
4612
4283
  }
@@ -4632,8 +4303,8 @@ __export(exports_newsletter, {
4632
4303
  newsletterDraftCommand: () => newsletterDraftCommand,
4633
4304
  newsletterDeleteCommand: () => newsletterDeleteCommand
4634
4305
  });
4635
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "node:fs";
4636
- function buildBody3(options, { requireContent }) {
4306
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
4307
+ function buildBody2(options, { requireContent }) {
4637
4308
  const body = {};
4638
4309
  if (options.contentFile !== undefined) {
4639
4310
  body.contentJson = readContentFile(options.contentFile);
@@ -4652,10 +4323,10 @@ function buildBody3(options, { requireContent }) {
4652
4323
  return body;
4653
4324
  }
4654
4325
  function readContentFile(filePath) {
4655
- if (!existsSync6(filePath)) {
4326
+ if (!existsSync4(filePath)) {
4656
4327
  throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
4657
4328
  }
4658
- const text = readFileSync6(filePath, "utf-8");
4329
+ const text = readFileSync4(filePath, "utf-8");
4659
4330
  try {
4660
4331
  return JSON.parse(text);
4661
4332
  } catch (error) {
@@ -4663,10 +4334,10 @@ function readContentFile(filePath) {
4663
4334
  }
4664
4335
  }
4665
4336
  async function newsletterDraftCommand(options) {
4666
- return adminApiPost("/api/admin/newsletter/editions", buildBody3(options, { requireContent: true }));
4337
+ return adminApiPost("/api/admin/newsletter/editions", buildBody2(options, { requireContent: true }));
4667
4338
  }
4668
4339
  async function newsletterUpdateCommand(id, options) {
4669
- return adminApiPatch(`/api/admin/newsletter/editions/${encodeURIComponent(id)}`, buildBody3(options, { requireContent: false }));
4340
+ return adminApiPatch(`/api/admin/newsletter/editions/${encodeURIComponent(id)}`, buildBody2(options, { requireContent: false }));
4670
4341
  }
4671
4342
  async function newsletterSendCommand(id) {
4672
4343
  return adminApiPost(`/api/admin/newsletter/editions/${encodeURIComponent(id)}/send`);
@@ -5290,7 +4961,7 @@ var init_format2 = __esm(() => {
5290
4961
  });
5291
4962
 
5292
4963
  // src/cli.ts
5293
- import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
4964
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
5294
4965
  import path2 from "path";
5295
4966
 
5296
4967
  // ../../node_modules/commander/lib/error.js
@@ -7456,8 +7127,8 @@ function addListenCommands(program2) {
7456
7127
  await runRecent(options, recentCommand3);
7457
7128
  });
7458
7129
  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);
7130
+ const { mixtapesCommand: mixtapesCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7131
+ await runMixtapes(options, mixtapesCommand2);
7461
7132
  });
7462
7133
  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
7134
  const openCommands = await Promise.resolve().then(() => (init_open(), exports_open));
@@ -7618,27 +7289,11 @@ function addAdminCommands(program2) {
7618
7289
  adminMixtapes.action(() => {
7619
7290
  adminMixtapes.outputHelp();
7620
7291
  });
7621
- 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);
7624
- });
7625
7292
  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);
7628
- });
7629
- 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);
7293
+ const { mixtapeUpdateCommand: mixtapeUpdateCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7294
+ await runMixtapeUpdate(id, options, mixtapeUpdateCommand2);
7632
7295
  });
7633
- 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);
7636
- });
7637
- 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);
7640
- });
7641
- adminMixtapes.command("list").description("List all mixtapes (including drafts)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
7296
+ adminMixtapes.command("list").description("List all mixtapes (including distributing)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
7642
7297
  const { mixtapeListCommand: mixtapeListCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7643
7298
  await runMixtapeList(options, mixtapeListCommand2);
7644
7299
  });
@@ -7647,22 +7302,22 @@ function addAdminCommands(program2) {
7647
7302
  await runMixtapeGet(idOrLogId, options, mixtapeGetCommand2);
7648
7303
  });
7649
7304
  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);
7305
+ const { mixtapeDistributeCommand: mixtapeDistributeCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7306
+ await runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand2);
7652
7307
  });
7653
7308
  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
7309
  const { publishYoutubeCommand: publishYoutubeCommand3 } = await Promise.resolve().then(() => (init_mixtape_youtube2(), exports_mixtape_youtube2));
7655
7310
  await runMixtapePublishYoutube(idOrLogId, options, publishYoutubeCommand3);
7656
7311
  });
7657
7312
  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);
7313
+ const { mixtapeResyncCommand: mixtapeResyncCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7314
+ await runMixtapeResync(idOrLogId, options, mixtapeResyncCommand2);
7660
7315
  });
7661
7316
  const adminClips = configureCommand(admin.command("clips").description("Mixtape clip (Fluncle Studio) commands"));
7662
7317
  adminClips.action(() => {
7663
7318
  adminClips.outputHelp();
7664
7319
  });
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) => {
7320
+ 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
7321
  const { clipsListCommand: clipsListCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
7667
7322
  await runClipsList(options, clipsListCommand2);
7668
7323
  });
@@ -7805,7 +7460,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
7805
7460
  console.log(` mime: ${result.mime}`);
7806
7461
  }
7807
7462
  async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
7808
- const script = options.scriptFile ? readFileSync7(options.scriptFile, "utf8") : options.script;
7463
+ const script = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
7809
7464
  if (!idOrLogId || !script || !script.trim()) {
7810
7465
  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
7466
  }
@@ -7861,7 +7516,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
7861
7516
  }
7862
7517
  }
7863
7518
  async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
7864
- const note = options.scriptFile ? readFileSync7(options.scriptFile, "utf8") : options.script;
7519
+ const note = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
7865
7520
  if (!idOrLogId || !note || !note.trim()) {
7866
7521
  throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--json]");
7867
7522
  }
@@ -8007,7 +7662,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
8007
7662
  return;
8008
7663
  }
8009
7664
  const candidate = path2.join(dir, name);
8010
- return existsSync7(candidate) ? candidate : undefined;
7665
+ return existsSync5(candidate) ? candidate : undefined;
8011
7666
  };
8012
7667
  const resolveFile = (explicit, name) => {
8013
7668
  if (explicit) {
@@ -8184,56 +7839,23 @@ async function runTrackPurgeVideo(idOrLogId, options, trackPurgeVideoCommand2) {
8184
7839
  }
8185
7840
  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
7841
  }
8187
- async function runMixtapeCreate(options, mixtapeCreateCommand3) {
8188
- const result = await mixtapeCreateCommand3(options);
8189
- if (options.json) {
8190
- printJson(result);
8191
- return;
8192
- }
8193
- console.log(`Logged draft ${result.mixtape.id}. It stays a draft until you publish it.`);
8194
- }
8195
- async function runMixtapeUpdate(id, options, mixtapeUpdateCommand3) {
7842
+ async function runMixtapeUpdate(id, options, mixtapeUpdateCommand2) {
8196
7843
  if (!id) {
8197
7844
  throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes update <id>");
8198
7845
  }
8199
- const result = await mixtapeUpdateCommand3(id, options);
7846
+ const result = await mixtapeUpdateCommand2(id, options);
8200
7847
  if (options.json) {
8201
7848
  printJson(result);
8202
7849
  return;
8203
7850
  }
8204
7851
  console.log(`Saved ${result.mixtape.id}.`);
8205
7852
  }
8206
- async function runMixtapeMembers(id, refs, options, mixtapeMembersCommand3) {
8207
- if (!id) {
8208
- throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes members <id> [refs...]");
8209
- }
8210
- if (refs.length === 0 && !options.from) {
8211
- throw new Error("Provide refs as arguments or a cue-sheet file with --from");
8212
- }
8213
- const result = await mixtapeMembersCommand3(id, refs, options);
8214
- if (options.json) {
8215
- printJson(result);
8216
- return;
8217
- }
8218
- console.log(`Saved the tracklist: ${result.mixtape.memberCount} bangers on ${result.mixtape.id}.`);
8219
- }
8220
- async function runMixtapePublish(id, options, mixtapePublishCommand3) {
8221
- if (!id) {
8222
- throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes publish <id>");
8223
- }
8224
- const result = await mixtapePublishCommand3(id);
8225
- if (options.json) {
8226
- printJson(result);
8227
- return;
8228
- }
8229
- console.log(`Minted ${result.mixtape.logId} (fluncle://${result.mixtape.logId}) \u2014 distributing. ` + "Run `distribute` to push it to the platforms.");
8230
- }
8231
- async function runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand3) {
7853
+ async function runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand2) {
8232
7854
  if (!idOrLogId) {
8233
7855
  throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes distribute <idOrLogId> --video <mp4> --audio <file>");
8234
7856
  }
8235
7857
  const onProgress = options.json ? () => {} : (message) => console.log(message);
8236
- const result = await mixtapeDistributeCommand3(idOrLogId, options, onProgress);
7858
+ const result = await mixtapeDistributeCommand2(idOrLogId, options, onProgress);
8237
7859
  if (options.json) {
8238
7860
  printJson(result);
8239
7861
  return;
@@ -8254,12 +7876,12 @@ async function runMixtapePublishYoutube(idOrLogId, options, publishYoutubeComman
8254
7876
  }
8255
7877
  console.log(`YouTube video is now public: ${result.url}`);
8256
7878
  }
8257
- async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand3) {
7879
+ async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand2) {
8258
7880
  if (!idOrLogId) {
8259
7881
  throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes resync <idOrLogId>");
8260
7882
  }
8261
7883
  const onProgress = options.json ? () => {} : (message) => console.log(message);
8262
- const result = await mixtapeResyncCommand3(idOrLogId, options, onProgress);
7884
+ const result = await mixtapeResyncCommand2(idOrLogId, options, onProgress);
8263
7885
  if (options.json) {
8264
7886
  printJson(result);
8265
7887
  return;
@@ -8269,20 +7891,6 @@ async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand3) {
8269
7891
  console.log(`Re-synced ${result.logId} (fluncle://${result.logId}).
8270
7892
  ${links}`);
8271
7893
  }
8272
- async function runMixtapeDelete(id, options, mixtapeDeleteCommand3) {
8273
- if (!id) {
8274
- throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes delete <id> --yes");
8275
- }
8276
- if (!options.yes) {
8277
- throw new Error("Pass --yes to confirm the discard. Published mixtapes can't be deleted.");
8278
- }
8279
- await mixtapeDeleteCommand3(id);
8280
- if (options.json) {
8281
- printJson({ id, ok: true });
8282
- return;
8283
- }
8284
- console.log(`Discarded draft ${id}.`);
8285
- }
8286
7894
  async function runMixtapeList(options, mixtapeListCommand2) {
8287
7895
  const mixtapes = await mixtapeListCommand2();
8288
7896
  if (options.json) {
@@ -8294,14 +7902,13 @@ async function runMixtapeList(options, mixtapeListCommand2) {
8294
7902
  return;
8295
7903
  }
8296
7904
  for (const mixtape of mixtapes) {
8297
- const status = mixtape.status ?? "draft";
8298
- const coordinate3 = mixtape.logId ?? "draft";
7905
+ const status = mixtape.status ?? "distributing";
7906
+ const coordinate3 = mixtape.logId ?? "unminted";
8299
7907
  console.log(`${coordinate3} ${status} ${mixtape.memberCount} bangers ${mixtape.title}`);
8300
7908
  }
8301
7909
  }
8302
7910
  async function runClipsList(options, clipsListCommand2) {
8303
7911
  const clips = await clipsListCommand2({
8304
- mixtapeId: options.mixtape,
8305
7912
  recordingId: options.recording,
8306
7913
  status: options.status
8307
7914
  });
@@ -8314,7 +7921,7 @@ async function runClipsList(options, clipsListCommand2) {
8314
7921
  return;
8315
7922
  }
8316
7923
  for (const clip of clips) {
8317
- const source = clip.recordingId ?? clip.mixtapeId ?? "\u2014";
7924
+ const source = clip.recordingId ?? "\u2014";
8318
7925
  console.log(`${clip.id} ${clip.status} ${source} ${clip.inMs}-${clip.outMs}ms x=${clip.xOffset}`);
8319
7926
  }
8320
7927
  }
@@ -8339,8 +7946,8 @@ async function runMixtapeGet(idOrLogId, options, mixtapeGetCommand2) {
8339
7946
  printJson(mixtape);
8340
7947
  return;
8341
7948
  }
8342
- const coordinate3 = mixtape.logId ?? "draft";
8343
- const status = mixtape.status ?? "draft";
7949
+ const coordinate3 = mixtape.logId ?? "unminted";
7950
+ const status = mixtape.status ?? "distributing";
8344
7951
  console.log(`${mixtape.title}`);
8345
7952
  console.log(` ${coordinate3} \xB7 ${status} \xB7 ${mixtape.memberCount} bangers`);
8346
7953
  if (mixtape.note) {
@@ -8477,8 +8084,8 @@ async function runRecent(options, recentCommand3) {
8477
8084
  console.log(trackRows2(tracks).join(`
8478
8085
  `));
8479
8086
  }
8480
- async function runMixtapes(options, mixtapesCommand3) {
8481
- const mixtapes = await mixtapesCommand3();
8087
+ async function runMixtapes(options, mixtapesCommand2) {
8088
+ const mixtapes = await mixtapesCommand2();
8482
8089
  if (options.json) {
8483
8090
  printJson({ mixtapes, ok: true });
8484
8091
  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.88.0"
35
35
  }