fluncle 0.89.0 → 0.90.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 +37 -7
  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.89.0".trim() ? "0.89.0".trim() : "0.1.0";
541
+ currentVersion = "0.90.0".trim() ? "0.90.0".trim() : "0.1.0";
542
542
  });
543
543
 
544
544
  // src/update-notifier.ts
@@ -2168,7 +2168,7 @@ function parseVersion2(version) {
2168
2168
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2169
2169
  var init_version2 = __esm(() => {
2170
2170
  init_output();
2171
- currentVersion2 = "0.89.0".trim() ? "0.89.0".trim() : "0.1.0";
2171
+ currentVersion2 = "0.90.0".trim() ? "0.90.0".trim() : "0.1.0";
2172
2172
  });
2173
2173
 
2174
2174
  // ../../packages/registry/src/index.ts
@@ -2895,17 +2895,30 @@ __export(exports_track, {
2895
2895
  trackGetCommand: () => trackGetCommand,
2896
2896
  trackDraftCommand: () => trackDraftCommand,
2897
2897
  trackContextCommand: () => trackContextCommand,
2898
+ isPlatesOnlyUpload: () => isPlatesOnlyUpload,
2898
2899
  checkBundleCompleteness: () => checkBundleCompleteness
2899
2900
  });
2900
2901
  async function trackGetCommand(idOrLogId) {
2901
2902
  return publicApiGet(`/api/tracks/${encodeURIComponent(idOrLogId)}`);
2902
2903
  }
2904
+ function isPlatesOnlyUpload(files) {
2905
+ const hasPlate = PLATE_FIELDS.some((option) => Boolean(files[option]));
2906
+ if (!hasPlate) {
2907
+ return false;
2908
+ }
2909
+ return Object.keys(files).every((option) => !files[option] || PLATE_FIELDS.includes(option) || NON_FILE_OPTIONS.includes(option));
2910
+ }
2903
2911
  function checkBundleCompleteness(files) {
2904
2912
  const uploadingFootage = FOOTAGE_FIELDS.some((option) => Boolean(files[option]));
2905
2913
  const missingFrom = (specs) => uploadingFootage ? specs.filter((spec) => !files[spec.option]).map((spec) => spec.file) : [];
2914
+ const plateWarnings = [];
2915
+ if (files.plateBackground && !files.plate) {
2916
+ plateWarnings.push("plate.background.png without plate.png — the background is the parallax layer OF a plate; pass --plate (or drop it in the --dir) too");
2917
+ }
2906
2918
  return {
2907
2919
  missingAdvisory: missingFrom(RERENDER_ADVISORY_FIELDS),
2908
2920
  missingContract: missingFrom(RERENDER_CONTRACT_FIELDS),
2921
+ plateWarnings,
2909
2922
  uploadingFootage
2910
2923
  };
2911
2924
  }
@@ -2922,6 +2935,9 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
2922
2935
  onProgress?.(`warning: ${missing} missing (provenance/eval only) — shipping without it`);
2923
2936
  }
2924
2937
  }
2938
+ for (const warning of completeness.plateWarnings) {
2939
+ onProgress?.(`warning: ${warning}`);
2940
+ }
2925
2941
  const present = VIDEO_FIELDS.map((spec) => ({
2926
2942
  field: spec.field,
2927
2943
  path: files[spec.option]
@@ -2929,6 +2945,7 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
2929
2945
  if (present.length === 0) {
2930
2946
  throw new CliError2("nothing_to_upload", "No bundle files resolved to upload (pass --dir <bundle> or explicit file flags).");
2931
2947
  }
2948
+ const platesOnly = isPlatesOnlyUpload(files);
2932
2949
  const presign = await adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/video/uploads`, { fields: present.map((spec) => spec.field) });
2933
2950
  const byField = new Map(presign.uploads.map((upload) => [upload.field, upload]));
2934
2951
  const urls = {};
@@ -2949,6 +2966,10 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
2949
2966
  }
2950
2967
  urls[spec.field] = `${FOUND_BASE}/${upload.key}`;
2951
2968
  }
2969
+ if (platesOnly) {
2970
+ onProgress?.(`plate pre-upload complete — compose against ${FOUND_BASE}/${presign.logId}/plate.png; finalize is deferred to the footage ship`);
2971
+ return { logId: presign.logId, ok: true, trackId: presign.trackId, urls };
2972
+ }
2952
2973
  const manifest = files.render ? await readManifestFields(files.render) : {};
2953
2974
  const videoModel = files.model?.trim().slice(0, 120) || manifest.model || DEFAULT_VIDEO_MODEL;
2954
2975
  const videoModelReasoning = files.reasoning?.trim().slice(0, 120) || manifest.reasoning || DEFAULT_VIDEO_REASONING;
@@ -3058,7 +3079,7 @@ async function trackNoteCommand(idOrLogId, options) {
3058
3079
  const body = { note: options.note };
3059
3080
  return adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/note`, body);
3060
3081
  }
3061
- var DEFAULT_VIDEO_MODEL = "anthropic/claude-opus-4-8", DEFAULT_VIDEO_REASONING = "high", FOUND_BASE = "https://found.fluncle.com", VIDEO_FIELDS, RERENDER_CONTRACT_FIELDS, RERENDER_ADVISORY_FIELDS, FOOTAGE_FIELDS;
3082
+ var DEFAULT_VIDEO_MODEL = "anthropic/claude-opus-4-8", DEFAULT_VIDEO_REASONING = "high", FOUND_BASE = "https://found.fluncle.com", VIDEO_FIELDS, RERENDER_CONTRACT_FIELDS, RERENDER_ADVISORY_FIELDS, FOOTAGE_FIELDS, PLATE_FIELDS, NON_FILE_OPTIONS;
3062
3083
  var init_track = __esm(() => {
3063
3084
  init_api();
3064
3085
  init_output();
@@ -3070,6 +3091,8 @@ var init_track = __esm(() => {
3070
3091
  { field: "footage-landscape-social", option: "footageLandscapeSocial" },
3071
3092
  { field: "poster", option: "poster" },
3072
3093
  { field: "cover", option: "cover" },
3094
+ { field: "plate", option: "plate" },
3095
+ { field: "plate-background", option: "plateBackground" },
3073
3096
  { field: "note", option: "note" },
3074
3097
  { field: "composition", option: "composition" },
3075
3098
  { field: "props", option: "props" },
@@ -3095,6 +3118,8 @@ var init_track = __esm(() => {
3095
3118
  "footageLandscape",
3096
3119
  "footageLandscapeSocial"
3097
3120
  ];
3121
+ PLATE_FIELDS = ["plate", "plateBackground"];
3122
+ NON_FILE_OPTIONS = ["model", "reasoning"];
3098
3123
  });
3099
3124
 
3100
3125
  // src/commands/add.ts
@@ -7272,7 +7297,7 @@ function addAdminCommands(program2) {
7272
7297
  const { trackUpdateCommand: trackUpdateCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
7273
7298
  await runTrackUpdate(trackId, options, trackUpdateCommand2);
7274
7299
  });
7275
- adminTrack.command("video").description("Upload a track's video bundle to R2 and link it").argument("[idOrLogId]").option("--allow-partial", "Allow an intentionally partial upload (skip the re-render-contract check; e.g. poster-only)", false).option("--composition <file>", "Composition source file").option("--cover <file>", "Cover image").option("--dir <dir>", "Bundle directory").option("--footage <file>", "Video footage (square crop source)").option("--footage-landscape <file>", "Clean landscape cut (optional escape hatch)").option("--footage-landscape-social <file>", "Landscape cut with baked text (optional)").option("--footage-notext <file>", "Portrait cut without the type layer (optional)").option("--footage-social <file>", "Portrait social cut (baked text)").option("--intent <file>", "Render-intent JSON (optional)").option("--json", "Print JSON", false).option("--metrics <file>", "Gate metrics JSON (optional)").option("--model <model>", "Authoring AI model (<provider>/<model>)").option("--note <file>", "Note file").option("--poster <file>", "Poster image").option("--props <file>", "Render props JSON").option("--reasoning <level>", "Authoring model reasoning effort (e.g. high)").option("--render <file>", "Render metadata JSON").option("--scene <file>", "Scene replay manifest JSON (fluncle.scene/1, optional)").allowExcessArguments().action(async (idOrLogId, options) => {
7300
+ adminTrack.command("video").description("Upload a track's video bundle to R2 and link it").argument("[idOrLogId]").option("--allow-partial", "Allow an intentionally partial upload (skip the re-render-contract check; e.g. poster-only)", false).option("--composition <file>", "Composition source file").option("--cover <file>", "Cover image").option("--dir <dir>", "Bundle directory").option("--footage <file>", "Video footage (square crop source)").option("--footage-landscape <file>", "Clean landscape cut (optional escape hatch)").option("--footage-landscape-social <file>", "Landscape cut with baked text (optional)").option("--footage-notext <file>", "Portrait cut without the type layer (optional)").option("--footage-social <file>", "Portrait social cut (baked text)").option("--intent <file>", "Render-intent JSON (optional)").option("--json", "Print JSON", false).option("--metrics <file>", "Gate metrics JSON (optional)").option("--model <model>", "Authoring AI model (<provider>/<model>)").option("--note <file>", "Note file").option("--plate <file>", "Plate-lane photographic plate (plate.png; uploadable pre-render)").option("--plate-background <file>", "The plate's subject-removed background (plate.background.png, optional)").option("--poster <file>", "Poster image").option("--props <file>", "Render props JSON").option("--reasoning <level>", "Authoring model reasoning effort (e.g. high)").option("--render <file>", "Render metadata JSON").option("--scene <file>", "Scene replay manifest JSON (fluncle.scene/1, optional)").allowExcessArguments().action(async (idOrLogId, options) => {
7276
7301
  const { trackVideoCommand: trackVideoCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
7277
7302
  await runTrackVideo(idOrLogId, options, trackVideoCommand2);
7278
7303
  });
@@ -7697,7 +7722,7 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
7697
7722
  }
7698
7723
  async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
7699
7724
  if (!idOrLogId) {
7700
- throw new Error("Missing id. Usage: fluncle admin tracks video <track_id|log_id> (--dir <dir> | --footage <file> [--footage-social <file>] [--footage-notext <file>] [--footage-landscape <file>] [--footage-landscape-social <file>] [--poster <file>] [--cover <file>] [--note <file>] [--composition <file>] [--props <file>] [--render <file>] [--intent <file>] [--metrics <file>] [--scene <file>]) [--allow-partial]");
7725
+ throw new Error("Missing id. Usage: fluncle admin tracks video <track_id|log_id> (--dir <dir> | --footage <file> [--footage-social <file>] [--footage-notext <file>] [--footage-landscape <file>] [--footage-landscape-social <file>] [--poster <file>] [--cover <file>] [--note <file>] [--composition <file>] [--props <file>] [--render <file>] [--intent <file>] [--metrics <file>] [--scene <file>] | --plate <file> [--plate-background <file>]) [--allow-partial]");
7701
7726
  }
7702
7727
  const dir = options.dir ? path2.resolve(process.cwd(), options.dir) : undefined;
7703
7728
  const fromDir = (name) => {
@@ -7725,14 +7750,17 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
7725
7750
  metrics: resolveFile(options.metrics, "metrics.json"),
7726
7751
  model: options.model,
7727
7752
  note: resolveFile(options.note, "note.txt"),
7753
+ plate: resolveFile(options.plate, "plate.png"),
7754
+ plateBackground: resolveFile(options.plateBackground, "plate.background.png"),
7728
7755
  poster: resolveFile(options.poster, "poster.jpg"),
7729
7756
  props: resolveFile(options.props, "props.json"),
7730
7757
  reasoning: options.reasoning,
7731
7758
  render: resolveFile(options.render, "render.json"),
7732
7759
  scene: resolveFile(options.scene, "scene.json")
7733
7760
  };
7734
- if (!files.footage && !options.allowPartial) {
7735
- throw new Error("A footage cut is required (--footage <file>, or --dir containing footage.mp4). Pass --allow-partial for a deliberate partial refresh (e.g. poster-only).");
7761
+ const { isPlatesOnlyUpload: isPlatesOnlyUpload2 } = await Promise.resolve().then(() => (init_track(), exports_track));
7762
+ if (!files.footage && !options.allowPartial && !isPlatesOnlyUpload2(files)) {
7763
+ throw new Error("A footage cut is required (--footage <file>, or --dir containing footage.mp4). Pass --allow-partial for a deliberate partial refresh (e.g. poster-only), or upload plates alone (--plate/--plate-background) for the plate-lane pre-upload.");
7736
7764
  }
7737
7765
  const onProgress = options.json ? undefined : (message) => console.log(message);
7738
7766
  const result = await trackVideoCommand2(idOrLogId, files, onProgress, {
@@ -8531,6 +8559,8 @@ var stringOptions = new Set([
8531
8559
  "--metrics",
8532
8560
  "--mime",
8533
8561
  "--note",
8562
+ "--plate",
8563
+ "--plate-background",
8534
8564
  "--platform",
8535
8565
  "--poster",
8536
8566
  "--props",
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.89.0"
34
+ "version": "0.90.0"
35
35
  }