fluncle 0.89.0 → 0.91.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.
- package/bin/fluncle.mjs +78 -12
- package/package.json +1 -1
package/bin/fluncle.mjs
CHANGED
|
@@ -497,14 +497,32 @@ var init_util = __esm(() => {
|
|
|
497
497
|
});
|
|
498
498
|
|
|
499
499
|
// src/output.ts
|
|
500
|
+
import { writeSync as writeSync2 } from "node:fs";
|
|
501
|
+
function writeStdoutSync2(text) {
|
|
502
|
+
const buf = Buffer.from(text, "utf8");
|
|
503
|
+
let offset = 0;
|
|
504
|
+
while (offset < buf.length) {
|
|
505
|
+
try {
|
|
506
|
+
offset += writeSync2(1, buf, offset);
|
|
507
|
+
} catch (error) {
|
|
508
|
+
if (error.code === "EAGAIN") {
|
|
509
|
+
Atomics.wait(EAGAIN_WAIT2, 0, 0, 1);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
throw error;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
500
516
|
function isJsonFailure(value) {
|
|
501
517
|
return typeof value === "object" && value !== null && typeof value.code === "string" && typeof value.message === "string";
|
|
502
518
|
}
|
|
503
519
|
function printJson2(value) {
|
|
504
|
-
|
|
520
|
+
writeStdoutSync2(JSON.stringify(value, null, 2) + `
|
|
521
|
+
`);
|
|
505
522
|
}
|
|
506
|
-
var CliError2;
|
|
523
|
+
var EAGAIN_WAIT2, CliError2;
|
|
507
524
|
var init_output = __esm(() => {
|
|
525
|
+
EAGAIN_WAIT2 = new Int32Array(new SharedArrayBuffer(4));
|
|
508
526
|
CliError2 = class CliError2 extends Error {
|
|
509
527
|
code;
|
|
510
528
|
constructor(code, message) {
|
|
@@ -538,7 +556,7 @@ function parseVersion(version) {
|
|
|
538
556
|
var currentVersion;
|
|
539
557
|
var init_version = __esm(() => {
|
|
540
558
|
init_output();
|
|
541
|
-
currentVersion = "0.
|
|
559
|
+
currentVersion = "0.91.0".trim() ? "0.91.0".trim() : "0.1.0";
|
|
542
560
|
});
|
|
543
561
|
|
|
544
562
|
// src/update-notifier.ts
|
|
@@ -2168,7 +2186,7 @@ function parseVersion2(version) {
|
|
|
2168
2186
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2169
2187
|
var init_version2 = __esm(() => {
|
|
2170
2188
|
init_output();
|
|
2171
|
-
currentVersion2 = "0.
|
|
2189
|
+
currentVersion2 = "0.91.0".trim() ? "0.91.0".trim() : "0.1.0";
|
|
2172
2190
|
});
|
|
2173
2191
|
|
|
2174
2192
|
// ../../packages/registry/src/index.ts
|
|
@@ -2895,17 +2913,30 @@ __export(exports_track, {
|
|
|
2895
2913
|
trackGetCommand: () => trackGetCommand,
|
|
2896
2914
|
trackDraftCommand: () => trackDraftCommand,
|
|
2897
2915
|
trackContextCommand: () => trackContextCommand,
|
|
2916
|
+
isPlatesOnlyUpload: () => isPlatesOnlyUpload,
|
|
2898
2917
|
checkBundleCompleteness: () => checkBundleCompleteness
|
|
2899
2918
|
});
|
|
2900
2919
|
async function trackGetCommand(idOrLogId) {
|
|
2901
2920
|
return publicApiGet(`/api/tracks/${encodeURIComponent(idOrLogId)}`);
|
|
2902
2921
|
}
|
|
2922
|
+
function isPlatesOnlyUpload(files) {
|
|
2923
|
+
const hasPlate = PLATE_FIELDS.some((option) => Boolean(files[option]));
|
|
2924
|
+
if (!hasPlate) {
|
|
2925
|
+
return false;
|
|
2926
|
+
}
|
|
2927
|
+
return Object.keys(files).every((option) => !files[option] || PLATE_FIELDS.includes(option) || NON_FILE_OPTIONS.includes(option));
|
|
2928
|
+
}
|
|
2903
2929
|
function checkBundleCompleteness(files) {
|
|
2904
2930
|
const uploadingFootage = FOOTAGE_FIELDS.some((option) => Boolean(files[option]));
|
|
2905
2931
|
const missingFrom = (specs) => uploadingFootage ? specs.filter((spec) => !files[spec.option]).map((spec) => spec.file) : [];
|
|
2932
|
+
const plateWarnings = [];
|
|
2933
|
+
if (files.plateBackground && !files.plate) {
|
|
2934
|
+
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");
|
|
2935
|
+
}
|
|
2906
2936
|
return {
|
|
2907
2937
|
missingAdvisory: missingFrom(RERENDER_ADVISORY_FIELDS),
|
|
2908
2938
|
missingContract: missingFrom(RERENDER_CONTRACT_FIELDS),
|
|
2939
|
+
plateWarnings,
|
|
2909
2940
|
uploadingFootage
|
|
2910
2941
|
};
|
|
2911
2942
|
}
|
|
@@ -2922,6 +2953,9 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
|
|
|
2922
2953
|
onProgress?.(`warning: ${missing} missing (provenance/eval only) — shipping without it`);
|
|
2923
2954
|
}
|
|
2924
2955
|
}
|
|
2956
|
+
for (const warning of completeness.plateWarnings) {
|
|
2957
|
+
onProgress?.(`warning: ${warning}`);
|
|
2958
|
+
}
|
|
2925
2959
|
const present = VIDEO_FIELDS.map((spec) => ({
|
|
2926
2960
|
field: spec.field,
|
|
2927
2961
|
path: files[spec.option]
|
|
@@ -2929,6 +2963,7 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
|
|
|
2929
2963
|
if (present.length === 0) {
|
|
2930
2964
|
throw new CliError2("nothing_to_upload", "No bundle files resolved to upload (pass --dir <bundle> or explicit file flags).");
|
|
2931
2965
|
}
|
|
2966
|
+
const platesOnly = isPlatesOnlyUpload(files);
|
|
2932
2967
|
const presign = await adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/video/uploads`, { fields: present.map((spec) => spec.field) });
|
|
2933
2968
|
const byField = new Map(presign.uploads.map((upload) => [upload.field, upload]));
|
|
2934
2969
|
const urls = {};
|
|
@@ -2949,6 +2984,10 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
|
|
|
2949
2984
|
}
|
|
2950
2985
|
urls[spec.field] = `${FOUND_BASE}/${upload.key}`;
|
|
2951
2986
|
}
|
|
2987
|
+
if (platesOnly) {
|
|
2988
|
+
onProgress?.(`plate pre-upload complete — compose against ${FOUND_BASE}/${presign.logId}/plate.png; finalize is deferred to the footage ship`);
|
|
2989
|
+
return { logId: presign.logId, ok: true, trackId: presign.trackId, urls };
|
|
2990
|
+
}
|
|
2952
2991
|
const manifest = files.render ? await readManifestFields(files.render) : {};
|
|
2953
2992
|
const videoModel = files.model?.trim().slice(0, 120) || manifest.model || DEFAULT_VIDEO_MODEL;
|
|
2954
2993
|
const videoModelReasoning = files.reasoning?.trim().slice(0, 120) || manifest.reasoning || DEFAULT_VIDEO_REASONING;
|
|
@@ -3058,7 +3097,7 @@ async function trackNoteCommand(idOrLogId, options) {
|
|
|
3058
3097
|
const body = { note: options.note };
|
|
3059
3098
|
return adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/note`, body);
|
|
3060
3099
|
}
|
|
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;
|
|
3100
|
+
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
3101
|
var init_track = __esm(() => {
|
|
3063
3102
|
init_api();
|
|
3064
3103
|
init_output();
|
|
@@ -3070,6 +3109,8 @@ var init_track = __esm(() => {
|
|
|
3070
3109
|
{ field: "footage-landscape-social", option: "footageLandscapeSocial" },
|
|
3071
3110
|
{ field: "poster", option: "poster" },
|
|
3072
3111
|
{ field: "cover", option: "cover" },
|
|
3112
|
+
{ field: "plate", option: "plate" },
|
|
3113
|
+
{ field: "plate-background", option: "plateBackground" },
|
|
3073
3114
|
{ field: "note", option: "note" },
|
|
3074
3115
|
{ field: "composition", option: "composition" },
|
|
3075
3116
|
{ field: "props", option: "props" },
|
|
@@ -3095,6 +3136,8 @@ var init_track = __esm(() => {
|
|
|
3095
3136
|
"footageLandscape",
|
|
3096
3137
|
"footageLandscapeSocial"
|
|
3097
3138
|
];
|
|
3139
|
+
PLATE_FIELDS = ["plate", "plateBackground"];
|
|
3140
|
+
NON_FILE_OPTIONS = ["model", "reasoning"];
|
|
3098
3141
|
});
|
|
3099
3142
|
|
|
3100
3143
|
// src/commands/add.ts
|
|
@@ -7081,6 +7124,23 @@ var spotifyPlaylistUrl = "https://open.spotify.com/playlist/1m5LADqpLjiBERdtqrIi
|
|
|
7081
7124
|
var telegramUrl = "https://t.me/fluncle";
|
|
7082
7125
|
|
|
7083
7126
|
// src/output.ts
|
|
7127
|
+
import { writeSync } from "node:fs";
|
|
7128
|
+
var EAGAIN_WAIT = new Int32Array(new SharedArrayBuffer(4));
|
|
7129
|
+
function writeStdoutSync(text) {
|
|
7130
|
+
const buf = Buffer.from(text, "utf8");
|
|
7131
|
+
let offset = 0;
|
|
7132
|
+
while (offset < buf.length) {
|
|
7133
|
+
try {
|
|
7134
|
+
offset += writeSync(1, buf, offset);
|
|
7135
|
+
} catch (error) {
|
|
7136
|
+
if (error.code === "EAGAIN") {
|
|
7137
|
+
Atomics.wait(EAGAIN_WAIT, 0, 0, 1);
|
|
7138
|
+
continue;
|
|
7139
|
+
}
|
|
7140
|
+
throw error;
|
|
7141
|
+
}
|
|
7142
|
+
}
|
|
7143
|
+
}
|
|
7084
7144
|
class CliError extends Error {
|
|
7085
7145
|
code;
|
|
7086
7146
|
constructor(code, message) {
|
|
@@ -7090,7 +7150,8 @@ class CliError extends Error {
|
|
|
7090
7150
|
}
|
|
7091
7151
|
}
|
|
7092
7152
|
function printJson(value) {
|
|
7093
|
-
|
|
7153
|
+
writeStdoutSync(JSON.stringify(value, null, 2) + `
|
|
7154
|
+
`);
|
|
7094
7155
|
}
|
|
7095
7156
|
function toJsonFailure(error) {
|
|
7096
7157
|
if (error instanceof CliError) {
|
|
@@ -7250,7 +7311,7 @@ function addAdminCommands(program2) {
|
|
|
7250
7311
|
const { queueCommand: queueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
7251
7312
|
await runAdminQueue(options, queueCommand2);
|
|
7252
7313
|
});
|
|
7253
|
-
adminTracks.command("list").description("List findings, filterable by musical-key presence (--no-key / --has-key)").option("--limit <limit>", "Number of findings to show", "50").option("--no-key", "Only findings with NO stored musical key (the key-backfill backlog)").option("--has-key <bool>", "Filter by key presence: true (has key) or false (missing)").option("--order <order>", "Sort: asc (oldest first) or desc (newest first)", "desc").option("--json", "Print JSON", false).action(async (options) => {
|
|
7314
|
+
adminTracks.command("list").description("List findings, filterable by musical-key presence (--no-key / --has-key)").option("--limit <limit>", "Number of findings to show (1-100)", "50").option("--all", "Fetch the ENTIRE catalogue, paginating past the 100-row cap (overrides --limit)", false).option("--no-key", "Only findings with NO stored musical key (the key-backfill backlog)").option("--has-key <bool>", "Filter by key presence: true (has key) or false (missing)").option("--order <order>", "Sort: asc (oldest first) or desc (newest first)", "desc").option("--json", "Print JSON", false).action(async (options) => {
|
|
7254
7315
|
const { listCommand: listCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
7255
7316
|
await runAdminTracksList(options, listCommand2);
|
|
7256
7317
|
});
|
|
@@ -7272,7 +7333,7 @@ function addAdminCommands(program2) {
|
|
|
7272
7333
|
const { trackUpdateCommand: trackUpdateCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
7273
7334
|
await runTrackUpdate(trackId, options, trackUpdateCommand2);
|
|
7274
7335
|
});
|
|
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) => {
|
|
7336
|
+
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
7337
|
const { trackVideoCommand: trackVideoCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
7277
7338
|
await runTrackVideo(idOrLogId, options, trackVideoCommand2);
|
|
7278
7339
|
});
|
|
@@ -7697,7 +7758,7 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
|
|
|
7697
7758
|
}
|
|
7698
7759
|
async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
|
|
7699
7760
|
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]");
|
|
7761
|
+
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
7762
|
}
|
|
7702
7763
|
const dir = options.dir ? path2.resolve(process.cwd(), options.dir) : undefined;
|
|
7703
7764
|
const fromDir = (name) => {
|
|
@@ -7725,14 +7786,17 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
|
|
|
7725
7786
|
metrics: resolveFile(options.metrics, "metrics.json"),
|
|
7726
7787
|
model: options.model,
|
|
7727
7788
|
note: resolveFile(options.note, "note.txt"),
|
|
7789
|
+
plate: resolveFile(options.plate, "plate.png"),
|
|
7790
|
+
plateBackground: resolveFile(options.plateBackground, "plate.background.png"),
|
|
7728
7791
|
poster: resolveFile(options.poster, "poster.jpg"),
|
|
7729
7792
|
props: resolveFile(options.props, "props.json"),
|
|
7730
7793
|
reasoning: options.reasoning,
|
|
7731
7794
|
render: resolveFile(options.render, "render.json"),
|
|
7732
7795
|
scene: resolveFile(options.scene, "scene.json")
|
|
7733
7796
|
};
|
|
7734
|
-
|
|
7735
|
-
|
|
7797
|
+
const { isPlatesOnlyUpload: isPlatesOnlyUpload2 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
7798
|
+
if (!files.footage && !options.allowPartial && !isPlatesOnlyUpload2(files)) {
|
|
7799
|
+
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
7800
|
}
|
|
7737
7801
|
const onProgress = options.json ? undefined : (message) => console.log(message);
|
|
7738
7802
|
const result = await trackVideoCommand2(idOrLogId, files, onProgress, {
|
|
@@ -8164,7 +8228,7 @@ function resolveHasKey(options) {
|
|
|
8164
8228
|
return;
|
|
8165
8229
|
}
|
|
8166
8230
|
async function runAdminTracksList(options, listCommand2) {
|
|
8167
|
-
const limit = parseListLimit(options.limit);
|
|
8231
|
+
const limit = options.all ? Number.POSITIVE_INFINITY : parseListLimit(options.limit);
|
|
8168
8232
|
const order = options.order === "asc" ? "asc" : "desc";
|
|
8169
8233
|
const hasKey = resolveHasKey(options);
|
|
8170
8234
|
const tracks = await listCommand2({ hasKey, limit, order });
|
|
@@ -8531,6 +8595,8 @@ var stringOptions = new Set([
|
|
|
8531
8595
|
"--metrics",
|
|
8532
8596
|
"--mime",
|
|
8533
8597
|
"--note",
|
|
8598
|
+
"--plate",
|
|
8599
|
+
"--plate-background",
|
|
8534
8600
|
"--platform",
|
|
8535
8601
|
"--poster",
|
|
8536
8602
|
"--props",
|
package/package.json
CHANGED