fluncle 0.107.0 → 0.109.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 +113 -333
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -557,7 +557,7 @@ function parseVersion(version) {
557
557
  var currentVersion;
558
558
  var init_version = __esm(() => {
559
559
  init_output();
560
- currentVersion = "0.107.0".trim() ? "0.107.0".trim() : "0.1.0";
560
+ currentVersion = "0.109.0".trim() ? "0.109.0".trim() : "0.1.0";
561
561
  });
562
562
 
563
563
  // src/update-notifier.ts
@@ -1100,12 +1100,12 @@ async function distributeYoutube(mixtapeId, videoPath, onProgress) {
1100
1100
  continue;
1101
1101
  }
1102
1102
  if (result.kind === "reauth") {
1103
- onProgress?.("Access token expired re-minting and resuming");
1103
+ onProgress?.("Access token expired, re-minting and resuming");
1104
1104
  session = { accessToken: await mintToken(), sessionUri: session.sessionUri };
1105
1105
  offset = await queryOffset(session.sessionUri, session.accessToken, contentLength);
1106
1106
  continue;
1107
1107
  }
1108
- onProgress?.("Upload session expired re-initiating");
1108
+ onProgress?.("Upload session expired, re-initiating");
1109
1109
  session = await initiate(mixtapeId, contentLength, contentType);
1110
1110
  offset = 0;
1111
1111
  }
@@ -1401,7 +1401,7 @@ async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {
1401
1401
  const mixtapeId = mixtape.id;
1402
1402
  const logId = mixtape.logId;
1403
1403
  if (!logId) {
1404
- throw new CliError2("mixtape_not_promoted", `${mixtapeId} has no coordinate yet promote its recording first:
1404
+ throw new CliError2("mixtape_not_promoted", `${mixtapeId} has no coordinate yet. Promote its recording first:
1405
1405
  ` + " fluncle admin recordings promote <recordingId>");
1406
1406
  }
1407
1407
  if (!mixtape.durationMs) {
@@ -1415,7 +1415,7 @@ async function mixtapeDistributeCommand(idOrLogId, options, onProgress = () => {
1415
1415
  if (mixtape.status === "published") {
1416
1416
  onProgress(`Already published (${logId}); re-distributing.`);
1417
1417
  } else {
1418
- onProgress(`Distributing ${logId} (promoted coordinate already minted).`);
1418
+ onProgress(`Distributing ${logId} (promoted, coordinate already minted).`);
1419
1419
  }
1420
1420
  const results = [];
1421
1421
  if (doYoutube) {
@@ -1446,7 +1446,7 @@ async function mixtapeResyncCommand(idOrLogId, options, onProgress = () => {}) {
1446
1446
  throw new CliError2("mixtape_not_found", `No mixtape with id or log id ${idOrLogId}`);
1447
1447
  }
1448
1448
  if (!mixtape.logId) {
1449
- throw new CliError2("mixtape_no_log_id", "The mixtape isn't published yet distribute it before re-syncing.");
1449
+ throw new CliError2("mixtape_no_log_id", "The mixtape isn't published yet. Distribute it before re-syncing.");
1450
1450
  }
1451
1451
  const mixtapeId = mixtape.id;
1452
1452
  const explicit = Boolean(options.youtube) || Boolean(options.mixcloud);
@@ -1917,7 +1917,7 @@ async function loginCommand() {
1917
1917
  try {
1918
1918
  await openExternal(verificationUrl);
1919
1919
  } catch {
1920
- console.log("Couldn't open a browser automatically visit the URL above to approve.");
1920
+ console.log("Couldn't open a browser automatically. Visit the URL above to approve.");
1921
1921
  }
1922
1922
  const token = await pollForToken(baseUrl, device);
1923
1923
  const user = await fetchSessionUser(baseUrl, token.access_token);
@@ -1939,7 +1939,7 @@ async function logoutCommand() {
1939
1939
  });
1940
1940
  }
1941
1941
  const cleared = clearUserToken();
1942
- console.log(cleared ? "Signed out. This device is no longer linked to your account." : "Not signed in nothing to do.");
1942
+ console.log(cleared ? "Signed out. This device is no longer linked to your account." : "Not signed in. Nothing to do.");
1943
1943
  }
1944
1944
  async function requestDeviceCode(baseUrl) {
1945
1945
  const response = await fetch(`${baseUrl}/api/auth/device/code`, {
@@ -2207,7 +2207,7 @@ function parseVersion2(version) {
2207
2207
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2208
2208
  var init_version2 = __esm(() => {
2209
2209
  init_output();
2210
- currentVersion2 = "0.107.0".trim() ? "0.107.0".trim() : "0.1.0";
2210
+ currentVersion2 = "0.109.0".trim() ? "0.109.0".trim() : "0.1.0";
2211
2211
  });
2212
2212
 
2213
2213
  // ../../packages/registry/src/index.ts
@@ -2928,7 +2928,8 @@ var init_src = __esm(() => {
2928
2928
  probeConfig: {
2929
2929
  cadenceMs: 7 * 24 * 60 * MINUTE_MS,
2930
2930
  cronName: "fluncle-newsletter",
2931
- kind: "cron"
2931
+ kind: "cron",
2932
+ schedule: { time: "15:00", tz: "Europe/Amsterdam", weekday: 5 }
2932
2933
  },
2933
2934
  weights: { status: "secondary" }
2934
2935
  },
@@ -2939,7 +2940,42 @@ var init_src = __esm(() => {
2939
2940
  kind: "cron",
2940
2941
  name: "cron.backup",
2941
2942
  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.*",
2942
- probeConfig: { cadenceMs: 24 * 60 * MINUTE_MS, cronName: "fluncle-backup", kind: "cron" },
2943
+ probeConfig: {
2944
+ cadenceMs: 24 * 60 * MINUTE_MS,
2945
+ cronName: "fluncle-backup",
2946
+ kind: "cron",
2947
+ schedule: { time: "03:00", tz: "Europe/Amsterdam" }
2948
+ },
2949
+ weights: { status: "secondary" }
2950
+ },
2951
+ {
2952
+ exposedContent: [
2953
+ "nightly codebase audit — one domain/night on a 7-day rotation; opens a PR the reviewer merges (claude -p, subscription auth)"
2954
+ ],
2955
+ kind: "cron",
2956
+ name: "cron.audit",
2957
+ operatorNotes: "01:00 Amsterdam, a rave-02 host systemd timer (docs/agents/hermes/audit-timer/). A full agentic claude -p session: audits the day's domain, fixes what's safe, files the rest to docs/audit-backlog.md, opens a PR. Subscription auth (CLAUDE_CODE_OAUTH_TOKEN), zero OpenRouter tokens. Source: docs/agents/hermes/scripts/audit-sweep.sh. Probed on /status as service `cron.audit`.",
2958
+ probeConfig: {
2959
+ cadenceMs: 24 * 60 * MINUTE_MS,
2960
+ cronName: "fluncle-audit",
2961
+ kind: "cron",
2962
+ schedule: { time: "01:00", tz: "Europe/Amsterdam" }
2963
+ },
2964
+ weights: { status: "secondary" }
2965
+ },
2966
+ {
2967
+ exposedContent: [
2968
+ "05:00 reviewer for the nightly audit PR — fix-small-and-merge on green CI, else comment + hold (claude -p)"
2969
+ ],
2970
+ kind: "cron",
2971
+ name: "cron.audit-review",
2972
+ operatorNotes: "05:00 Amsterdam, a rave-02 host systemd timer (docs/agents/hermes/audit-review-timer/). Reviews the newest open audit/* PR adversarially; merges when required checks are green and nothing high-impact remains, else comments and leaves it for the operator. Source: docs/agents/hermes/scripts/audit-review-sweep.sh. Probed on /status as service `cron.audit-review`.",
2973
+ probeConfig: {
2974
+ cadenceMs: 24 * 60 * MINUTE_MS,
2975
+ cronName: "fluncle-audit-review",
2976
+ kind: "cron",
2977
+ schedule: { time: "05:00", tz: "Europe/Amsterdam" }
2978
+ },
2943
2979
  weights: { status: "secondary" }
2944
2980
  }
2945
2981
  ];
@@ -3052,7 +3088,7 @@ function checkBundleCompleteness(files) {
3052
3088
  const missingFrom = (specs) => uploadingFootage ? specs.filter((spec) => !files[spec.option]).map((spec) => spec.file) : [];
3053
3089
  const plateWarnings = [];
3054
3090
  if (files.plateBackground && !files.plate) {
3055
- 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");
3091
+ 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");
3056
3092
  }
3057
3093
  return {
3058
3094
  missingAdvisory: missingFrom(RERENDER_ADVISORY_FIELDS),
@@ -3068,10 +3104,10 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
3068
3104
  }
3069
3105
  if (completeness.uploadingFootage) {
3070
3106
  if (completeness.missingContract.length > 0) {
3071
- onProgress?.(`warning: --allow-partial uploading WITHOUT the re-render contract (${completeness.missingContract.join(", ")}); the R2 bundle will NOT be re-renderable`);
3107
+ onProgress?.(`warning: --allow-partial, uploading WITHOUT the re-render contract (${completeness.missingContract.join(", ")}); the R2 bundle will NOT be re-renderable`);
3072
3108
  }
3073
3109
  for (const missing of completeness.missingAdvisory) {
3074
- onProgress?.(`warning: ${missing} missing (provenance/eval only) shipping without it`);
3110
+ onProgress?.(`warning: ${missing} missing (provenance/eval only), shipping without it`);
3075
3111
  }
3076
3112
  }
3077
3113
  for (const warning of completeness.plateWarnings) {
@@ -3106,7 +3142,7 @@ async function trackVideoCommand(idOrLogId, files, onProgress, options = {}) {
3106
3142
  urls[spec.field] = `${FOUND_BASE}/${upload.key}`;
3107
3143
  }
3108
3144
  if (platesOnly) {
3109
- onProgress?.(`plate pre-upload complete compose against ${FOUND_BASE}/${presign.logId}/plate.png; finalize is deferred to the footage ship`);
3145
+ onProgress?.(`plate pre-upload complete. Compose against ${FOUND_BASE}/${presign.logId}/plate.png; finalize is deferred to the footage ship`);
3110
3146
  return { logId: presign.logId, ok: true, trackId: presign.trackId, urls };
3111
3147
  }
3112
3148
  const manifest = files.render ? await readManifestFields(files.render) : {};
@@ -3264,246 +3300,6 @@ var init_track = __esm(() => {
3264
3300
  NON_FILE_OPTIONS = ["model", "reasoning"];
3265
3301
  });
3266
3302
 
3267
- // src/commands/helm.ts
3268
- var exports_helm = {};
3269
- __export(exports_helm, {
3270
- resolveHelmDir: () => resolveHelmDir,
3271
- helmUninstallCommand: () => helmUninstallCommand,
3272
- helmOpenCommand: () => helmOpenCommand,
3273
- helmInstallCommand: () => helmInstallCommand,
3274
- buildLaunchAgentPlist: () => buildLaunchAgentPlist
3275
- });
3276
- import { existsSync, mkdirSync as mkdirSync2, openSync, rmSync as rmSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "node:fs";
3277
- import { homedir as homedir4 } from "node:os";
3278
- import { dirname as dirname3, join as join4, resolve } from "node:path";
3279
- function launchAgentPlistPath() {
3280
- return join4(homedir4(), "Library/LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
3281
- }
3282
- function helmLogPath() {
3283
- return join4(homedir4(), "Library/Logs/fluncle-helm.log");
3284
- }
3285
- function walkUpForHelm(start) {
3286
- let dir = start;
3287
- for (let depth = 0;depth < 8; depth++) {
3288
- const candidate = join4(dir, "apps/helm");
3289
- if (existsSync(join4(candidate, "src/server.ts"))) {
3290
- return candidate;
3291
- }
3292
- const parent = dirname3(dir);
3293
- if (parent === dir) {
3294
- break;
3295
- }
3296
- dir = parent;
3297
- }
3298
- return null;
3299
- }
3300
- function persistHelmDir(dir) {
3301
- try {
3302
- mkdirSync2(dirname3(HELM_DIR_FILE), { recursive: true });
3303
- writeFileSync2(HELM_DIR_FILE, `${dir}
3304
- `, { mode: 384 });
3305
- } catch {}
3306
- }
3307
- function resolveHelmDir() {
3308
- const fromEnv = process.env.FLUNCLE_HELM_DIR;
3309
- if (fromEnv) {
3310
- if (!existsSync(join4(fromEnv, "src/server.ts"))) {
3311
- throw new CliError2("helm_not_found", `FLUNCLE_HELM_DIR points at ${fromEnv}, but there's no helm there (src/server.ts missing).`);
3312
- }
3313
- return resolve(fromEnv);
3314
- }
3315
- const found = walkUpForHelm(process.cwd()) ?? walkUpForHelm(import.meta.dir);
3316
- if (found) {
3317
- persistHelmDir(found);
3318
- return found;
3319
- }
3320
- try {
3321
- const remembered = readFileSync2(HELM_DIR_FILE, "utf8").trim();
3322
- if (remembered && existsSync(join4(remembered, "src/server.ts"))) {
3323
- return remembered;
3324
- }
3325
- } catch {}
3326
- throw new CliError2("helm_not_found", "No helm aboard. Run this once from the fluncle repo (the helm remembers the way after that), or set FLUNCLE_HELM_DIR.");
3327
- }
3328
- function resolveBun() {
3329
- const bun = Bun.which("bun");
3330
- if (!bun) {
3331
- throw new CliError2("no_bun", "No bun on the PATH. The helm daemon runs under bun.");
3332
- }
3333
- return bun;
3334
- }
3335
- async function daemonHealthy() {
3336
- try {
3337
- const response = await fetch(`${HELM_URL}/api/health`, {
3338
- signal: AbortSignal.timeout(1500)
3339
- });
3340
- return response.ok;
3341
- } catch {
3342
- return false;
3343
- }
3344
- }
3345
- async function ensureBuilt(helmDir) {
3346
- if (existsSync(join4(helmDir, "dist/index.html"))) {
3347
- return;
3348
- }
3349
- console.log("The glass isn't built yet. Building it once…");
3350
- const proc = Bun.spawn([resolveBun(), "run", "build"], {
3351
- cwd: helmDir,
3352
- stderr: "inherit",
3353
- stdin: "ignore",
3354
- stdout: "inherit"
3355
- });
3356
- const exitCode = await proc.exited;
3357
- if (exitCode !== 0) {
3358
- throw new CliError2("helm_build_failed", `The glass build failed (exit ${exitCode}).`);
3359
- }
3360
- }
3361
- function bootDaemon(helmDir) {
3362
- const logPath = helmLogPath();
3363
- mkdirSync2(dirname3(logPath), { recursive: true });
3364
- const logFd = openSync(logPath, "a");
3365
- const proc = Bun.spawn([resolveBun(), "src/server.ts"], {
3366
- cwd: helmDir,
3367
- stderr: logFd,
3368
- stdin: "ignore",
3369
- stdout: logFd
3370
- });
3371
- proc.unref();
3372
- }
3373
- async function waitForHealth(timeoutMs) {
3374
- const deadline = Date.now() + timeoutMs;
3375
- while (Date.now() < deadline) {
3376
- if (await daemonHealthy()) {
3377
- return true;
3378
- }
3379
- await Bun.sleep(250);
3380
- }
3381
- return false;
3382
- }
3383
- async function openAppWindow() {
3384
- if (process.platform === "darwin" && chromeInstalled()) {
3385
- const proc = Bun.spawn(["open", "-na", "Google Chrome", "--args", `--app=${HELM_URL}`], {
3386
- stderr: "ignore",
3387
- stdin: "ignore",
3388
- stdout: "ignore"
3389
- });
3390
- if (await proc.exited === 0) {
3391
- return;
3392
- }
3393
- }
3394
- await openExternal(HELM_URL);
3395
- }
3396
- function chromeInstalled() {
3397
- return existsSync("/Applications/Google Chrome.app") || existsSync(join4(homedir4(), "Applications/Google Chrome.app"));
3398
- }
3399
- async function helmOpenCommand() {
3400
- if (await daemonHealthy()) {
3401
- console.log(`The helm holds on :${HELM_PORT}. Opening the window.`);
3402
- await openAppWindow();
3403
- return;
3404
- }
3405
- const helmDir = resolveHelmDir();
3406
- await ensureBuilt(helmDir);
3407
- bootDaemon(helmDir);
3408
- if (!await waitForHealth(15000)) {
3409
- throw new CliError2("helm_no_answer", `The daemon didn't answer on :${HELM_PORT}. Its log is at ${helmLogPath()}.`);
3410
- }
3411
- console.log(`Helm raised on :${HELM_PORT}. Opening the window.`);
3412
- await openAppWindow();
3413
- }
3414
- function xmlEscape(text) {
3415
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3416
- }
3417
- function buildLaunchAgentPlist(helmDir, bunPath, logPath) {
3418
- const path2 = `${dirname3(bunPath)}:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin`;
3419
- return `<?xml version="1.0" encoding="UTF-8"?>
3420
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3421
- <plist version="1.0">
3422
- <dict>
3423
- <key>EnvironmentVariables</key>
3424
- <dict>
3425
- <key>PATH</key>
3426
- <string>${xmlEscape(path2)}</string>
3427
- </dict>
3428
- <key>KeepAlive</key>
3429
- <dict>
3430
- <key>SuccessfulExit</key>
3431
- <false/>
3432
- </dict>
3433
- <key>Label</key>
3434
- <string>${LAUNCH_AGENT_LABEL}</string>
3435
- <key>ProgramArguments</key>
3436
- <array>
3437
- <string>${xmlEscape(bunPath)}</string>
3438
- <string>src/server.ts</string>
3439
- </array>
3440
- <key>RunAtLoad</key>
3441
- <true/>
3442
- <key>StandardErrorPath</key>
3443
- <string>${xmlEscape(logPath)}</string>
3444
- <key>StandardOutPath</key>
3445
- <string>${xmlEscape(logPath)}</string>
3446
- <key>WorkingDirectory</key>
3447
- <string>${xmlEscape(helmDir)}</string>
3448
- </dict>
3449
- </plist>
3450
- `;
3451
- }
3452
- function currentUid() {
3453
- const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
3454
- if (uid === undefined) {
3455
- throw new CliError2("no_uid", "Couldn't read the user id — launchd needs a gui domain.");
3456
- }
3457
- return uid;
3458
- }
3459
- async function launchctl(args) {
3460
- const proc = Bun.spawn(["launchctl", ...args], {
3461
- stderr: "ignore",
3462
- stdin: "ignore",
3463
- stdout: "ignore"
3464
- });
3465
- return proc.exited;
3466
- }
3467
- async function helmInstallCommand() {
3468
- if (process.platform !== "darwin") {
3469
- throw new CliError2("unsupported_platform", "launchd is a macOS thing. Nothing to install here.");
3470
- }
3471
- const helmDir = resolveHelmDir();
3472
- await ensureBuilt(helmDir);
3473
- const plistPath = launchAgentPlistPath();
3474
- mkdirSync2(dirname3(plistPath), { recursive: true });
3475
- mkdirSync2(dirname3(helmLogPath()), { recursive: true });
3476
- writeFileSync2(plistPath, buildLaunchAgentPlist(helmDir, resolveBun(), helmLogPath()));
3477
- const uid = currentUid();
3478
- await launchctl(["bootout", `gui/${uid}/${LAUNCH_AGENT_LABEL}`]);
3479
- const exitCode = await launchctl(["bootstrap", `gui/${uid}`, plistPath]);
3480
- if (exitCode !== 0) {
3481
- throw new CliError2("launchctl_refused", `launchctl bootstrap refused (exit ${exitCode}). The plist is at ${plistPath}.`);
3482
- }
3483
- console.log(`LaunchAgent installed (${LAUNCH_AGENT_LABEL}).`);
3484
- console.log(`The daemon rises at login and holds :${HELM_PORT}. Log: ${helmLogPath()}`);
3485
- }
3486
- async function helmUninstallCommand() {
3487
- if (process.platform !== "darwin") {
3488
- throw new CliError2("unsupported_platform", "launchd is a macOS thing. Nothing to remove here.");
3489
- }
3490
- const plistPath = launchAgentPlistPath();
3491
- await launchctl(["bootout", `gui/${currentUid()}/${LAUNCH_AGENT_LABEL}`]);
3492
- if (existsSync(plistPath)) {
3493
- rmSync2(plistPath);
3494
- console.log("LaunchAgent removed and the daemon stood down.");
3495
- return;
3496
- }
3497
- console.log("No LaunchAgent on file. Nothing stood down.");
3498
- }
3499
- var HELM_PORT = 4190, HELM_URL, LAUNCH_AGENT_LABEL = "com.fluncle.helm", HELM_DIR_FILE;
3500
- var init_helm = __esm(() => {
3501
- init_open_external();
3502
- init_output();
3503
- HELM_URL = `http://127.0.0.1:${HELM_PORT}`;
3504
- HELM_DIR_FILE = join4(homedir4(), ".config/fluncle/helm-dir");
3505
- });
3506
-
3507
3303
  // src/commands/add.ts
3508
3304
  var exports_add = {};
3509
3305
  __export(exports_add, {
@@ -3925,12 +3721,12 @@ async function distributeYoutube2(mixtapeId, videoPath, onProgress) {
3925
3721
  continue;
3926
3722
  }
3927
3723
  if (result.kind === "reauth") {
3928
- onProgress?.("Access token expired re-minting and resuming");
3724
+ onProgress?.("Access token expired, re-minting and resuming");
3929
3725
  session = { accessToken: await mintToken2(), sessionUri: session.sessionUri };
3930
3726
  offset = await queryOffset2(session.sessionUri, session.accessToken, contentLength);
3931
3727
  continue;
3932
3728
  }
3933
- onProgress?.("Upload session expired re-initiating");
3729
+ onProgress?.("Upload session expired, re-initiating");
3934
3730
  session = await initiate2(mixtapeId, contentLength, contentType);
3935
3731
  offset = 0;
3936
3732
  }
@@ -4085,9 +3881,9 @@ var init_multipart = __esm(() => {
4085
3881
 
4086
3882
  // src/commands/mixtape-set-video.ts
4087
3883
  import { randomUUID } from "node:crypto";
4088
- import { existsSync as existsSync2, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
3884
+ import { existsSync, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
4089
3885
  import { tmpdir } from "node:os";
4090
- import { join as join5 } from "node:path";
3886
+ import { join as join4 } from "node:path";
4091
3887
  function renditionFfmpegArgs(inputPath, outputPath) {
4092
3888
  return [
4093
3889
  "-y",
@@ -4115,11 +3911,11 @@ function renditionFfmpegArgs(inputPath, outputPath) {
4115
3911
  ];
4116
3912
  }
4117
3913
  async function uploadRenditionMultipart(masterPath, presignPath, onProgress = () => {}) {
4118
- if (!existsSync2(masterPath)) {
3914
+ if (!existsSync(masterPath)) {
4119
3915
  throw new CliError2("file_not_found", `Set-video master not found: ${masterPath}`);
4120
3916
  }
4121
3917
  await assertFfmpeg();
4122
- const renditionPath = join5(tmpdir(), `fluncle-set-${randomUUID()}.mp4`);
3918
+ const renditionPath = join4(tmpdir(), `fluncle-set-${randomUUID()}.mp4`);
4123
3919
  onProgress("Set video: deriving the 1080p faststart rendition (ffmpeg)…");
4124
3920
  await deriveRendition(masterPath, renditionPath);
4125
3921
  try {
@@ -4148,7 +3944,7 @@ async function uploadRenditionMultipart(masterPath, presignPath, onProgress = ()
4148
3944
  }
4149
3945
  return { key: presign.key, url: `${FOUND_BASE2}/${presign.key}` };
4150
3946
  } finally {
4151
- rmSync3(renditionPath, { force: true });
3947
+ rmSync2(renditionPath, { force: true });
4152
3948
  }
4153
3949
  }
4154
3950
  async function putPart(url, path2, part, onProgress = () => {}) {
@@ -4164,8 +3960,8 @@ async function putPart(url, path2, part, onProgress = () => {}) {
4164
3960
  throw new CliError2("r2_part_failed", `Part ${part.partNumber} failed after ${MAX_PART_ATTEMPTS} attempts: ${error instanceof Error ? error.message : String(error)}`);
4165
3961
  }
4166
3962
  const backoffMs = 500 * 2 ** (attempt - 1);
4167
- onProgress(`Set video: part ${part.partNumber} dropped retry ${attempt}/${MAX_PART_ATTEMPTS - 1} in ${backoffMs}ms…`);
4168
- await new Promise((resolve2) => setTimeout(resolve2, backoffMs));
3963
+ onProgress(`Set video: part ${part.partNumber} dropped, retry ${attempt}/${MAX_PART_ATTEMPTS - 1} in ${backoffMs}ms…`);
3964
+ await new Promise((resolve) => setTimeout(resolve, backoffMs));
4169
3965
  }
4170
3966
  }
4171
3967
  }
@@ -4245,7 +4041,7 @@ __export(exports_recordings, {
4245
4041
  recordingDeleteCommand: () => recordingDeleteCommand,
4246
4042
  recordingCreateCommand: () => recordingCreateCommand
4247
4043
  });
4248
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
4044
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
4249
4045
  async function recordingGet(id) {
4250
4046
  const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
4251
4047
  return response.recording;
@@ -4304,7 +4100,7 @@ async function recordingCreateCommand(options = {}) {
4304
4100
  printJson2({ ok: true, recording: recording2 });
4305
4101
  return;
4306
4102
  }
4307
- console.log(`Plan ${recording2.id} created handle "${recording2.title}"`);
4103
+ console.log(`Plan ${recording2.id} created, handle "${recording2.title}"`);
4308
4104
  return;
4309
4105
  }
4310
4106
  const title = options.title?.trim();
@@ -4314,7 +4110,7 @@ async function recordingCreateCommand(options = {}) {
4314
4110
  if (!options.video) {
4315
4111
  throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
4316
4112
  }
4317
- if (!existsSync3(options.video)) {
4113
+ if (!existsSync2(options.video)) {
4318
4114
  throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
4319
4115
  }
4320
4116
  const created = await adminApiPost("/api/admin/recordings", {
@@ -4327,7 +4123,7 @@ async function recordingCreateCommand(options = {}) {
4327
4123
  console.log(message);
4328
4124
  }
4329
4125
  };
4330
- log(`Recording ${recording.id} created staging the set video…`);
4126
+ log(`Recording ${recording.id} created, staging the set video…`);
4331
4127
  const upload = await uploadRenditionMultipart(options.video, `/api/admin/recordings/${encodeURIComponent(recording.id)}/set-video/presign`, log);
4332
4128
  if (options.json) {
4333
4129
  printJson2({ key: upload.key, ok: true, recording, url: upload.url });
@@ -4350,17 +4146,17 @@ async function recordingUpdateCommand(id, options = {}) {
4350
4146
  body.parentId = options.parentId;
4351
4147
  }
4352
4148
  if (options.tracklistFile !== undefined) {
4353
- if (!existsSync3(options.tracklistFile)) {
4149
+ if (!existsSync2(options.tracklistFile)) {
4354
4150
  throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
4355
4151
  }
4356
4152
  try {
4357
- body.tracklistJson = JSON.parse(readFileSync3(options.tracklistFile, "utf8"));
4153
+ body.tracklistJson = JSON.parse(readFileSync2(options.tracklistFile, "utf8"));
4358
4154
  } catch {
4359
4155
  throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
4360
4156
  }
4361
4157
  }
4362
4158
  if (Object.keys(body).length === 0) {
4363
- throw new CliError2("no_fields", "Nothing to update pass --title, --recorded-at, --parent-id, or --tracklist-file");
4159
+ throw new CliError2("no_fields", "Nothing to update. Pass --title, --recorded-at, --parent-id, or --tracklist-file");
4364
4160
  }
4365
4161
  const response = await adminApiPatch(`/api/admin/recordings/${encodeURIComponent(id)}`, body);
4366
4162
  if (options.json) {
@@ -4399,12 +4195,12 @@ async function recordingReplaceCuesCommand(id, options = {}) {
4399
4195
  if (!options.cuesFile) {
4400
4196
  throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
4401
4197
  }
4402
- if (!existsSync3(options.cuesFile)) {
4198
+ if (!existsSync2(options.cuesFile)) {
4403
4199
  throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
4404
4200
  }
4405
4201
  let cues;
4406
4202
  try {
4407
- cues = JSON.parse(readFileSync3(options.cuesFile, "utf8"));
4203
+ cues = JSON.parse(readFileSync2(options.cuesFile, "utf8"));
4408
4204
  } catch {
4409
4205
  throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
4410
4206
  }
@@ -4442,9 +4238,9 @@ __export(exports_clips, {
4442
4238
  CLIP_AUDIO_BITRATE: () => CLIP_AUDIO_BITRATE
4443
4239
  });
4444
4240
  import { randomUUID as randomUUID2 } from "node:crypto";
4445
- import { rmSync as rmSync4, statSync as statSync4 } from "node:fs";
4241
+ import { rmSync as rmSync3, statSync as statSync4 } from "node:fs";
4446
4242
  import { tmpdir as tmpdir2 } from "node:os";
4447
- import { join as join6 } from "node:path";
4243
+ import { join as join5 } from "node:path";
4448
4244
  function clipFootageKey(clipId) {
4449
4245
  return `${clipId}/footage.mp4`;
4450
4246
  }
@@ -4541,7 +4337,7 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
4541
4337
  }
4542
4338
  const source = await resolveClipSource(clip);
4543
4339
  await assertFfmpeg2();
4544
- const outputPath = join6(tmpdir2(), `fluncle-clip-${randomUUID2()}.mp4`);
4340
+ const outputPath = join5(tmpdir2(), `fluncle-clip-${randomUUID2()}.mp4`);
4545
4341
  try {
4546
4342
  onProgress(`Clip ${clipId}: cutting [${clip.inMs}–${clip.outMs}ms]…`);
4547
4343
  await runClipCut({
@@ -4553,7 +4349,7 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
4553
4349
  });
4554
4350
  const sizeBytes = statSync4(outputPath).size;
4555
4351
  if (sizeBytes > MAX_CLIP_BYTES) {
4556
- throw new CliError2("clip_too_large", `The cut is ${(sizeBytes / 1e6).toFixed(1)} MB (> 100 MB Cloudflare MT ceiling) shorten the window or lower the bitrate cap`);
4352
+ throw new CliError2("clip_too_large", `The cut is ${(sizeBytes / 1e6).toFixed(1)} MB (> 100 MB Cloudflare MT ceiling). Shorten the window or lower the bitrate cap`);
4557
4353
  }
4558
4354
  onProgress(`Clip ${clipId}: uploading ${(sizeBytes / 1e6).toFixed(1)} MB…`);
4559
4355
  const presign = await adminApiPost(`/api/admin/clips/${encodeURIComponent(clipId)}/cut/presign`, { contentType: "video/mp4" });
@@ -4567,7 +4363,7 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
4567
4363
  url: `${FOUND_BASE3}/${presign.key}`
4568
4364
  };
4569
4365
  } finally {
4570
- rmSync4(outputPath, { force: true });
4366
+ rmSync3(outputPath, { force: true });
4571
4367
  }
4572
4368
  }
4573
4369
  async function putClip(url, contentType, path2) {
@@ -4625,7 +4421,7 @@ __export(exports_recordings2, {
4625
4421
  recordingDeleteCommand: () => recordingDeleteCommand2,
4626
4422
  recordingCreateCommand: () => recordingCreateCommand2
4627
4423
  });
4628
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
4424
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
4629
4425
  async function recordingGet2(id) {
4630
4426
  const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
4631
4427
  return response.recording;
@@ -4684,7 +4480,7 @@ async function recordingCreateCommand2(options = {}) {
4684
4480
  printJson2({ ok: true, recording: recording2 });
4685
4481
  return;
4686
4482
  }
4687
- console.log(`Plan ${recording2.id} created handle "${recording2.title}"`);
4483
+ console.log(`Plan ${recording2.id} created, handle "${recording2.title}"`);
4688
4484
  return;
4689
4485
  }
4690
4486
  const title = options.title?.trim();
@@ -4694,7 +4490,7 @@ async function recordingCreateCommand2(options = {}) {
4694
4490
  if (!options.video) {
4695
4491
  throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
4696
4492
  }
4697
- if (!existsSync4(options.video)) {
4493
+ if (!existsSync3(options.video)) {
4698
4494
  throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
4699
4495
  }
4700
4496
  const created = await adminApiPost("/api/admin/recordings", {
@@ -4707,7 +4503,7 @@ async function recordingCreateCommand2(options = {}) {
4707
4503
  console.log(message);
4708
4504
  }
4709
4505
  };
4710
- log(`Recording ${recording.id} created staging the set video…`);
4506
+ log(`Recording ${recording.id} created, staging the set video…`);
4711
4507
  const upload = await uploadRenditionMultipart(options.video, `/api/admin/recordings/${encodeURIComponent(recording.id)}/set-video/presign`, log);
4712
4508
  if (options.json) {
4713
4509
  printJson2({ key: upload.key, ok: true, recording, url: upload.url });
@@ -4730,17 +4526,17 @@ async function recordingUpdateCommand2(id, options = {}) {
4730
4526
  body.parentId = options.parentId;
4731
4527
  }
4732
4528
  if (options.tracklistFile !== undefined) {
4733
- if (!existsSync4(options.tracklistFile)) {
4529
+ if (!existsSync3(options.tracklistFile)) {
4734
4530
  throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
4735
4531
  }
4736
4532
  try {
4737
- body.tracklistJson = JSON.parse(readFileSync4(options.tracklistFile, "utf8"));
4533
+ body.tracklistJson = JSON.parse(readFileSync3(options.tracklistFile, "utf8"));
4738
4534
  } catch {
4739
4535
  throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
4740
4536
  }
4741
4537
  }
4742
4538
  if (Object.keys(body).length === 0) {
4743
- throw new CliError2("no_fields", "Nothing to update pass --title, --recorded-at, --parent-id, or --tracklist-file");
4539
+ throw new CliError2("no_fields", "Nothing to update. Pass --title, --recorded-at, --parent-id, or --tracklist-file");
4744
4540
  }
4745
4541
  const response = await adminApiPatch(`/api/admin/recordings/${encodeURIComponent(id)}`, body);
4746
4542
  if (options.json) {
@@ -4779,12 +4575,12 @@ async function recordingReplaceCuesCommand2(id, options = {}) {
4779
4575
  if (!options.cuesFile) {
4780
4576
  throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
4781
4577
  }
4782
- if (!existsSync4(options.cuesFile)) {
4578
+ if (!existsSync3(options.cuesFile)) {
4783
4579
  throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
4784
4580
  }
4785
4581
  let cues;
4786
4582
  try {
4787
- cues = JSON.parse(readFileSync4(options.cuesFile, "utf8"));
4583
+ cues = JSON.parse(readFileSync3(options.cuesFile, "utf8"));
4788
4584
  } catch {
4789
4585
  throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
4790
4586
  }
@@ -4810,7 +4606,7 @@ __export(exports_newsletter, {
4810
4606
  newsletterDraftCommand: () => newsletterDraftCommand,
4811
4607
  newsletterDeleteCommand: () => newsletterDeleteCommand
4812
4608
  });
4813
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
4609
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
4814
4610
  function buildBody2(options, { requireContent }) {
4815
4611
  const body = {};
4816
4612
  if (options.contentFile !== undefined) {
@@ -4830,10 +4626,10 @@ function buildBody2(options, { requireContent }) {
4830
4626
  return body;
4831
4627
  }
4832
4628
  function readContentFile(filePath) {
4833
- if (!existsSync5(filePath)) {
4629
+ if (!existsSync4(filePath)) {
4834
4630
  throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
4835
4631
  }
4836
- const text = readFileSync5(filePath, "utf-8");
4632
+ const text = readFileSync4(filePath, "utf-8");
4837
4633
  try {
4838
4634
  return JSON.parse(text);
4839
4635
  } catch (error) {
@@ -5250,7 +5046,7 @@ async function selectWithKeyboard2(items, options) {
5250
5046
  let renderedLines = 0;
5251
5047
  let done = false;
5252
5048
  const wasRaw = stdin.isRaw === true;
5253
- return await new Promise((resolve2) => {
5049
+ return await new Promise((resolve) => {
5254
5050
  function cleanup() {
5255
5051
  if (done) {
5256
5052
  return;
@@ -5265,14 +5061,14 @@ async function selectWithKeyboard2(items, options) {
5265
5061
  cleanup();
5266
5062
  stdout.write(renderedLines > 0 ? `
5267
5063
  ` : "");
5268
- resolve2(item);
5064
+ resolve(item);
5269
5065
  }
5270
5066
  function cancel() {
5271
5067
  cleanup();
5272
5068
  clearRendered2(stdout, renderedLines);
5273
5069
  stdout.write(`Cancelled.
5274
5070
  `);
5275
- resolve2(undefined);
5071
+ resolve(undefined);
5276
5072
  }
5277
5073
  function render() {
5278
5074
  clearRendered2(stdout, renderedLines);
@@ -5328,7 +5124,7 @@ async function paginateWithKeyboard(options) {
5328
5124
  let busy = false;
5329
5125
  let done = false;
5330
5126
  const wasRaw = stdin.isRaw === true;
5331
- return await new Promise((resolve2) => {
5127
+ return await new Promise((resolve) => {
5332
5128
  function cleanup() {
5333
5129
  if (done) {
5334
5130
  return;
@@ -5343,7 +5139,7 @@ async function paginateWithKeyboard(options) {
5343
5139
  cleanup();
5344
5140
  stdout.write(`
5345
5141
  `);
5346
- resolve2();
5142
+ resolve();
5347
5143
  }
5348
5144
  function render() {
5349
5145
  clearRendered2(stdout, renderedLines);
@@ -5496,7 +5292,7 @@ var init_format2 = __esm(() => {
5496
5292
  });
5497
5293
 
5498
5294
  // src/cli.ts
5499
- import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
5295
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
5500
5296
  import path2 from "path";
5501
5297
 
5502
5298
  // ../../node_modules/commander/lib/error.js
@@ -7642,7 +7438,6 @@ ${fluncleAsciiLogo}
7642
7438
  addMetaCommands(program2);
7643
7439
  addTrackCommands(program2);
7644
7440
  addAdminCommands(program2);
7645
- addHelmCommands(program2);
7646
7441
  return program2;
7647
7442
  }
7648
7443
  async function main(args = process.argv.slice(2)) {
@@ -7680,7 +7475,7 @@ function addListenCommands(program2) {
7680
7475
  const { recentCommand: recentCommand3 } = await Promise.resolve().then(() => (init_recent(), exports_recent));
7681
7476
  await runRecent(options, recentCommand3);
7682
7477
  });
7683
- program2.command("artists").description("Browse artists in Fluncle's archive").argument("[slug]", "Artist slug \u2014 omit for the full list").option("--json", "Print JSON", false).action(async (slug, options) => {
7478
+ program2.command("artists").description("Browse artists in Fluncle's archive").argument("[slug]", "Artist slug (omit for the full list)").option("--json", "Print JSON", false).action(async (slug, options) => {
7684
7479
  const { artistsListCommand: artistsListCommand2, artistsGetCommand: artistsGetCommand2 } = await Promise.resolve().then(() => (init_artists(), exports_artists));
7685
7480
  await runArtists(slug, options, { artistsGetCommand: artistsGetCommand2, artistsListCommand: artistsListCommand2 });
7686
7481
  });
@@ -7745,21 +7540,6 @@ function addTrackCommands(program2) {
7745
7540
  await runTrackGet(idOrLogId, options, trackGetCommand2);
7746
7541
  });
7747
7542
  }
7748
- function addHelmCommands(program2) {
7749
- const helm = configureCommand(program2.command("helm", { hidden: true }).description("Fluncle's Helm \u2014 the operator's mission control"));
7750
- helm.action(async () => {
7751
- const { helmOpenCommand: helmOpenCommand2 } = await Promise.resolve().then(() => (init_helm(), exports_helm));
7752
- await helmOpenCommand2();
7753
- });
7754
- helm.command("install").description("Install the launchd LaunchAgent (the daemon rises at login)").action(async () => {
7755
- const { helmInstallCommand: helmInstallCommand2 } = await Promise.resolve().then(() => (init_helm(), exports_helm));
7756
- await helmInstallCommand2();
7757
- });
7758
- helm.command("uninstall").description("Remove the LaunchAgent and stand the daemon down").action(async () => {
7759
- const { helmUninstallCommand: helmUninstallCommand2 } = await Promise.resolve().then(() => (init_helm(), exports_helm));
7760
- await helmUninstallCommand2();
7761
- });
7762
- }
7763
7543
  function addAdminCommands(program2) {
7764
7544
  const admin = configureCommand(program2.command("admin", { hidden: true }).description("Operator commands"));
7765
7545
  admin.action(() => {
@@ -7784,27 +7564,27 @@ function addAdminCommands(program2) {
7784
7564
  const { listCommand: listCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7785
7565
  await runAdminTracksList(options, listCommand2);
7786
7566
  });
7787
- adminTracks.command("enrich").description("Enrichment worklist (pending, failed, or stuck processing) \u2014 use --queue").option("--queue", "Show the enrichment worklist, oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--json", "Print JSON", false).action(async (options) => {
7567
+ adminTracks.command("enrich").description("Enrichment worklist (pending, failed, or stuck processing). Use --queue").option("--queue", "Show the enrichment worklist, oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--json", "Print JSON", false).action(async (options) => {
7788
7568
  if (!options.queue) {
7789
- console.error("`tracks enrich` is a worklist view \u2014 enrichment runs on the on-box `fluncle-enrich` cron.\nUse `tracks enrich --queue` to see findings needing (re-)enrichment.");
7569
+ console.error("`tracks enrich` is a worklist view. Enrichment runs on the on-box `fluncle-enrich` cron.\nUse `tracks enrich --queue` to see findings needing (re-)enrichment.");
7790
7570
  process.exitCode = 1;
7791
7571
  return;
7792
7572
  }
7793
7573
  const { enrichQueueCommand: enrichQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7794
7574
  await runAdminEnrichQueue(options, enrichQueueCommand2);
7795
7575
  });
7796
- adminTracks.command("embed").description("Audio-embedding worklist (findings with no MuQ vector yet) \u2014 use --queue").option("--queue", "Show the embedding worklist, oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--json", "Print JSON", false).action(async (options) => {
7576
+ adminTracks.command("embed").description("Audio-embedding worklist (findings with no MuQ vector yet). Use --queue").option("--queue", "Show the embedding worklist, oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--json", "Print JSON", false).action(async (options) => {
7797
7577
  if (!options.queue) {
7798
- console.error("`tracks embed` is a worklist view \u2014 embedding runs on the on-box `fluncle-embed` cron.\nUse `tracks embed --queue` to see findings needing an audio embedding.");
7578
+ console.error("`tracks embed` is a worklist view. Embedding runs on the on-box `fluncle-embed` cron.\nUse `tracks embed --queue` to see findings needing an audio embedding.");
7799
7579
  process.exitCode = 1;
7800
7580
  return;
7801
7581
  }
7802
7582
  const { embedQueueCommand: embedQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
7803
7583
  await runAdminEmbedQueue(options, embedQueueCommand2);
7804
7584
  });
7805
- adminTracks.command("capture-audio").description("Full-song capture worklist (findings with no source audio yet) \u2014 use --queue").option("--queue", "Show the capture worklist, newest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--json", "Print JSON", false).action(async (options) => {
7585
+ adminTracks.command("capture-audio").description("Full-song capture worklist (findings with no source audio yet). Use --queue").option("--queue", "Show the capture worklist, newest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--json", "Print JSON", false).action(async (options) => {
7806
7586
  if (!options.queue) {
7807
- console.error("`tracks capture-audio` is a worklist view \u2014 capture runs on the on-box `fluncle-capture` cron.\nUse `tracks capture-audio --queue` to see findings needing a full-song capture.");
7587
+ console.error("`tracks capture-audio` is a worklist view. Capture runs on the on-box `fluncle-capture` cron.\nUse `tracks capture-audio --queue` to see findings needing a full-song capture.");
7808
7588
  process.exitCode = 1;
7809
7589
  return;
7810
7590
  }
@@ -7896,7 +7676,7 @@ function addAdminCommands(program2) {
7896
7676
  const { mixtapeGetCommand: mixtapeGetCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7897
7677
  await runMixtapeGet(idOrLogId, options, mixtapeGetCommand2);
7898
7678
  });
7899
- adminMixtapes.command("distribute").description("Push a promoted mixtape to YouTube (video) and Mixcloud (audio). The mixtape must already be promoted (`recordings promote`) \u2014 distribute is push-only.").argument("[idOrLogId]").option("--video <file>", "Video file for YouTube").option("--audio <file>", "Audio file for Mixcloud").option("--youtube", "Only distribute to YouTube").option("--mixcloud", "Only distribute to Mixcloud").option("--unlisted", "Keep Mixcloud private too (YouTube is always unlisted until publish-youtube)").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
7679
+ adminMixtapes.command("distribute").description("Push a promoted mixtape to YouTube (video) and Mixcloud (audio). The mixtape must already be promoted (`recordings promote`). Distribute is push-only.").argument("[idOrLogId]").option("--video <file>", "Video file for YouTube").option("--audio <file>", "Audio file for Mixcloud").option("--youtube", "Only distribute to YouTube").option("--mixcloud", "Only distribute to Mixcloud").option("--unlisted", "Keep Mixcloud private too (YouTube is always unlisted until publish-youtube)").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
7900
7680
  const { mixtapeDistributeCommand: mixtapeDistributeCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
7901
7681
  await runMixtapeDistribute(idOrLogId, options, mixtapeDistributeCommand2);
7902
7682
  });
@@ -7920,11 +7700,11 @@ function addAdminCommands(program2) {
7920
7700
  const { clipScheduleCommand: clipScheduleCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
7921
7701
  await runClipsSchedule(clipId, options, clipScheduleCommand2);
7922
7702
  });
7923
- 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) => {
7703
+ adminClips.command("drip-pause").description("Pause the whole Instagram drip-feed, the kill switch (operator)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
7924
7704
  const { clipDripPauseCommand: clipDripPauseCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
7925
7705
  await runClipsDripPause(true, options, clipDripPauseCommand2);
7926
7706
  });
7927
- 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) => {
7707
+ adminClips.command("drip-resume").description("Resume the Instagram drip-feed, clear the kill switch (operator)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
7928
7708
  const { clipDripPauseCommand: clipDripPauseCommand2 } = await Promise.resolve().then(() => (init_clips(), exports_clips));
7929
7709
  await runClipsDripPause(false, options, clipDripPauseCommand2);
7930
7710
  });
@@ -7976,7 +7756,7 @@ function addAdminCommands(program2) {
7976
7756
  const { newsletterUpdateCommand: newsletterUpdateCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
7977
7757
  await runNewsletterUpdate(id, options, newsletterUpdateCommand2);
7978
7758
  });
7979
- adminNewsletter.command("send").description("Send an edition \u2014 OPERATOR only (Resend broadcast + mint the number)").argument("[id]").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
7759
+ adminNewsletter.command("send").description("Send an edition, OPERATOR only (Resend broadcast + mint the number)").argument("[id]").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
7980
7760
  const { newsletterSendCommand: newsletterSendCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
7981
7761
  await runNewsletterSend(id, options, newsletterSendCommand2);
7982
7762
  });
@@ -7984,7 +7764,7 @@ function addAdminCommands(program2) {
7984
7764
  const { newsletterListCommand: newsletterListCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
7985
7765
  await runNewsletterList(options, newsletterListCommand2);
7986
7766
  });
7987
- adminNewsletter.command("delete").description("Delete an edition (draft or sent) \u2014 OPERATOR only; reopens the send window").argument("[id]").option("--yes", "Confirm the delete", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
7767
+ adminNewsletter.command("delete").description("Delete an edition (draft or sent). OPERATOR only; reopens the send window").argument("[id]").option("--yes", "Confirm the delete", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
7988
7768
  const { newsletterDeleteCommand: newsletterDeleteCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
7989
7769
  await runNewsletterDelete(id, options, newsletterDeleteCommand2);
7990
7770
  });
@@ -8084,7 +7864,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
8084
7864
  console.log(` mime: ${result.mime}`);
8085
7865
  }
8086
7866
  async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
8087
- const script = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
7867
+ const script = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
8088
7868
  if (!idOrLogId || !script || !script.trim()) {
8089
7869
  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]");
8090
7870
  }
@@ -8140,7 +7920,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
8140
7920
  }
8141
7921
  }
8142
7922
  async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
8143
- const note = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
7923
+ const note = options.scriptFile ? readFileSync5(options.scriptFile, "utf8") : options.script;
8144
7924
  if (!idOrLogId || !note || !note.trim()) {
8145
7925
  throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--json]");
8146
7926
  }
@@ -8329,7 +8109,7 @@ async function runArtistResolveQueue(options, listArtistsCommand2) {
8329
8109
  return;
8330
8110
  }
8331
8111
  if (result.artists.length === 0) {
8332
- console.log("Every artist has been resolved. Nothing waiting on the sweep.");
8112
+ console.log("Resolved every artist. Nothing waiting on the sweep.");
8333
8113
  return;
8334
8114
  }
8335
8115
  const noun = result.artists.length === 1 ? "artist" : "artists";
@@ -8371,7 +8151,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
8371
8151
  return;
8372
8152
  }
8373
8153
  const candidate = path2.join(dir, name);
8374
- return existsSync6(candidate) ? candidate : undefined;
8154
+ return existsSync5(candidate) ? candidate : undefined;
8375
8155
  };
8376
8156
  const resolveFile = (explicit, name) => {
8377
8157
  if (explicit) {
@@ -8555,7 +8335,7 @@ async function runTrackUpdate(trackId, options, trackUpdateCommand2) {
8555
8335
  if (bpm !== undefined && !Number.isFinite(bpm)) {
8556
8336
  throw new Error(`Invalid --bpm: ${options.bpm}`);
8557
8337
  }
8558
- const embeddingRaw = options.embeddingFile ? readFileSync6(options.embeddingFile, "utf8") : options.embedding;
8338
+ const embeddingRaw = options.embeddingFile ? readFileSync5(options.embeddingFile, "utf8") : options.embedding;
8559
8339
  const embedding = parseEmbeddingArg(embeddingRaw);
8560
8340
  const result = await trackUpdateCommand2(trackId, {
8561
8341
  bpm,
@@ -8581,7 +8361,7 @@ async function runTrackRequeueVideo(idOrLogId, options, trackRequeueVideoCommand
8581
8361
  printJson(result);
8582
8362
  return;
8583
8363
  }
8584
- console.log(result.alreadyClear ? `${result.logId} already had no video \u2014 nothing to clear.` : `Cleared the video for ${result.logId}. It's back on the render queue (and off radio until re-rendered).`);
8364
+ console.log(result.alreadyClear ? `${result.logId} already had no video. Nothing to clear.` : `Cleared the video for ${result.logId}. It's back on the render queue (and off radio until re-rendered).`);
8585
8365
  }
8586
8366
  async function runTrackPurgeVideo(idOrLogId, options, trackPurgeVideoCommand2) {
8587
8367
  if (!idOrLogId) {
@@ -8592,7 +8372,7 @@ async function runTrackPurgeVideo(idOrLogId, options, trackPurgeVideoCommand2) {
8592
8372
  printJson(result);
8593
8373
  return;
8594
8374
  }
8595
- 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.`);
8375
+ console.log(result.noVideo ? `${result.logId} has no video. Nothing to purge.` : `Purging the stale renditions for ${result.logId} from the edge. The next play picks up the fresh render.`);
8596
8376
  }
8597
8377
  async function runMixtapeUpdate(id, options, mixtapeUpdateCommand2) {
8598
8378
  if (!id) {
@@ -8780,14 +8560,14 @@ async function runNewsletterSend(id, options, newsletterSendCommand2) {
8780
8560
  return;
8781
8561
  }
8782
8562
  const number = result.edition.number;
8783
- console.log(number === undefined ? `Sent edition ${result.edition.id}.` : `Sent edition #${number} \u2014 it's out to the list and in the archive.`);
8563
+ console.log(number === undefined ? `Sent edition ${result.edition.id}.` : `Sent edition #${number}. It's out to the list and in the archive.`);
8784
8564
  }
8785
8565
  async function runNewsletterDelete(id, options, newsletterDeleteCommand2) {
8786
8566
  if (!id) {
8787
8567
  throw new Error("Missing edition id. Usage: fluncle admin newsletter delete <id> --yes");
8788
8568
  }
8789
8569
  if (!options.yes) {
8790
- throw new Error("Pass --yes to confirm \u2014 this hard-deletes the edition (draft or sent).");
8570
+ throw new Error("Pass --yes to confirm. This hard-deletes the edition (draft or sent).");
8791
8571
  }
8792
8572
  const result = await newsletterDeleteCommand2(id);
8793
8573
  if (options.json) {
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.107.0"
34
+ "version": "0.109.0"
35
35
  }