fluncle 0.95.0 → 0.96.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 +76 -11
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -556,7 +556,7 @@ function parseVersion(version) {
556
556
  var currentVersion;
557
557
  var init_version = __esm(() => {
558
558
  init_output();
559
- currentVersion = "0.95.0".trim() ? "0.95.0".trim() : "0.1.0";
559
+ currentVersion = "0.96.0".trim() ? "0.96.0".trim() : "0.1.0";
560
560
  });
561
561
 
562
562
  // src/update-notifier.ts
@@ -2186,7 +2186,7 @@ function parseVersion2(version) {
2186
2186
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2187
2187
  var init_version2 = __esm(() => {
2188
2188
  init_output();
2189
- currentVersion2 = "0.95.0".trim() ? "0.95.0".trim() : "0.1.0";
2189
+ currentVersion2 = "0.96.0".trim() ? "0.96.0".trim() : "0.1.0";
2190
2190
  });
2191
2191
 
2192
2192
  // ../../packages/registry/src/index.ts
@@ -2789,6 +2789,17 @@ var init_src = __esm(() => {
2789
2789
  probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-social-capture", kind: "cron" },
2790
2790
  weights: { status: "hidden" }
2791
2791
  },
2792
+ {
2793
+ command: "fluncle admin clips drip-pause",
2794
+ exposedContent: [
2795
+ "post the due, cut clips to Instagram on a jittered ~daily cadence via Postiz (--no-agent, Worker HTTP)"
2796
+ ],
2797
+ kind: "cron",
2798
+ name: "cron.clip-drip",
2799
+ 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.",
2800
+ probeConfig: { cadenceMs: 20 * MINUTE_MS, cronName: "fluncle-clip-drip", kind: "cron" },
2801
+ weights: { status: "hidden" }
2802
+ },
2792
2803
  {
2793
2804
  command: "fluncle admin tracks queue",
2794
2805
  exposedContent: [
@@ -4284,7 +4295,10 @@ var exports_clips = {};
4284
4295
  __export(exports_clips, {
4285
4296
  setVideoUrl: () => setVideoUrl,
4286
4297
  clipsListCommand: () => clipsListCommand,
4298
+ clipScheduleCommand: () => clipScheduleCommand,
4299
+ clipPostsListCommand: () => clipPostsListCommand,
4287
4300
  clipFootageKey: () => clipFootageKey,
4301
+ clipDripPauseCommand: () => clipDripPauseCommand,
4288
4302
  clipCutFilterComplex: () => clipCutFilterComplex,
4289
4303
  clipCutFfmpegArgs: () => clipCutFfmpegArgs,
4290
4304
  clipCutCommand: () => clipCutCommand,
@@ -4361,6 +4375,20 @@ async function clipsListCommand(filter = {}) {
4361
4375
  const response = await adminApiGet(`/api/admin/clips${query ? `?${query}` : ""}`);
4362
4376
  return response.clips;
4363
4377
  }
4378
+ async function clipPostsListCommand() {
4379
+ const response = await adminApiGet("/api/admin/clips/social");
4380
+ return response.posts;
4381
+ }
4382
+ async function clipScheduleCommand(clipId, scheduledFor) {
4383
+ const response = await adminApiPatch(`/api/admin/clips/${encodeURIComponent(clipId)}/schedule`, { scheduledFor });
4384
+ return response.post;
4385
+ }
4386
+ async function clipDripPauseCommand(paused) {
4387
+ const response = await adminApiPut("/api/admin/clips/drip/state", {
4388
+ paused
4389
+ });
4390
+ return response.paused;
4391
+ }
4364
4392
  async function resolveClipSource(clip) {
4365
4393
  if (!clip.recordingId) {
4366
4394
  throw new CliError2("clip_unlinked", `Clip ${clip.id} is linked to no recording`);
@@ -7704,8 +7732,20 @@ function addAdminCommands(program2) {
7704
7732
  adminClips.outputHelp();
7705
7733
  });
7706
7734
  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);
7735
+ const { clipsListCommand: clipsListCommand2, clipPostsListCommand: clipPostsListCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
7736
+ await runClipsList(options, clipsListCommand2, clipPostsListCommand2);
7737
+ });
7738
+ 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) => {
7739
+ const { clipScheduleCommand: clipScheduleCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
7740
+ await runClipsSchedule(clipId, options, clipScheduleCommand2);
7741
+ });
7742
+ 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) => {
7743
+ const { clipDripPauseCommand: clipDripPauseCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
7744
+ await runClipsDripPause(true, options, clipDripPauseCommand2);
7745
+ });
7746
+ 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) => {
7747
+ const { clipDripPauseCommand: clipDripPauseCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
7748
+ await runClipsDripPause(false, options, clipDripPauseCommand2);
7709
7749
  });
7710
7750
  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
7751
  const { clipCutCommand: clipCutCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
@@ -8321,13 +8361,17 @@ async function runMixtapeList(options, mixtapeListCommand2) {
8321
8361
  console.log(`${coordinate3} ${status} ${mixtape.memberCount} bangers ${mixtape.title}`);
8322
8362
  }
8323
8363
  }
8324
- async function runClipsList(options, clipsListCommand2) {
8325
- const clips = await clipsListCommand2({
8326
- recordingId: options.recording,
8327
- status: options.status
8328
- });
8364
+ async function runClipsList(options, clipsListCommand2, clipPostsListCommand2) {
8365
+ const [clips, posts] = await Promise.all([
8366
+ clipsListCommand2({ recordingId: options.recording, status: options.status }),
8367
+ clipPostsListCommand2().catch(() => [])
8368
+ ]);
8369
+ const dripByClip = new Map(posts.map((post) => [post.clipId, post]));
8329
8370
  if (options.json) {
8330
- printJson({ clips, ok: true });
8371
+ printJson({
8372
+ clips: clips.map((clip) => ({ ...clip, drip: dripByClip.get(clip.id) })),
8373
+ ok: true
8374
+ });
8331
8375
  return;
8332
8376
  }
8333
8377
  if (clips.length === 0) {
@@ -8336,7 +8380,9 @@ async function runClipsList(options, clipsListCommand2) {
8336
8380
  }
8337
8381
  for (const clip of clips) {
8338
8382
  const source = clip.recordingId ?? "\u2014";
8339
- console.log(`${clip.id} ${clip.status} ${source} ${clip.inMs}-${clip.outMs}ms x=${clip.xOffset}`);
8383
+ const post = dripByClip.get(clip.id);
8384
+ const drip = post ? `${post.status} ${post.scheduledFor}` : "\u2014";
8385
+ console.log(`${clip.id} ${clip.status} ${source} ${clip.inMs}-${clip.outMs}ms x=${clip.xOffset} ${drip}`);
8340
8386
  }
8341
8387
  }
8342
8388
  async function runClipsCut(clipId, options, clipCutCommand2) {
@@ -8351,6 +8397,25 @@ async function runClipsCut(clipId, options, clipCutCommand2) {
8351
8397
  }
8352
8398
  console.log(`Cut ${result.clipId} \u2192 ${result.url} (${(result.sizeBytes / 1e6).toFixed(1)} MB).`);
8353
8399
  }
8400
+ async function runClipsSchedule(clipId, options, clipScheduleCommand2) {
8401
+ if (!clipId) {
8402
+ throw new Error("Missing clip id. Usage: fluncle admin clips schedule <clipId> --at <iso>");
8403
+ }
8404
+ const post = await clipScheduleCommand2(clipId, options.at);
8405
+ if (options.json) {
8406
+ printJson({ ok: true, post });
8407
+ return;
8408
+ }
8409
+ console.log(`Scheduled ${post.clipId} for ${post.scheduledFor}.`);
8410
+ }
8411
+ async function runClipsDripPause(paused, options, clipDripPauseCommand2) {
8412
+ const result = await clipDripPauseCommand2(paused);
8413
+ if (options.json) {
8414
+ printJson({ ok: true, paused: result });
8415
+ return;
8416
+ }
8417
+ console.log(result ? "The drip-feed is paused." : "The drip-feed is running.");
8418
+ }
8354
8419
  async function runMixtapeGet(idOrLogId, options, mixtapeGetCommand2) {
8355
8420
  if (!idOrLogId) {
8356
8421
  throw new Error("Missing mixtape id or log id. Usage: fluncle admin mixtapes get <id|logId>");
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.95.0"
34
+ "version": "0.96.0"
35
35
  }