fluncle 0.95.0 → 0.97.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 +88 -12
- package/package.json +1 -1
package/bin/fluncle.mjs
CHANGED
|
@@ -489,11 +489,12 @@ function baseTitleMatches(findingTitle, candidateTitle) {
|
|
|
489
489
|
}
|
|
490
490
|
return true;
|
|
491
491
|
}
|
|
492
|
-
var VERSION_MARKER, REMIX_MARKER, VERSION_STOPWORDS;
|
|
492
|
+
var VERSION_MARKER, REMIX_MARKER, VERSION_STOPWORDS, TIKTOK_DRAFT_STALE_MS;
|
|
493
493
|
var init_util = __esm(() => {
|
|
494
494
|
VERSION_MARKER = /\b(mix|edit|version|remix|dub|vip|bootleg|rework|re-?edit|flip|refix|remaster(?:ed)?|instrumental)\b/i;
|
|
495
495
|
REMIX_MARKER = /\b(remix|bootleg|vip|rework|re-?edit|flip|refix)\b/i;
|
|
496
496
|
VERSION_STOPWORDS = new Set(["mix", "the", "and", "feat", "ft", "edit", "version", "remix"]);
|
|
497
|
+
TIKTOK_DRAFT_STALE_MS = 24 * 60 * 60 * 1000;
|
|
497
498
|
});
|
|
498
499
|
|
|
499
500
|
// src/output.ts
|
|
@@ -556,7 +557,7 @@ function parseVersion(version) {
|
|
|
556
557
|
var currentVersion;
|
|
557
558
|
var init_version = __esm(() => {
|
|
558
559
|
init_output();
|
|
559
|
-
currentVersion = "0.
|
|
560
|
+
currentVersion = "0.97.0".trim() ? "0.97.0".trim() : "0.1.0";
|
|
560
561
|
});
|
|
561
562
|
|
|
562
563
|
// src/update-notifier.ts
|
|
@@ -2186,7 +2187,7 @@ function parseVersion2(version) {
|
|
|
2186
2187
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2187
2188
|
var init_version2 = __esm(() => {
|
|
2188
2189
|
init_output();
|
|
2189
|
-
currentVersion2 = "0.
|
|
2190
|
+
currentVersion2 = "0.97.0".trim() ? "0.97.0".trim() : "0.1.0";
|
|
2190
2191
|
});
|
|
2191
2192
|
|
|
2192
2193
|
// ../../packages/registry/src/index.ts
|
|
@@ -2789,6 +2790,17 @@ var init_src = __esm(() => {
|
|
|
2789
2790
|
probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-social-capture", kind: "cron" },
|
|
2790
2791
|
weights: { status: "hidden" }
|
|
2791
2792
|
},
|
|
2793
|
+
{
|
|
2794
|
+
command: "fluncle admin clips drip-pause",
|
|
2795
|
+
exposedContent: [
|
|
2796
|
+
"post the due, cut clips to Instagram on a jittered ~daily cadence via Postiz (--no-agent, Worker HTTP)"
|
|
2797
|
+
],
|
|
2798
|
+
kind: "cron",
|
|
2799
|
+
name: "cron.clip-drip",
|
|
2800
|
+
operatorNotes: "every 20m. Pure HTTP trigger, zero LLM tokens. Admin tier (needs the Worker's Postiz key, which the box never sees — the box only triggers; the `finalize_clip_cut` / `record_health` precedent). The Worker checks the global kill switch FIRST (paused → no-op), then posts due clips bounded by a per-tick cap AND a rolling-24h IG cap. Every clip auto-enters the schedule at a jittered ~23-25h after the queue tail. The operator pauses/resumes with `fluncle admin clips drip-pause` / `drip-resume`. Source: docs/agents/hermes/scripts/clip-drip-sweep.sh. Probed on /status as cron.clip-drip.",
|
|
2801
|
+
probeConfig: { cadenceMs: 20 * MINUTE_MS, cronName: "fluncle-clip-drip", kind: "cron" },
|
|
2802
|
+
weights: { status: "hidden" }
|
|
2803
|
+
},
|
|
2792
2804
|
{
|
|
2793
2805
|
command: "fluncle admin tracks queue",
|
|
2794
2806
|
exposedContent: [
|
|
@@ -2824,6 +2836,16 @@ var init_src = __esm(() => {
|
|
|
2824
2836
|
kind: "cron"
|
|
2825
2837
|
},
|
|
2826
2838
|
weights: { status: "secondary" }
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
exposedContent: [
|
|
2842
|
+
"daily gzip dump of the prod database → a PRIVATE R2 bucket (owned off-site backup) + 30 daily / 12 monthly retention (--no-agent)"
|
|
2843
|
+
],
|
|
2844
|
+
kind: "cron",
|
|
2845
|
+
name: "cron.backup",
|
|
2846
|
+
operatorNotes: "daily. An OWNED, off-Cloudflare backup: dumps prod Turso over the libSQL HTTP pipeline → gzip → a PRIVATE R2 bucket (never fluncle-videos, which is world-served at found.fluncle.com) + prune. Zero LLM tokens; talks to Turso + R2 directly (no fluncle CLI, no agent token). Turso's managed PITR is the belt; this is the braces. Restore is proven by apps/web/scripts/restore-drill.ts. Source: docs/agents/hermes/scripts/backup-sweep.*",
|
|
2847
|
+
probeConfig: { cadenceMs: 24 * 60 * MINUTE_MS, cronName: "fluncle-backup", kind: "cron" },
|
|
2848
|
+
weights: { status: "secondary" }
|
|
2827
2849
|
}
|
|
2828
2850
|
];
|
|
2829
2851
|
});
|
|
@@ -4284,7 +4306,10 @@ var exports_clips = {};
|
|
|
4284
4306
|
__export(exports_clips, {
|
|
4285
4307
|
setVideoUrl: () => setVideoUrl,
|
|
4286
4308
|
clipsListCommand: () => clipsListCommand,
|
|
4309
|
+
clipScheduleCommand: () => clipScheduleCommand,
|
|
4310
|
+
clipPostsListCommand: () => clipPostsListCommand,
|
|
4287
4311
|
clipFootageKey: () => clipFootageKey,
|
|
4312
|
+
clipDripPauseCommand: () => clipDripPauseCommand,
|
|
4288
4313
|
clipCutFilterComplex: () => clipCutFilterComplex,
|
|
4289
4314
|
clipCutFfmpegArgs: () => clipCutFfmpegArgs,
|
|
4290
4315
|
clipCutCommand: () => clipCutCommand,
|
|
@@ -4361,6 +4386,20 @@ async function clipsListCommand(filter = {}) {
|
|
|
4361
4386
|
const response = await adminApiGet(`/api/admin/clips${query ? `?${query}` : ""}`);
|
|
4362
4387
|
return response.clips;
|
|
4363
4388
|
}
|
|
4389
|
+
async function clipPostsListCommand() {
|
|
4390
|
+
const response = await adminApiGet("/api/admin/clips/social");
|
|
4391
|
+
return response.posts;
|
|
4392
|
+
}
|
|
4393
|
+
async function clipScheduleCommand(clipId, scheduledFor) {
|
|
4394
|
+
const response = await adminApiPatch(`/api/admin/clips/${encodeURIComponent(clipId)}/schedule`, { scheduledFor });
|
|
4395
|
+
return response.post;
|
|
4396
|
+
}
|
|
4397
|
+
async function clipDripPauseCommand(paused) {
|
|
4398
|
+
const response = await adminApiPut("/api/admin/clips/drip/state", {
|
|
4399
|
+
paused
|
|
4400
|
+
});
|
|
4401
|
+
return response.paused;
|
|
4402
|
+
}
|
|
4364
4403
|
async function resolveClipSource(clip) {
|
|
4365
4404
|
if (!clip.recordingId) {
|
|
4366
4405
|
throw new CliError2("clip_unlinked", `Clip ${clip.id} is linked to no recording`);
|
|
@@ -7704,8 +7743,20 @@ function addAdminCommands(program2) {
|
|
|
7704
7743
|
adminClips.outputHelp();
|
|
7705
7744
|
});
|
|
7706
7745
|
adminClips.command("list").description("List clips (filter by --status pending|done and/or --recording <id>)").option("--status <status>", "Filter by cut status (pending|done)").option("--recording <id>", "Filter by recording id").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
7707
|
-
const { clipsListCommand: clipsListCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
|
|
7708
|
-
await runClipsList(options, clipsListCommand2);
|
|
7746
|
+
const { clipsListCommand: clipsListCommand2, clipPostsListCommand: clipPostsListCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
|
|
7747
|
+
await runClipsList(options, clipsListCommand2, clipPostsListCommand2);
|
|
7748
|
+
});
|
|
7749
|
+
adminClips.command("schedule").description("Set or override a clip's Instagram drip slot (operator)").argument("[clipId]").requiredOption("--at <iso>", "The drip slot, an ISO-8601 timestamp").option("--json", "Print JSON", false).allowExcessArguments().action(async (clipId, options) => {
|
|
7750
|
+
const { clipScheduleCommand: clipScheduleCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
|
|
7751
|
+
await runClipsSchedule(clipId, options, clipScheduleCommand2);
|
|
7752
|
+
});
|
|
7753
|
+
adminClips.command("drip-pause").description("Pause the whole Instagram drip-feed \u2014 the kill switch (operator)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
7754
|
+
const { clipDripPauseCommand: clipDripPauseCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
|
|
7755
|
+
await runClipsDripPause(true, options, clipDripPauseCommand2);
|
|
7756
|
+
});
|
|
7757
|
+
adminClips.command("drip-resume").description("Resume the Instagram drip-feed \u2014 clear the kill switch (operator)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
7758
|
+
const { clipDripPauseCommand: clipDripPauseCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
|
|
7759
|
+
await runClipsDripPause(false, options, clipDripPauseCommand2);
|
|
7709
7760
|
});
|
|
7710
7761
|
adminClips.command("cut").description("Cut one clip's framed 9:16 footage from its set rendition, then ship it").argument("[clipId]").option("--json", "Print JSON", false).allowExcessArguments().action(async (clipId, options) => {
|
|
7711
7762
|
const { clipCutCommand: clipCutCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
|
|
@@ -8321,13 +8372,17 @@ async function runMixtapeList(options, mixtapeListCommand2) {
|
|
|
8321
8372
|
console.log(`${coordinate3} ${status} ${mixtape.memberCount} bangers ${mixtape.title}`);
|
|
8322
8373
|
}
|
|
8323
8374
|
}
|
|
8324
|
-
async function runClipsList(options, clipsListCommand2) {
|
|
8325
|
-
const clips = await
|
|
8326
|
-
recordingId: options.recording,
|
|
8327
|
-
|
|
8328
|
-
|
|
8375
|
+
async function runClipsList(options, clipsListCommand2, clipPostsListCommand2) {
|
|
8376
|
+
const [clips, posts] = await Promise.all([
|
|
8377
|
+
clipsListCommand2({ recordingId: options.recording, status: options.status }),
|
|
8378
|
+
clipPostsListCommand2().catch(() => [])
|
|
8379
|
+
]);
|
|
8380
|
+
const dripByClip = new Map(posts.map((post) => [post.clipId, post]));
|
|
8329
8381
|
if (options.json) {
|
|
8330
|
-
printJson({
|
|
8382
|
+
printJson({
|
|
8383
|
+
clips: clips.map((clip) => ({ ...clip, drip: dripByClip.get(clip.id) })),
|
|
8384
|
+
ok: true
|
|
8385
|
+
});
|
|
8331
8386
|
return;
|
|
8332
8387
|
}
|
|
8333
8388
|
if (clips.length === 0) {
|
|
@@ -8336,7 +8391,9 @@ async function runClipsList(options, clipsListCommand2) {
|
|
|
8336
8391
|
}
|
|
8337
8392
|
for (const clip of clips) {
|
|
8338
8393
|
const source = clip.recordingId ?? "\u2014";
|
|
8339
|
-
|
|
8394
|
+
const post = dripByClip.get(clip.id);
|
|
8395
|
+
const drip = post ? `${post.status} ${post.scheduledFor}` : "\u2014";
|
|
8396
|
+
console.log(`${clip.id} ${clip.status} ${source} ${clip.inMs}-${clip.outMs}ms x=${clip.xOffset} ${drip}`);
|
|
8340
8397
|
}
|
|
8341
8398
|
}
|
|
8342
8399
|
async function runClipsCut(clipId, options, clipCutCommand2) {
|
|
@@ -8351,6 +8408,25 @@ async function runClipsCut(clipId, options, clipCutCommand2) {
|
|
|
8351
8408
|
}
|
|
8352
8409
|
console.log(`Cut ${result.clipId} \u2192 ${result.url} (${(result.sizeBytes / 1e6).toFixed(1)} MB).`);
|
|
8353
8410
|
}
|
|
8411
|
+
async function runClipsSchedule(clipId, options, clipScheduleCommand2) {
|
|
8412
|
+
if (!clipId) {
|
|
8413
|
+
throw new Error("Missing clip id. Usage: fluncle admin clips schedule <clipId> --at <iso>");
|
|
8414
|
+
}
|
|
8415
|
+
const post = await clipScheduleCommand2(clipId, options.at);
|
|
8416
|
+
if (options.json) {
|
|
8417
|
+
printJson({ ok: true, post });
|
|
8418
|
+
return;
|
|
8419
|
+
}
|
|
8420
|
+
console.log(`Scheduled ${post.clipId} for ${post.scheduledFor}.`);
|
|
8421
|
+
}
|
|
8422
|
+
async function runClipsDripPause(paused, options, clipDripPauseCommand2) {
|
|
8423
|
+
const result = await clipDripPauseCommand2(paused);
|
|
8424
|
+
if (options.json) {
|
|
8425
|
+
printJson({ ok: true, paused: result });
|
|
8426
|
+
return;
|
|
8427
|
+
}
|
|
8428
|
+
console.log(result ? "The drip-feed is paused." : "The drip-feed is running.");
|
|
8429
|
+
}
|
|
8354
8430
|
async function runMixtapeGet(idOrLogId, options, mixtapeGetCommand2) {
|
|
8355
8431
|
if (!idOrLogId) {
|
|
8356
8432
|
throw new Error("Missing mixtape id or log id. Usage: fluncle admin mixtapes get <id|logId>");
|
package/package.json
CHANGED