fluncle 0.83.0 → 0.85.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 +117 -15
  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.83.0".trim() ? "0.83.0".trim() : "0.1.0";
541
+ currentVersion = "0.85.0".trim() ? "0.85.0".trim() : "0.1.0";
542
542
  });
543
543
 
544
544
  // src/update-notifier.ts
@@ -2446,7 +2446,7 @@ function parseVersion2(version) {
2446
2446
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2447
2447
  var init_version2 = __esm(() => {
2448
2448
  init_output();
2449
- currentVersion2 = "0.83.0".trim() ? "0.83.0".trim() : "0.1.0";
2449
+ currentVersion2 = "0.85.0".trim() ? "0.85.0".trim() : "0.1.0";
2450
2450
  });
2451
2451
 
2452
2452
  // ../../packages/registry/src/index.ts
@@ -3866,6 +3866,7 @@ var exports_recordings = {};
3866
3866
  __export(exports_recordings, {
3867
3867
  recordingsListCommand: () => recordingsListCommand,
3868
3868
  recordingUpdateCommand: () => recordingUpdateCommand,
3869
+ recordingReplaceCuesCommand: () => recordingReplaceCuesCommand,
3869
3870
  recordingPromoteCommand: () => recordingPromoteCommand,
3870
3871
  recordingGetCommand: () => recordingGetCommand,
3871
3872
  recordingGet: () => recordingGet,
@@ -3878,12 +3879,21 @@ async function recordingGet(id) {
3878
3879
  return response.recording;
3879
3880
  }
3880
3881
  function formatRecordingSummary(recording) {
3882
+ const kind = recording.hasVideo ? `take v${recording.version}` : "plan";
3881
3883
  const promoted = recording.logId ? ` → fluncle://${recording.logId}` : " (un-promoted)";
3882
3884
  const cues = recording.tracklist.length;
3883
- return `${recording.id} ${recording.title}${promoted} [${cues} cue${cues === 1 ? "" : "s"}]`;
3885
+ return `${recording.id} [${kind}] ${recording.title}${promoted} [${cues} cue${cues === 1 ? "" : "s"}]`;
3884
3886
  }
3885
3887
  async function recordingsListCommand(options = {}) {
3886
- const response = await adminApiGet("/api/admin/recordings");
3888
+ const query = new URLSearchParams;
3889
+ if (options.kind) {
3890
+ query.set("kind", options.kind);
3891
+ }
3892
+ if (options.parentId) {
3893
+ query.set("parentId", options.parentId);
3894
+ }
3895
+ const suffix = query.toString() ? `?${query.toString()}` : "";
3896
+ const response = await adminApiGet(`/api/admin/recordings${suffix}`);
3887
3897
  if (options.json) {
3888
3898
  printJson2({ ok: true, recordings: response.recordings });
3889
3899
  return;
@@ -3905,16 +3915,29 @@ async function recordingGetCommand(id, options = {}) {
3905
3915
  return;
3906
3916
  }
3907
3917
  console.log(formatRecordingSummary(recording));
3908
- console.log(` r2Key: ${recording.r2Key}`);
3918
+ console.log(` r2Key: ${recording.r2Key ?? "— (no set video)"}`);
3909
3919
  for (const cue of recording.tracklist) {
3910
3920
  const at = cue.startMs === undefined ? "—" : `${(cue.startMs / 1000).toFixed(1)}s`;
3911
3921
  console.log(` ${at} ${cue.artists.join(", ")} — ${cue.title}`);
3912
3922
  }
3913
3923
  }
3914
3924
  async function recordingCreateCommand(options = {}) {
3925
+ if (options.plan) {
3926
+ const created2 = await adminApiPost("/api/admin/recordings", {
3927
+ kind: "plan",
3928
+ recordedAt: options.recordedAt
3929
+ });
3930
+ const recording2 = created2.recording;
3931
+ if (options.json) {
3932
+ printJson2({ ok: true, recording: recording2 });
3933
+ return;
3934
+ }
3935
+ console.log(`Plan ${recording2.id} created — handle "${recording2.title}"`);
3936
+ return;
3937
+ }
3915
3938
  const title = options.title?.trim();
3916
3939
  if (!title) {
3917
- throw new CliError2("missing_title", "A recording needs a --title");
3940
+ throw new CliError2("missing_title", "A recording needs a --title (or --plan for a videoless plan)");
3918
3941
  }
3919
3942
  if (!options.video) {
3920
3943
  throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
@@ -3951,6 +3974,9 @@ async function recordingUpdateCommand(id, options = {}) {
3951
3974
  if (options.recordedAt !== undefined) {
3952
3975
  body.recordedAt = options.recordedAt;
3953
3976
  }
3977
+ if (options.parentId !== undefined) {
3978
+ body.parentId = options.parentId;
3979
+ }
3954
3980
  if (options.tracklistFile !== undefined) {
3955
3981
  if (!existsSync3(options.tracklistFile)) {
3956
3982
  throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
@@ -3962,7 +3988,7 @@ async function recordingUpdateCommand(id, options = {}) {
3962
3988
  }
3963
3989
  }
3964
3990
  if (Object.keys(body).length === 0) {
3965
- throw new CliError2("no_fields", "Nothing to update — pass --title, --recorded-at, or --tracklist-file");
3991
+ throw new CliError2("no_fields", "Nothing to update — pass --title, --recorded-at, --parent-id, or --tracklist-file");
3966
3992
  }
3967
3993
  const response = await adminApiPatch(`/api/admin/recordings/${encodeURIComponent(id)}`, body);
3968
3994
  if (options.json) {
@@ -3994,6 +4020,29 @@ async function recordingPromoteCommand(id, options = {}) {
3994
4020
  }
3995
4021
  console.log(recording.logId ? `Promoted recording ${id} → mixtape fluncle://${recording.logId}` : `Promoted recording ${id}`);
3996
4022
  }
4023
+ async function recordingReplaceCuesCommand(id, options = {}) {
4024
+ if (!id) {
4025
+ throw new CliError2("missing_id", "Missing recording id for: replace-cues");
4026
+ }
4027
+ if (!options.cuesFile) {
4028
+ throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
4029
+ }
4030
+ if (!existsSync3(options.cuesFile)) {
4031
+ throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
4032
+ }
4033
+ let cues;
4034
+ try {
4035
+ cues = JSON.parse(readFileSync3(options.cuesFile, "utf8"));
4036
+ } catch {
4037
+ throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
4038
+ }
4039
+ const response = await adminApiPut(`/api/admin/recordings/${encodeURIComponent(id)}/cues`, { cues });
4040
+ if (options.json) {
4041
+ printJson2({ ok: true, recording: response.recording });
4042
+ return;
4043
+ }
4044
+ console.log(`Replaced cues for ${formatRecordingSummary(response.recording)}`);
4045
+ }
3997
4046
  var init_recordings = __esm(() => {
3998
4047
  init_api();
3999
4048
  init_output();
@@ -4441,6 +4490,7 @@ var exports_recordings2 = {};
4441
4490
  __export(exports_recordings2, {
4442
4491
  recordingsListCommand: () => recordingsListCommand2,
4443
4492
  recordingUpdateCommand: () => recordingUpdateCommand2,
4493
+ recordingReplaceCuesCommand: () => recordingReplaceCuesCommand2,
4444
4494
  recordingPromoteCommand: () => recordingPromoteCommand2,
4445
4495
  recordingGetCommand: () => recordingGetCommand2,
4446
4496
  recordingGet: () => recordingGet2,
@@ -4453,12 +4503,21 @@ async function recordingGet2(id) {
4453
4503
  return response.recording;
4454
4504
  }
4455
4505
  function formatRecordingSummary2(recording) {
4506
+ const kind = recording.hasVideo ? `take v${recording.version}` : "plan";
4456
4507
  const promoted = recording.logId ? ` → fluncle://${recording.logId}` : " (un-promoted)";
4457
4508
  const cues = recording.tracklist.length;
4458
- return `${recording.id} ${recording.title}${promoted} [${cues} cue${cues === 1 ? "" : "s"}]`;
4509
+ return `${recording.id} [${kind}] ${recording.title}${promoted} [${cues} cue${cues === 1 ? "" : "s"}]`;
4459
4510
  }
4460
4511
  async function recordingsListCommand2(options = {}) {
4461
- const response = await adminApiGet("/api/admin/recordings");
4512
+ const query = new URLSearchParams;
4513
+ if (options.kind) {
4514
+ query.set("kind", options.kind);
4515
+ }
4516
+ if (options.parentId) {
4517
+ query.set("parentId", options.parentId);
4518
+ }
4519
+ const suffix = query.toString() ? `?${query.toString()}` : "";
4520
+ const response = await adminApiGet(`/api/admin/recordings${suffix}`);
4462
4521
  if (options.json) {
4463
4522
  printJson2({ ok: true, recordings: response.recordings });
4464
4523
  return;
@@ -4480,16 +4539,29 @@ async function recordingGetCommand2(id, options = {}) {
4480
4539
  return;
4481
4540
  }
4482
4541
  console.log(formatRecordingSummary2(recording));
4483
- console.log(` r2Key: ${recording.r2Key}`);
4542
+ console.log(` r2Key: ${recording.r2Key ?? "— (no set video)"}`);
4484
4543
  for (const cue of recording.tracklist) {
4485
4544
  const at = cue.startMs === undefined ? "—" : `${(cue.startMs / 1000).toFixed(1)}s`;
4486
4545
  console.log(` ${at} ${cue.artists.join(", ")} — ${cue.title}`);
4487
4546
  }
4488
4547
  }
4489
4548
  async function recordingCreateCommand2(options = {}) {
4549
+ if (options.plan) {
4550
+ const created2 = await adminApiPost("/api/admin/recordings", {
4551
+ kind: "plan",
4552
+ recordedAt: options.recordedAt
4553
+ });
4554
+ const recording2 = created2.recording;
4555
+ if (options.json) {
4556
+ printJson2({ ok: true, recording: recording2 });
4557
+ return;
4558
+ }
4559
+ console.log(`Plan ${recording2.id} created — handle "${recording2.title}"`);
4560
+ return;
4561
+ }
4490
4562
  const title = options.title?.trim();
4491
4563
  if (!title) {
4492
- throw new CliError2("missing_title", "A recording needs a --title");
4564
+ throw new CliError2("missing_title", "A recording needs a --title (or --plan for a videoless plan)");
4493
4565
  }
4494
4566
  if (!options.video) {
4495
4567
  throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
@@ -4526,6 +4598,9 @@ async function recordingUpdateCommand2(id, options = {}) {
4526
4598
  if (options.recordedAt !== undefined) {
4527
4599
  body.recordedAt = options.recordedAt;
4528
4600
  }
4601
+ if (options.parentId !== undefined) {
4602
+ body.parentId = options.parentId;
4603
+ }
4529
4604
  if (options.tracklistFile !== undefined) {
4530
4605
  if (!existsSync5(options.tracklistFile)) {
4531
4606
  throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
@@ -4537,7 +4612,7 @@ async function recordingUpdateCommand2(id, options = {}) {
4537
4612
  }
4538
4613
  }
4539
4614
  if (Object.keys(body).length === 0) {
4540
- throw new CliError2("no_fields", "Nothing to update — pass --title, --recorded-at, or --tracklist-file");
4615
+ throw new CliError2("no_fields", "Nothing to update — pass --title, --recorded-at, --parent-id, or --tracklist-file");
4541
4616
  }
4542
4617
  const response = await adminApiPatch(`/api/admin/recordings/${encodeURIComponent(id)}`, body);
4543
4618
  if (options.json) {
@@ -4569,6 +4644,29 @@ async function recordingPromoteCommand2(id, options = {}) {
4569
4644
  }
4570
4645
  console.log(recording.logId ? `Promoted recording ${id} → mixtape fluncle://${recording.logId}` : `Promoted recording ${id}`);
4571
4646
  }
4647
+ async function recordingReplaceCuesCommand2(id, options = {}) {
4648
+ if (!id) {
4649
+ throw new CliError2("missing_id", "Missing recording id for: replace-cues");
4650
+ }
4651
+ if (!options.cuesFile) {
4652
+ throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
4653
+ }
4654
+ if (!existsSync5(options.cuesFile)) {
4655
+ throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
4656
+ }
4657
+ let cues;
4658
+ try {
4659
+ cues = JSON.parse(readFileSync5(options.cuesFile, "utf8"));
4660
+ } catch {
4661
+ throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
4662
+ }
4663
+ const response = await adminApiPut(`/api/admin/recordings/${encodeURIComponent(id)}/cues`, { cues });
4664
+ if (options.json) {
4665
+ printJson2({ ok: true, recording: response.recording });
4666
+ return;
4667
+ }
4668
+ console.log(`Replaced cues for ${formatRecordingSummary2(response.recording)}`);
4669
+ }
4572
4670
  var init_recordings2 = __esm(() => {
4573
4671
  init_api();
4574
4672
  init_output();
@@ -7626,11 +7724,11 @@ function addAdminCommands(program2) {
7626
7724
  adminRecordings.action(() => {
7627
7725
  adminRecordings.outputHelp();
7628
7726
  });
7629
- adminRecordings.command("create").description("Create a recording + stage its set video (from --video)").option("--title <text>", "Recording title").option("--video <file>", "Set-video master to stage (a 1080p rendition is derived)").option("--recorded-at <date>", "Recorded date (ISO)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
7727
+ adminRecordings.command("create").description("Create a recording (--video to stage a take, or --plan for a videoless plan)").option("--plan", "Create a videoless PLAN (server mints a Galaxy-vocab handle)", false).option("--title <text>", "Recording title (a take)").option("--video <file>", "Set-video master to stage (a 1080p rendition is derived)").option("--recorded-at <date>", "Recorded date (ISO)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
7630
7728
  const { recordingCreateCommand: recordingCreateCommand3 } = await Promise.resolve().then(() => (init_recordings2(), exports_recordings2));
7631
7729
  await recordingCreateCommand3(options);
7632
7730
  });
7633
- adminRecordings.command("list").description("List every recording").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
7731
+ adminRecordings.command("list").description("List recordings (--kind plan|take, --parent-id <plan> for a plan's takes)").option("--kind <kind>", "Filter by kind: plan | take").option("--parent-id <id>", "List the takes attached to this plan").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
7634
7732
  const { recordingsListCommand: recordingsListCommand3 } = await Promise.resolve().then(() => (init_recordings2(), exports_recordings2));
7635
7733
  await recordingsListCommand3(options);
7636
7734
  });
@@ -7638,10 +7736,14 @@ function addAdminCommands(program2) {
7638
7736
  const { recordingGetCommand: recordingGetCommand3 } = await Promise.resolve().then(() => (init_recordings2(), exports_recordings2));
7639
7737
  await recordingGetCommand3(id, options);
7640
7738
  });
7641
- adminRecordings.command("update").description("Update a recording's title, recorded date, or tracklist (--tracklist-file)").argument("[id]").option("--title <text>", "Recording title").option("--recorded-at <date>", "Recorded date (ISO)").option("--tracklist-file <file>", "JSON file with the whole cue tracklist array").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
7739
+ adminRecordings.command("update").description("Update a recording's title/recorded date/tracklist, or attach it to a plan").argument("[id]").option("--title <text>", "Recording title").option("--recorded-at <date>", "Recorded date (ISO)").option("--parent-id <id>", "Attach this take to its plan (assigns the take's version)").option("--tracklist-file <file>", "JSON file with the whole cue tracklist array").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
7642
7740
  const { recordingUpdateCommand: recordingUpdateCommand3 } = await Promise.resolve().then(() => (init_recordings2(), exports_recordings2));
7643
7741
  await recordingUpdateCommand3(id, options);
7644
7742
  });
7743
+ adminRecordings.command("replace-cues").description("Replace a recording's whole cue tracklist from a JSON file (--cues-file)").argument("[id]").option("--cues-file <file>", "JSON file with the ordered cue array").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
7744
+ const { recordingReplaceCuesCommand: recordingReplaceCuesCommand3 } = await Promise.resolve().then(() => (init_recordings2(), exports_recordings2));
7745
+ await recordingReplaceCuesCommand3(id, options);
7746
+ });
7645
7747
  adminRecordings.command("delete").description("Delete a recording (cascade its clips)").argument("[id]").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
7646
7748
  const { recordingDeleteCommand: recordingDeleteCommand3 } = await Promise.resolve().then(() => (init_recordings2(), exports_recordings2));
7647
7749
  await recordingDeleteCommand3(id, options);
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.83.0"
34
+ "version": "0.85.0"
35
35
  }