fluncle 0.88.0 → 0.89.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 +59 -12
  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.88.0".trim() ? "0.88.0".trim() : "0.1.0";
541
+ currentVersion = "0.89.0".trim() ? "0.89.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.88.0".trim() ? "0.88.0".trim() : "0.1.0";
2171
+ currentVersion2 = "0.89.0".trim() ? "0.89.0".trim() : "0.1.0";
2172
2172
  });
2173
2173
 
2174
2174
  // ../../packages/registry/src/index.ts
@@ -2894,16 +2894,41 @@ __export(exports_track, {
2894
2894
  trackNoteCommand: () => trackNoteCommand,
2895
2895
  trackGetCommand: () => trackGetCommand,
2896
2896
  trackDraftCommand: () => trackDraftCommand,
2897
- trackContextCommand: () => trackContextCommand
2897
+ trackContextCommand: () => trackContextCommand,
2898
+ checkBundleCompleteness: () => checkBundleCompleteness
2898
2899
  });
2899
2900
  async function trackGetCommand(idOrLogId) {
2900
2901
  return publicApiGet(`/api/tracks/${encodeURIComponent(idOrLogId)}`);
2901
2902
  }
2902
- async function trackVideoCommand(idOrLogId, files, onProgress) {
2903
+ function checkBundleCompleteness(files) {
2904
+ const uploadingFootage = FOOTAGE_FIELDS.some((option) => Boolean(files[option]));
2905
+ const missingFrom = (specs) => uploadingFootage ? specs.filter((spec) => !files[spec.option]).map((spec) => spec.file) : [];
2906
+ return {
2907
+ missingAdvisory: missingFrom(RERENDER_ADVISORY_FIELDS),
2908
+ missingContract: missingFrom(RERENDER_CONTRACT_FIELDS),
2909
+ uploadingFootage
2910
+ };
2911
+ }
2912
+ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
2913
+ const completeness = checkBundleCompleteness(files);
2914
+ if (completeness.uploadingFootage && completeness.missingContract.length > 0 && !options.allowPartial) {
2915
+ throw new CliError2("bundle_incomplete", `Refusing to upload a PARTIAL bundle: footage is being uploaded but the re-render contract is missing ${completeness.missingContract.join(", ")}. ` + `A footage-only upload leaves composition.tsx/props.json/render.json stale on R2 and desyncs the render.json from the DB ledger. ` + `Ship the complete bundle (re-run \`ship\` and upload with --dir), or pass --allow-partial for a deliberate partial refresh (e.g. poster-only).`);
2916
+ }
2917
+ if (completeness.uploadingFootage) {
2918
+ if (completeness.missingContract.length > 0) {
2919
+ onProgress?.(`warning: --allow-partial — uploading WITHOUT the re-render contract (${completeness.missingContract.join(", ")}); the R2 bundle will NOT be re-renderable`);
2920
+ }
2921
+ for (const missing of completeness.missingAdvisory) {
2922
+ onProgress?.(`warning: ${missing} missing (provenance/eval only) — shipping without it`);
2923
+ }
2924
+ }
2903
2925
  const present = VIDEO_FIELDS.map((spec) => ({
2904
2926
  field: spec.field,
2905
2927
  path: files[spec.option]
2906
2928
  })).filter((spec) => Boolean(spec.path));
2929
+ if (present.length === 0) {
2930
+ throw new CliError2("nothing_to_upload", "No bundle files resolved to upload (pass --dir <bundle> or explicit file flags).");
2931
+ }
2907
2932
  const presign = await adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/video/uploads`, { fields: present.map((spec) => spec.field) });
2908
2933
  const byField = new Map(presign.uploads.map((upload) => [upload.field, upload]));
2909
2934
  const urls = {};
@@ -3033,7 +3058,7 @@ async function trackNoteCommand(idOrLogId, options) {
3033
3058
  const body = { note: options.note };
3034
3059
  return adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/note`, body);
3035
3060
  }
3036
- var DEFAULT_VIDEO_MODEL = "anthropic/claude-opus-4-8", DEFAULT_VIDEO_REASONING = "high", FOUND_BASE = "https://found.fluncle.com", VIDEO_FIELDS;
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;
3037
3062
  var init_track = __esm(() => {
3038
3063
  init_api();
3039
3064
  init_output();
@@ -3050,7 +3075,25 @@ var init_track = __esm(() => {
3050
3075
  { field: "props", option: "props" },
3051
3076
  { field: "render", option: "render" },
3052
3077
  { field: "intent", option: "intent" },
3053
- { field: "metrics", option: "metrics" }
3078
+ { field: "metrics", option: "metrics" },
3079
+ { field: "scene", option: "scene" }
3080
+ ];
3081
+ RERENDER_CONTRACT_FIELDS = [
3082
+ { file: "composition.tsx", option: "composition" },
3083
+ { file: "props.json", option: "props" },
3084
+ { file: "render.json", option: "render" }
3085
+ ];
3086
+ RERENDER_ADVISORY_FIELDS = [
3087
+ { file: "intent.json", option: "intent" },
3088
+ { file: "metrics.json", option: "metrics" },
3089
+ { file: "scene.json", option: "scene" }
3090
+ ];
3091
+ FOOTAGE_FIELDS = [
3092
+ "footage",
3093
+ "footageSocial",
3094
+ "footageNotext",
3095
+ "footageLandscape",
3096
+ "footageLandscapeSocial"
3054
3097
  ];
3055
3098
  });
3056
3099
 
@@ -7229,7 +7272,7 @@ function addAdminCommands(program2) {
7229
7272
  const { trackUpdateCommand: trackUpdateCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
7230
7273
  await runTrackUpdate(trackId, options, trackUpdateCommand2);
7231
7274
  });
7232
- adminTrack.command("video").description("Upload a track's video bundle to R2 and link it").argument("[idOrLogId]").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").allowExcessArguments().action(async (idOrLogId, options) => {
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) => {
7233
7276
  const { trackVideoCommand: trackVideoCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
7234
7277
  await runTrackVideo(idOrLogId, options, trackVideoCommand2);
7235
7278
  });
@@ -7654,7 +7697,7 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
7654
7697
  }
7655
7698
  async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
7656
7699
  if (!idOrLogId) {
7657
- 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>])");
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]");
7658
7701
  }
7659
7702
  const dir = options.dir ? path2.resolve(process.cwd(), options.dir) : undefined;
7660
7703
  const fromDir = (name) => {
@@ -7685,13 +7728,16 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
7685
7728
  poster: resolveFile(options.poster, "poster.jpg"),
7686
7729
  props: resolveFile(options.props, "props.json"),
7687
7730
  reasoning: options.reasoning,
7688
- render: resolveFile(options.render, "render.json")
7731
+ render: resolveFile(options.render, "render.json"),
7732
+ scene: resolveFile(options.scene, "scene.json")
7689
7733
  };
7690
- if (!files.footage) {
7691
- throw new Error("A footage cut is required (--footage <file>, or --dir containing footage.mp4)");
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).");
7692
7736
  }
7693
7737
  const onProgress = options.json ? undefined : (message) => console.log(message);
7694
- const result = await trackVideoCommand2(idOrLogId, files, onProgress);
7738
+ const result = await trackVideoCommand2(idOrLogId, files, onProgress, {
7739
+ allowPartial: options.allowPartial
7740
+ });
7695
7741
  if (options.json) {
7696
7742
  printJson(result);
7697
7743
  return;
@@ -8491,6 +8537,7 @@ var stringOptions = new Set([
8491
8537
  "--query",
8492
8538
  "--recorded-at",
8493
8539
  "--render",
8540
+ "--scene",
8494
8541
  "--scheduled-for",
8495
8542
  "--soundcloud-url",
8496
8543
  "--source",
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.88.0"
34
+ "version": "0.89.0"
35
35
  }