fluncle 0.87.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.
- package/bin/fluncle.mjs +89 -188
- 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.
|
|
541
|
+
currentVersion = "0.89.0".trim() ? "0.89.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("
|
|
1366
|
+
throw new CliError2("mixtape_not_promoted", `${mixtapeId} has no coordinate yet — promote 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.
|
|
2171
|
+
currentVersion2 = "0.89.0".trim() ? "0.89.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,
|
|
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",
|
|
@@ -2977,16 +2894,41 @@ __export(exports_track, {
|
|
|
2977
2894
|
trackNoteCommand: () => trackNoteCommand,
|
|
2978
2895
|
trackGetCommand: () => trackGetCommand,
|
|
2979
2896
|
trackDraftCommand: () => trackDraftCommand,
|
|
2980
|
-
trackContextCommand: () => trackContextCommand
|
|
2897
|
+
trackContextCommand: () => trackContextCommand,
|
|
2898
|
+
checkBundleCompleteness: () => checkBundleCompleteness
|
|
2981
2899
|
});
|
|
2982
2900
|
async function trackGetCommand(idOrLogId) {
|
|
2983
2901
|
return publicApiGet(`/api/tracks/${encodeURIComponent(idOrLogId)}`);
|
|
2984
2902
|
}
|
|
2985
|
-
|
|
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
|
+
}
|
|
2986
2925
|
const present = VIDEO_FIELDS.map((spec) => ({
|
|
2987
2926
|
field: spec.field,
|
|
2988
2927
|
path: files[spec.option]
|
|
2989
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
|
+
}
|
|
2990
2932
|
const presign = await adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/video/uploads`, { fields: present.map((spec) => spec.field) });
|
|
2991
2933
|
const byField = new Map(presign.uploads.map((upload) => [upload.field, upload]));
|
|
2992
2934
|
const urls = {};
|
|
@@ -3116,7 +3058,7 @@ async function trackNoteCommand(idOrLogId, options) {
|
|
|
3116
3058
|
const body = { note: options.note };
|
|
3117
3059
|
return adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/note`, body);
|
|
3118
3060
|
}
|
|
3119
|
-
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;
|
|
3120
3062
|
var init_track = __esm(() => {
|
|
3121
3063
|
init_api();
|
|
3122
3064
|
init_output();
|
|
@@ -3133,7 +3075,25 @@ var init_track = __esm(() => {
|
|
|
3133
3075
|
{ field: "props", option: "props" },
|
|
3134
3076
|
{ field: "render", option: "render" },
|
|
3135
3077
|
{ field: "intent", option: "intent" },
|
|
3136
|
-
{ 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"
|
|
3137
3097
|
];
|
|
3138
3098
|
});
|
|
3139
3099
|
|
|
@@ -3668,7 +3628,7 @@ var init_mixtape_youtube2 = __esm(() => {
|
|
|
3668
3628
|
|
|
3669
3629
|
// src/commands/mixtape-set-video.ts
|
|
3670
3630
|
import { randomUUID } from "node:crypto";
|
|
3671
|
-
import { existsSync
|
|
3631
|
+
import { existsSync, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
|
|
3672
3632
|
import { tmpdir } from "node:os";
|
|
3673
3633
|
import { join as join4 } from "node:path";
|
|
3674
3634
|
function planMultipart(contentLength, partSize = DEFAULT_PART_SIZE) {
|
|
@@ -3725,7 +3685,7 @@ function renditionFfmpegArgs(inputPath, outputPath) {
|
|
|
3725
3685
|
];
|
|
3726
3686
|
}
|
|
3727
3687
|
async function uploadRenditionMultipart(masterPath, presignPath, onProgress = () => {}) {
|
|
3728
|
-
if (!
|
|
3688
|
+
if (!existsSync(masterPath)) {
|
|
3729
3689
|
throw new CliError2("file_not_found", `Set-video master not found: ${masterPath}`);
|
|
3730
3690
|
}
|
|
3731
3691
|
await assertFfmpeg();
|
|
@@ -3838,7 +3798,7 @@ __export(exports_recordings, {
|
|
|
3838
3798
|
recordingDeleteCommand: () => recordingDeleteCommand,
|
|
3839
3799
|
recordingCreateCommand: () => recordingCreateCommand
|
|
3840
3800
|
});
|
|
3841
|
-
import { existsSync as
|
|
3801
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
3842
3802
|
async function recordingGet(id) {
|
|
3843
3803
|
const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
|
|
3844
3804
|
return response.recording;
|
|
@@ -3907,7 +3867,7 @@ async function recordingCreateCommand(options = {}) {
|
|
|
3907
3867
|
if (!options.video) {
|
|
3908
3868
|
throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
|
|
3909
3869
|
}
|
|
3910
|
-
if (!
|
|
3870
|
+
if (!existsSync2(options.video)) {
|
|
3911
3871
|
throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
|
|
3912
3872
|
}
|
|
3913
3873
|
const created = await adminApiPost("/api/admin/recordings", {
|
|
@@ -3943,11 +3903,11 @@ async function recordingUpdateCommand(id, options = {}) {
|
|
|
3943
3903
|
body.parentId = options.parentId;
|
|
3944
3904
|
}
|
|
3945
3905
|
if (options.tracklistFile !== undefined) {
|
|
3946
|
-
if (!
|
|
3906
|
+
if (!existsSync2(options.tracklistFile)) {
|
|
3947
3907
|
throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
|
|
3948
3908
|
}
|
|
3949
3909
|
try {
|
|
3950
|
-
body.tracklistJson = JSON.parse(
|
|
3910
|
+
body.tracklistJson = JSON.parse(readFileSync2(options.tracklistFile, "utf8"));
|
|
3951
3911
|
} catch {
|
|
3952
3912
|
throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
|
|
3953
3913
|
}
|
|
@@ -3992,12 +3952,12 @@ async function recordingReplaceCuesCommand(id, options = {}) {
|
|
|
3992
3952
|
if (!options.cuesFile) {
|
|
3993
3953
|
throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
|
|
3994
3954
|
}
|
|
3995
|
-
if (!
|
|
3955
|
+
if (!existsSync2(options.cuesFile)) {
|
|
3996
3956
|
throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
|
|
3997
3957
|
}
|
|
3998
3958
|
let cues;
|
|
3999
3959
|
try {
|
|
4000
|
-
cues = JSON.parse(
|
|
3960
|
+
cues = JSON.parse(readFileSync2(options.cuesFile, "utf8"));
|
|
4001
3961
|
} catch {
|
|
4002
3962
|
throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
|
|
4003
3963
|
}
|
|
@@ -4201,7 +4161,7 @@ __export(exports_recordings2, {
|
|
|
4201
4161
|
recordingDeleteCommand: () => recordingDeleteCommand2,
|
|
4202
4162
|
recordingCreateCommand: () => recordingCreateCommand2
|
|
4203
4163
|
});
|
|
4204
|
-
import { existsSync as
|
|
4164
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
4205
4165
|
async function recordingGet2(id) {
|
|
4206
4166
|
const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
|
|
4207
4167
|
return response.recording;
|
|
@@ -4270,7 +4230,7 @@ async function recordingCreateCommand2(options = {}) {
|
|
|
4270
4230
|
if (!options.video) {
|
|
4271
4231
|
throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
|
|
4272
4232
|
}
|
|
4273
|
-
if (!
|
|
4233
|
+
if (!existsSync3(options.video)) {
|
|
4274
4234
|
throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
|
|
4275
4235
|
}
|
|
4276
4236
|
const created = await adminApiPost("/api/admin/recordings", {
|
|
@@ -4306,11 +4266,11 @@ async function recordingUpdateCommand2(id, options = {}) {
|
|
|
4306
4266
|
body.parentId = options.parentId;
|
|
4307
4267
|
}
|
|
4308
4268
|
if (options.tracklistFile !== undefined) {
|
|
4309
|
-
if (!
|
|
4269
|
+
if (!existsSync3(options.tracklistFile)) {
|
|
4310
4270
|
throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
|
|
4311
4271
|
}
|
|
4312
4272
|
try {
|
|
4313
|
-
body.tracklistJson = JSON.parse(
|
|
4273
|
+
body.tracklistJson = JSON.parse(readFileSync3(options.tracklistFile, "utf8"));
|
|
4314
4274
|
} catch {
|
|
4315
4275
|
throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
|
|
4316
4276
|
}
|
|
@@ -4355,12 +4315,12 @@ async function recordingReplaceCuesCommand2(id, options = {}) {
|
|
|
4355
4315
|
if (!options.cuesFile) {
|
|
4356
4316
|
throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
|
|
4357
4317
|
}
|
|
4358
|
-
if (!
|
|
4318
|
+
if (!existsSync3(options.cuesFile)) {
|
|
4359
4319
|
throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
|
|
4360
4320
|
}
|
|
4361
4321
|
let cues;
|
|
4362
4322
|
try {
|
|
4363
|
-
cues = JSON.parse(
|
|
4323
|
+
cues = JSON.parse(readFileSync3(options.cuesFile, "utf8"));
|
|
4364
4324
|
} catch {
|
|
4365
4325
|
throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
|
|
4366
4326
|
}
|
|
@@ -4386,7 +4346,7 @@ __export(exports_newsletter, {
|
|
|
4386
4346
|
newsletterDraftCommand: () => newsletterDraftCommand,
|
|
4387
4347
|
newsletterDeleteCommand: () => newsletterDeleteCommand
|
|
4388
4348
|
});
|
|
4389
|
-
import { existsSync as
|
|
4349
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
4390
4350
|
function buildBody2(options, { requireContent }) {
|
|
4391
4351
|
const body = {};
|
|
4392
4352
|
if (options.contentFile !== undefined) {
|
|
@@ -4406,10 +4366,10 @@ function buildBody2(options, { requireContent }) {
|
|
|
4406
4366
|
return body;
|
|
4407
4367
|
}
|
|
4408
4368
|
function readContentFile(filePath) {
|
|
4409
|
-
if (!
|
|
4369
|
+
if (!existsSync4(filePath)) {
|
|
4410
4370
|
throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
|
|
4411
4371
|
}
|
|
4412
|
-
const text =
|
|
4372
|
+
const text = readFileSync4(filePath, "utf-8");
|
|
4413
4373
|
try {
|
|
4414
4374
|
return JSON.parse(text);
|
|
4415
4375
|
} catch (error) {
|
|
@@ -5044,7 +5004,7 @@ var init_format2 = __esm(() => {
|
|
|
5044
5004
|
});
|
|
5045
5005
|
|
|
5046
5006
|
// src/cli.ts
|
|
5047
|
-
import { existsSync as
|
|
5007
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
5048
5008
|
import path2 from "path";
|
|
5049
5009
|
|
|
5050
5010
|
// ../../node_modules/commander/lib/error.js
|
|
@@ -7312,7 +7272,7 @@ function addAdminCommands(program2) {
|
|
|
7312
7272
|
const { trackUpdateCommand: trackUpdateCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
7313
7273
|
await runTrackUpdate(trackId, options, trackUpdateCommand2);
|
|
7314
7274
|
});
|
|
7315
|
-
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) => {
|
|
7316
7276
|
const { trackVideoCommand: trackVideoCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
7317
7277
|
await runTrackVideo(idOrLogId, options, trackVideoCommand2);
|
|
7318
7278
|
});
|
|
@@ -7372,27 +7332,11 @@ function addAdminCommands(program2) {
|
|
|
7372
7332
|
adminMixtapes.action(() => {
|
|
7373
7333
|
adminMixtapes.outputHelp();
|
|
7374
7334
|
});
|
|
7375
|
-
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) => {
|
|
7376
|
-
const { mixtapeCreateCommand: mixtapeCreateCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7377
|
-
await runMixtapeCreate(options, mixtapeCreateCommand2);
|
|
7378
|
-
});
|
|
7379
7335
|
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) => {
|
|
7380
7336
|
const { mixtapeUpdateCommand: mixtapeUpdateCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7381
7337
|
await runMixtapeUpdate(id, options, mixtapeUpdateCommand2);
|
|
7382
7338
|
});
|
|
7383
|
-
adminMixtapes.command("
|
|
7384
|
-
const { mixtapeMembersCommand: mixtapeMembersCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7385
|
-
await runMixtapeMembers(id, refs, options, mixtapeMembersCommand2);
|
|
7386
|
-
});
|
|
7387
|
-
adminMixtapes.command("publish").description("Publish a mixtape draft").argument("[id]").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
|
|
7388
|
-
const { mixtapePublishCommand: mixtapePublishCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7389
|
-
await runMixtapePublish(id, options, mixtapePublishCommand2);
|
|
7390
|
-
});
|
|
7391
|
-
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) => {
|
|
7392
|
-
const { mixtapeDeleteCommand: mixtapeDeleteCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7393
|
-
await runMixtapeDelete(id, options, mixtapeDeleteCommand2);
|
|
7394
|
-
});
|
|
7395
|
-
adminMixtapes.command("list").description("List all mixtapes (including drafts)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
7339
|
+
adminMixtapes.command("list").description("List all mixtapes (including distributing)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
7396
7340
|
const { mixtapeListCommand: mixtapeListCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7397
7341
|
await runMixtapeList(options, mixtapeListCommand2);
|
|
7398
7342
|
});
|
|
@@ -7559,7 +7503,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
|
|
|
7559
7503
|
console.log(` mime: ${result.mime}`);
|
|
7560
7504
|
}
|
|
7561
7505
|
async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
|
|
7562
|
-
const script = options.scriptFile ?
|
|
7506
|
+
const script = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
|
|
7563
7507
|
if (!idOrLogId || !script || !script.trim()) {
|
|
7564
7508
|
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]");
|
|
7565
7509
|
}
|
|
@@ -7615,7 +7559,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
|
|
|
7615
7559
|
}
|
|
7616
7560
|
}
|
|
7617
7561
|
async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
|
|
7618
|
-
const note = options.scriptFile ?
|
|
7562
|
+
const note = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
|
|
7619
7563
|
if (!idOrLogId || !note || !note.trim()) {
|
|
7620
7564
|
throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--json]");
|
|
7621
7565
|
}
|
|
@@ -7753,7 +7697,7 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
|
|
|
7753
7697
|
}
|
|
7754
7698
|
async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
|
|
7755
7699
|
if (!idOrLogId) {
|
|
7756
|
-
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]");
|
|
7757
7701
|
}
|
|
7758
7702
|
const dir = options.dir ? path2.resolve(process.cwd(), options.dir) : undefined;
|
|
7759
7703
|
const fromDir = (name) => {
|
|
@@ -7761,7 +7705,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
|
|
|
7761
7705
|
return;
|
|
7762
7706
|
}
|
|
7763
7707
|
const candidate = path2.join(dir, name);
|
|
7764
|
-
return
|
|
7708
|
+
return existsSync5(candidate) ? candidate : undefined;
|
|
7765
7709
|
};
|
|
7766
7710
|
const resolveFile = (explicit, name) => {
|
|
7767
7711
|
if (explicit) {
|
|
@@ -7784,13 +7728,16 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
|
|
|
7784
7728
|
poster: resolveFile(options.poster, "poster.jpg"),
|
|
7785
7729
|
props: resolveFile(options.props, "props.json"),
|
|
7786
7730
|
reasoning: options.reasoning,
|
|
7787
|
-
render: resolveFile(options.render, "render.json")
|
|
7731
|
+
render: resolveFile(options.render, "render.json"),
|
|
7732
|
+
scene: resolveFile(options.scene, "scene.json")
|
|
7788
7733
|
};
|
|
7789
|
-
if (!files.footage) {
|
|
7790
|
-
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).");
|
|
7791
7736
|
}
|
|
7792
7737
|
const onProgress = options.json ? undefined : (message) => console.log(message);
|
|
7793
|
-
const result = await trackVideoCommand2(idOrLogId, files, onProgress
|
|
7738
|
+
const result = await trackVideoCommand2(idOrLogId, files, onProgress, {
|
|
7739
|
+
allowPartial: options.allowPartial
|
|
7740
|
+
});
|
|
7794
7741
|
if (options.json) {
|
|
7795
7742
|
printJson(result);
|
|
7796
7743
|
return;
|
|
@@ -7938,14 +7885,6 @@ async function runTrackPurgeVideo(idOrLogId, options, trackPurgeVideoCommand2) {
|
|
|
7938
7885
|
}
|
|
7939
7886
|
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.`);
|
|
7940
7887
|
}
|
|
7941
|
-
async function runMixtapeCreate(options, mixtapeCreateCommand2) {
|
|
7942
|
-
const result = await mixtapeCreateCommand2(options);
|
|
7943
|
-
if (options.json) {
|
|
7944
|
-
printJson(result);
|
|
7945
|
-
return;
|
|
7946
|
-
}
|
|
7947
|
-
console.log(`Logged draft ${result.mixtape.id}. It stays a draft until you publish it.`);
|
|
7948
|
-
}
|
|
7949
7888
|
async function runMixtapeUpdate(id, options, mixtapeUpdateCommand2) {
|
|
7950
7889
|
if (!id) {
|
|
7951
7890
|
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes update <id>");
|
|
@@ -7957,31 +7896,6 @@ async function runMixtapeUpdate(id, options, mixtapeUpdateCommand2) {
|
|
|
7957
7896
|
}
|
|
7958
7897
|
console.log(`Saved ${result.mixtape.id}.`);
|
|
7959
7898
|
}
|
|
7960
|
-
async function runMixtapeMembers(id, refs, options, mixtapeMembersCommand2) {
|
|
7961
|
-
if (!id) {
|
|
7962
|
-
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes members <id> [refs...]");
|
|
7963
|
-
}
|
|
7964
|
-
if (refs.length === 0 && !options.from) {
|
|
7965
|
-
throw new Error("Provide refs as arguments or a cue-sheet file with --from");
|
|
7966
|
-
}
|
|
7967
|
-
const result = await mixtapeMembersCommand2(id, refs, options);
|
|
7968
|
-
if (options.json) {
|
|
7969
|
-
printJson(result);
|
|
7970
|
-
return;
|
|
7971
|
-
}
|
|
7972
|
-
console.log(`Saved the tracklist: ${result.mixtape.memberCount} bangers on ${result.mixtape.id}.`);
|
|
7973
|
-
}
|
|
7974
|
-
async function runMixtapePublish(id, options, mixtapePublishCommand2) {
|
|
7975
|
-
if (!id) {
|
|
7976
|
-
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes publish <id>");
|
|
7977
|
-
}
|
|
7978
|
-
const result = await mixtapePublishCommand2(id);
|
|
7979
|
-
if (options.json) {
|
|
7980
|
-
printJson(result);
|
|
7981
|
-
return;
|
|
7982
|
-
}
|
|
7983
|
-
console.log(`Minted ${result.mixtape.logId} (fluncle://${result.mixtape.logId}) \u2014 distributing. ` + "Run `distribute` to push it to the platforms.");
|
|
7984
|
-
}
|
|
7985
7899
|
async function runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand2) {
|
|
7986
7900
|
if (!idOrLogId) {
|
|
7987
7901
|
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes distribute <idOrLogId> --video <mp4> --audio <file>");
|
|
@@ -8023,20 +7937,6 @@ async function runMixtapeResync(idOrLogId, options, mixtapeResyncCommand2) {
|
|
|
8023
7937
|
console.log(`Re-synced ${result.logId} (fluncle://${result.logId}).
|
|
8024
7938
|
${links}`);
|
|
8025
7939
|
}
|
|
8026
|
-
async function runMixtapeDelete(id, options, mixtapeDeleteCommand2) {
|
|
8027
|
-
if (!id) {
|
|
8028
|
-
throw new Error("Missing mixtape id. Usage: fluncle admin mixtapes delete <id> --yes");
|
|
8029
|
-
}
|
|
8030
|
-
if (!options.yes) {
|
|
8031
|
-
throw new Error("Pass --yes to confirm the discard. Published mixtapes can't be deleted.");
|
|
8032
|
-
}
|
|
8033
|
-
await mixtapeDeleteCommand2(id);
|
|
8034
|
-
if (options.json) {
|
|
8035
|
-
printJson({ id, ok: true });
|
|
8036
|
-
return;
|
|
8037
|
-
}
|
|
8038
|
-
console.log(`Discarded draft ${id}.`);
|
|
8039
|
-
}
|
|
8040
7940
|
async function runMixtapeList(options, mixtapeListCommand2) {
|
|
8041
7941
|
const mixtapes = await mixtapeListCommand2();
|
|
8042
7942
|
if (options.json) {
|
|
@@ -8048,8 +7948,8 @@ async function runMixtapeList(options, mixtapeListCommand2) {
|
|
|
8048
7948
|
return;
|
|
8049
7949
|
}
|
|
8050
7950
|
for (const mixtape of mixtapes) {
|
|
8051
|
-
const status = mixtape.status ?? "
|
|
8052
|
-
const coordinate3 = mixtape.logId ?? "
|
|
7951
|
+
const status = mixtape.status ?? "distributing";
|
|
7952
|
+
const coordinate3 = mixtape.logId ?? "unminted";
|
|
8053
7953
|
console.log(`${coordinate3} ${status} ${mixtape.memberCount} bangers ${mixtape.title}`);
|
|
8054
7954
|
}
|
|
8055
7955
|
}
|
|
@@ -8092,8 +7992,8 @@ async function runMixtapeGet(idOrLogId, options, mixtapeGetCommand2) {
|
|
|
8092
7992
|
printJson(mixtape);
|
|
8093
7993
|
return;
|
|
8094
7994
|
}
|
|
8095
|
-
const coordinate3 = mixtape.logId ?? "
|
|
8096
|
-
const status = mixtape.status ?? "
|
|
7995
|
+
const coordinate3 = mixtape.logId ?? "unminted";
|
|
7996
|
+
const status = mixtape.status ?? "distributing";
|
|
8097
7997
|
console.log(`${mixtape.title}`);
|
|
8098
7998
|
console.log(` ${coordinate3} \xB7 ${status} \xB7 ${mixtape.memberCount} bangers`);
|
|
8099
7999
|
if (mixtape.note) {
|
|
@@ -8637,6 +8537,7 @@ var stringOptions = new Set([
|
|
|
8637
8537
|
"--query",
|
|
8638
8538
|
"--recorded-at",
|
|
8639
8539
|
"--render",
|
|
8540
|
+
"--scene",
|
|
8640
8541
|
"--scheduled-for",
|
|
8641
8542
|
"--soundcloud-url",
|
|
8642
8543
|
"--source",
|
package/package.json
CHANGED