fluncle 0.93.0 → 0.95.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 +292 -36
- 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.
|
|
559
|
+
currentVersion = "0.95.0".trim() ? "0.95.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.
|
|
2189
|
+
currentVersion2 = "0.95.0".trim() ? "0.95.0".trim() : "0.1.0";
|
|
2190
2190
|
});
|
|
2191
2191
|
|
|
2192
2192
|
// ../../packages/registry/src/index.ts
|
|
@@ -3144,6 +3144,246 @@ var init_track = __esm(() => {
|
|
|
3144
3144
|
NON_FILE_OPTIONS = ["model", "reasoning"];
|
|
3145
3145
|
});
|
|
3146
3146
|
|
|
3147
|
+
// src/commands/helm.ts
|
|
3148
|
+
var exports_helm = {};
|
|
3149
|
+
__export(exports_helm, {
|
|
3150
|
+
resolveHelmDir: () => resolveHelmDir,
|
|
3151
|
+
helmUninstallCommand: () => helmUninstallCommand,
|
|
3152
|
+
helmOpenCommand: () => helmOpenCommand,
|
|
3153
|
+
helmInstallCommand: () => helmInstallCommand,
|
|
3154
|
+
buildLaunchAgentPlist: () => buildLaunchAgentPlist
|
|
3155
|
+
});
|
|
3156
|
+
import { existsSync, mkdirSync as mkdirSync2, openSync, rmSync as rmSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
3157
|
+
import { homedir as homedir4 } from "node:os";
|
|
3158
|
+
import { dirname as dirname3, join as join4, resolve } from "node:path";
|
|
3159
|
+
function launchAgentPlistPath() {
|
|
3160
|
+
return join4(homedir4(), "Library/LaunchAgents", `${LAUNCH_AGENT_LABEL}.plist`);
|
|
3161
|
+
}
|
|
3162
|
+
function helmLogPath() {
|
|
3163
|
+
return join4(homedir4(), "Library/Logs/fluncle-helm.log");
|
|
3164
|
+
}
|
|
3165
|
+
function walkUpForHelm(start) {
|
|
3166
|
+
let dir = start;
|
|
3167
|
+
for (let depth = 0;depth < 8; depth++) {
|
|
3168
|
+
const candidate = join4(dir, "apps/helm");
|
|
3169
|
+
if (existsSync(join4(candidate, "src/server.ts"))) {
|
|
3170
|
+
return candidate;
|
|
3171
|
+
}
|
|
3172
|
+
const parent = dirname3(dir);
|
|
3173
|
+
if (parent === dir) {
|
|
3174
|
+
break;
|
|
3175
|
+
}
|
|
3176
|
+
dir = parent;
|
|
3177
|
+
}
|
|
3178
|
+
return null;
|
|
3179
|
+
}
|
|
3180
|
+
function persistHelmDir(dir) {
|
|
3181
|
+
try {
|
|
3182
|
+
mkdirSync2(dirname3(HELM_DIR_FILE), { recursive: true });
|
|
3183
|
+
writeFileSync2(HELM_DIR_FILE, `${dir}
|
|
3184
|
+
`, { mode: 384 });
|
|
3185
|
+
} catch {}
|
|
3186
|
+
}
|
|
3187
|
+
function resolveHelmDir() {
|
|
3188
|
+
const fromEnv = process.env.FLUNCLE_HELM_DIR;
|
|
3189
|
+
if (fromEnv) {
|
|
3190
|
+
if (!existsSync(join4(fromEnv, "src/server.ts"))) {
|
|
3191
|
+
throw new CliError2("helm_not_found", `FLUNCLE_HELM_DIR points at ${fromEnv}, but there's no helm there (src/server.ts missing).`);
|
|
3192
|
+
}
|
|
3193
|
+
return resolve(fromEnv);
|
|
3194
|
+
}
|
|
3195
|
+
const found = walkUpForHelm(process.cwd()) ?? walkUpForHelm(import.meta.dir);
|
|
3196
|
+
if (found) {
|
|
3197
|
+
persistHelmDir(found);
|
|
3198
|
+
return found;
|
|
3199
|
+
}
|
|
3200
|
+
try {
|
|
3201
|
+
const remembered = readFileSync2(HELM_DIR_FILE, "utf8").trim();
|
|
3202
|
+
if (remembered && existsSync(join4(remembered, "src/server.ts"))) {
|
|
3203
|
+
return remembered;
|
|
3204
|
+
}
|
|
3205
|
+
} catch {}
|
|
3206
|
+
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.");
|
|
3207
|
+
}
|
|
3208
|
+
function resolveBun() {
|
|
3209
|
+
const bun = Bun.which("bun");
|
|
3210
|
+
if (!bun) {
|
|
3211
|
+
throw new CliError2("no_bun", "No bun on the PATH. The helm daemon runs under bun.");
|
|
3212
|
+
}
|
|
3213
|
+
return bun;
|
|
3214
|
+
}
|
|
3215
|
+
async function daemonHealthy() {
|
|
3216
|
+
try {
|
|
3217
|
+
const response = await fetch(`${HELM_URL}/api/health`, {
|
|
3218
|
+
signal: AbortSignal.timeout(1500)
|
|
3219
|
+
});
|
|
3220
|
+
return response.ok;
|
|
3221
|
+
} catch {
|
|
3222
|
+
return false;
|
|
3223
|
+
}
|
|
3224
|
+
}
|
|
3225
|
+
async function ensureBuilt(helmDir) {
|
|
3226
|
+
if (existsSync(join4(helmDir, "dist/index.html"))) {
|
|
3227
|
+
return;
|
|
3228
|
+
}
|
|
3229
|
+
console.log("The glass isn't built yet. Building it once…");
|
|
3230
|
+
const proc = Bun.spawn([resolveBun(), "run", "build"], {
|
|
3231
|
+
cwd: helmDir,
|
|
3232
|
+
stderr: "inherit",
|
|
3233
|
+
stdin: "ignore",
|
|
3234
|
+
stdout: "inherit"
|
|
3235
|
+
});
|
|
3236
|
+
const exitCode = await proc.exited;
|
|
3237
|
+
if (exitCode !== 0) {
|
|
3238
|
+
throw new CliError2("helm_build_failed", `The glass build failed (exit ${exitCode}).`);
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
function bootDaemon(helmDir) {
|
|
3242
|
+
const logPath = helmLogPath();
|
|
3243
|
+
mkdirSync2(dirname3(logPath), { recursive: true });
|
|
3244
|
+
const logFd = openSync(logPath, "a");
|
|
3245
|
+
const proc = Bun.spawn([resolveBun(), "src/server.ts"], {
|
|
3246
|
+
cwd: helmDir,
|
|
3247
|
+
stderr: logFd,
|
|
3248
|
+
stdin: "ignore",
|
|
3249
|
+
stdout: logFd
|
|
3250
|
+
});
|
|
3251
|
+
proc.unref();
|
|
3252
|
+
}
|
|
3253
|
+
async function waitForHealth(timeoutMs) {
|
|
3254
|
+
const deadline = Date.now() + timeoutMs;
|
|
3255
|
+
while (Date.now() < deadline) {
|
|
3256
|
+
if (await daemonHealthy()) {
|
|
3257
|
+
return true;
|
|
3258
|
+
}
|
|
3259
|
+
await Bun.sleep(250);
|
|
3260
|
+
}
|
|
3261
|
+
return false;
|
|
3262
|
+
}
|
|
3263
|
+
async function openAppWindow() {
|
|
3264
|
+
if (process.platform === "darwin" && chromeInstalled()) {
|
|
3265
|
+
const proc = Bun.spawn(["open", "-na", "Google Chrome", "--args", `--app=${HELM_URL}`], {
|
|
3266
|
+
stderr: "ignore",
|
|
3267
|
+
stdin: "ignore",
|
|
3268
|
+
stdout: "ignore"
|
|
3269
|
+
});
|
|
3270
|
+
if (await proc.exited === 0) {
|
|
3271
|
+
return;
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
await openExternal(HELM_URL);
|
|
3275
|
+
}
|
|
3276
|
+
function chromeInstalled() {
|
|
3277
|
+
return existsSync("/Applications/Google Chrome.app") || existsSync(join4(homedir4(), "Applications/Google Chrome.app"));
|
|
3278
|
+
}
|
|
3279
|
+
async function helmOpenCommand() {
|
|
3280
|
+
if (await daemonHealthy()) {
|
|
3281
|
+
console.log(`The helm holds on :${HELM_PORT}. Opening the window.`);
|
|
3282
|
+
await openAppWindow();
|
|
3283
|
+
return;
|
|
3284
|
+
}
|
|
3285
|
+
const helmDir = resolveHelmDir();
|
|
3286
|
+
await ensureBuilt(helmDir);
|
|
3287
|
+
bootDaemon(helmDir);
|
|
3288
|
+
if (!await waitForHealth(15000)) {
|
|
3289
|
+
throw new CliError2("helm_no_answer", `The daemon didn't answer on :${HELM_PORT}. Its log is at ${helmLogPath()}.`);
|
|
3290
|
+
}
|
|
3291
|
+
console.log(`Helm raised on :${HELM_PORT}. Opening the window.`);
|
|
3292
|
+
await openAppWindow();
|
|
3293
|
+
}
|
|
3294
|
+
function xmlEscape(text) {
|
|
3295
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """);
|
|
3296
|
+
}
|
|
3297
|
+
function buildLaunchAgentPlist(helmDir, bunPath, logPath) {
|
|
3298
|
+
const path2 = `${dirname3(bunPath)}:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin`;
|
|
3299
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
3300
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3301
|
+
<plist version="1.0">
|
|
3302
|
+
<dict>
|
|
3303
|
+
<key>EnvironmentVariables</key>
|
|
3304
|
+
<dict>
|
|
3305
|
+
<key>PATH</key>
|
|
3306
|
+
<string>${xmlEscape(path2)}</string>
|
|
3307
|
+
</dict>
|
|
3308
|
+
<key>KeepAlive</key>
|
|
3309
|
+
<dict>
|
|
3310
|
+
<key>SuccessfulExit</key>
|
|
3311
|
+
<false/>
|
|
3312
|
+
</dict>
|
|
3313
|
+
<key>Label</key>
|
|
3314
|
+
<string>${LAUNCH_AGENT_LABEL}</string>
|
|
3315
|
+
<key>ProgramArguments</key>
|
|
3316
|
+
<array>
|
|
3317
|
+
<string>${xmlEscape(bunPath)}</string>
|
|
3318
|
+
<string>src/server.ts</string>
|
|
3319
|
+
</array>
|
|
3320
|
+
<key>RunAtLoad</key>
|
|
3321
|
+
<true/>
|
|
3322
|
+
<key>StandardErrorPath</key>
|
|
3323
|
+
<string>${xmlEscape(logPath)}</string>
|
|
3324
|
+
<key>StandardOutPath</key>
|
|
3325
|
+
<string>${xmlEscape(logPath)}</string>
|
|
3326
|
+
<key>WorkingDirectory</key>
|
|
3327
|
+
<string>${xmlEscape(helmDir)}</string>
|
|
3328
|
+
</dict>
|
|
3329
|
+
</plist>
|
|
3330
|
+
`;
|
|
3331
|
+
}
|
|
3332
|
+
function currentUid() {
|
|
3333
|
+
const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
|
|
3334
|
+
if (uid === undefined) {
|
|
3335
|
+
throw new CliError2("no_uid", "Couldn't read the user id — launchd needs a gui domain.");
|
|
3336
|
+
}
|
|
3337
|
+
return uid;
|
|
3338
|
+
}
|
|
3339
|
+
async function launchctl(args) {
|
|
3340
|
+
const proc = Bun.spawn(["launchctl", ...args], {
|
|
3341
|
+
stderr: "ignore",
|
|
3342
|
+
stdin: "ignore",
|
|
3343
|
+
stdout: "ignore"
|
|
3344
|
+
});
|
|
3345
|
+
return proc.exited;
|
|
3346
|
+
}
|
|
3347
|
+
async function helmInstallCommand() {
|
|
3348
|
+
if (process.platform !== "darwin") {
|
|
3349
|
+
throw new CliError2("unsupported_platform", "launchd is a macOS thing. Nothing to install here.");
|
|
3350
|
+
}
|
|
3351
|
+
const helmDir = resolveHelmDir();
|
|
3352
|
+
await ensureBuilt(helmDir);
|
|
3353
|
+
const plistPath = launchAgentPlistPath();
|
|
3354
|
+
mkdirSync2(dirname3(plistPath), { recursive: true });
|
|
3355
|
+
mkdirSync2(dirname3(helmLogPath()), { recursive: true });
|
|
3356
|
+
writeFileSync2(plistPath, buildLaunchAgentPlist(helmDir, resolveBun(), helmLogPath()));
|
|
3357
|
+
const uid = currentUid();
|
|
3358
|
+
await launchctl(["bootout", `gui/${uid}/${LAUNCH_AGENT_LABEL}`]);
|
|
3359
|
+
const exitCode = await launchctl(["bootstrap", `gui/${uid}`, plistPath]);
|
|
3360
|
+
if (exitCode !== 0) {
|
|
3361
|
+
throw new CliError2("launchctl_refused", `launchctl bootstrap refused (exit ${exitCode}). The plist is at ${plistPath}.`);
|
|
3362
|
+
}
|
|
3363
|
+
console.log(`LaunchAgent installed (${LAUNCH_AGENT_LABEL}).`);
|
|
3364
|
+
console.log(`The daemon rises at login and holds :${HELM_PORT}. Log: ${helmLogPath()}`);
|
|
3365
|
+
}
|
|
3366
|
+
async function helmUninstallCommand() {
|
|
3367
|
+
if (process.platform !== "darwin") {
|
|
3368
|
+
throw new CliError2("unsupported_platform", "launchd is a macOS thing. Nothing to remove here.");
|
|
3369
|
+
}
|
|
3370
|
+
const plistPath = launchAgentPlistPath();
|
|
3371
|
+
await launchctl(["bootout", `gui/${currentUid()}/${LAUNCH_AGENT_LABEL}`]);
|
|
3372
|
+
if (existsSync(plistPath)) {
|
|
3373
|
+
rmSync2(plistPath);
|
|
3374
|
+
console.log("LaunchAgent removed and the daemon stood down.");
|
|
3375
|
+
return;
|
|
3376
|
+
}
|
|
3377
|
+
console.log("No LaunchAgent on file. Nothing stood down.");
|
|
3378
|
+
}
|
|
3379
|
+
var HELM_PORT = 4190, HELM_URL, LAUNCH_AGENT_LABEL = "com.fluncle.helm", HELM_DIR_FILE;
|
|
3380
|
+
var init_helm = __esm(() => {
|
|
3381
|
+
init_open_external();
|
|
3382
|
+
init_output();
|
|
3383
|
+
HELM_URL = `http://127.0.0.1:${HELM_PORT}`;
|
|
3384
|
+
HELM_DIR_FILE = join4(homedir4(), ".config/fluncle/helm-dir");
|
|
3385
|
+
});
|
|
3386
|
+
|
|
3147
3387
|
// src/commands/add.ts
|
|
3148
3388
|
var exports_add = {};
|
|
3149
3389
|
__export(exports_add, {
|
|
@@ -3675,9 +3915,9 @@ var init_mixtape_youtube2 = __esm(() => {
|
|
|
3675
3915
|
|
|
3676
3916
|
// src/commands/mixtape-set-video.ts
|
|
3677
3917
|
import { randomUUID } from "node:crypto";
|
|
3678
|
-
import { existsSync, rmSync as
|
|
3918
|
+
import { existsSync as existsSync2, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
|
|
3679
3919
|
import { tmpdir } from "node:os";
|
|
3680
|
-
import { join as
|
|
3920
|
+
import { join as join5 } from "node:path";
|
|
3681
3921
|
function planMultipart(contentLength, partSize = DEFAULT_PART_SIZE) {
|
|
3682
3922
|
if (!Number.isInteger(contentLength) || contentLength <= 0) {
|
|
3683
3923
|
throw new CliError2("invalid_size", `The rendition size must be a positive integer (got ${contentLength})`);
|
|
@@ -3732,11 +3972,11 @@ function renditionFfmpegArgs(inputPath, outputPath) {
|
|
|
3732
3972
|
];
|
|
3733
3973
|
}
|
|
3734
3974
|
async function uploadRenditionMultipart(masterPath, presignPath, onProgress = () => {}) {
|
|
3735
|
-
if (!
|
|
3975
|
+
if (!existsSync2(masterPath)) {
|
|
3736
3976
|
throw new CliError2("file_not_found", `Set-video master not found: ${masterPath}`);
|
|
3737
3977
|
}
|
|
3738
3978
|
await assertFfmpeg();
|
|
3739
|
-
const renditionPath =
|
|
3979
|
+
const renditionPath = join5(tmpdir(), `fluncle-set-${randomUUID()}.mp4`);
|
|
3740
3980
|
onProgress("Set video: deriving the 1080p faststart rendition (ffmpeg)…");
|
|
3741
3981
|
await deriveRendition(masterPath, renditionPath);
|
|
3742
3982
|
try {
|
|
@@ -3765,7 +4005,7 @@ async function uploadRenditionMultipart(masterPath, presignPath, onProgress = ()
|
|
|
3765
4005
|
}
|
|
3766
4006
|
return { key: presign.key, url: `${FOUND_BASE2}/${presign.key}` };
|
|
3767
4007
|
} finally {
|
|
3768
|
-
|
|
4008
|
+
rmSync3(renditionPath, { force: true });
|
|
3769
4009
|
}
|
|
3770
4010
|
}
|
|
3771
4011
|
async function putPart(url, path2, part, onProgress = () => {}) {
|
|
@@ -3782,7 +4022,7 @@ async function putPart(url, path2, part, onProgress = () => {}) {
|
|
|
3782
4022
|
}
|
|
3783
4023
|
const backoffMs = 500 * 2 ** (attempt - 1);
|
|
3784
4024
|
onProgress(`Set video: part ${part.partNumber} dropped — retry ${attempt}/${MAX_PART_ATTEMPTS - 1} in ${backoffMs}ms…`);
|
|
3785
|
-
await new Promise((
|
|
4025
|
+
await new Promise((resolve2) => setTimeout(resolve2, backoffMs));
|
|
3786
4026
|
}
|
|
3787
4027
|
}
|
|
3788
4028
|
}
|
|
@@ -3863,7 +4103,7 @@ __export(exports_recordings, {
|
|
|
3863
4103
|
recordingDeleteCommand: () => recordingDeleteCommand,
|
|
3864
4104
|
recordingCreateCommand: () => recordingCreateCommand
|
|
3865
4105
|
});
|
|
3866
|
-
import { existsSync as
|
|
4106
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
3867
4107
|
async function recordingGet(id) {
|
|
3868
4108
|
const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
|
|
3869
4109
|
return response.recording;
|
|
@@ -3932,7 +4172,7 @@ async function recordingCreateCommand(options = {}) {
|
|
|
3932
4172
|
if (!options.video) {
|
|
3933
4173
|
throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
|
|
3934
4174
|
}
|
|
3935
|
-
if (!
|
|
4175
|
+
if (!existsSync3(options.video)) {
|
|
3936
4176
|
throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
|
|
3937
4177
|
}
|
|
3938
4178
|
const created = await adminApiPost("/api/admin/recordings", {
|
|
@@ -3968,11 +4208,11 @@ async function recordingUpdateCommand(id, options = {}) {
|
|
|
3968
4208
|
body.parentId = options.parentId;
|
|
3969
4209
|
}
|
|
3970
4210
|
if (options.tracklistFile !== undefined) {
|
|
3971
|
-
if (!
|
|
4211
|
+
if (!existsSync3(options.tracklistFile)) {
|
|
3972
4212
|
throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
|
|
3973
4213
|
}
|
|
3974
4214
|
try {
|
|
3975
|
-
body.tracklistJson = JSON.parse(
|
|
4215
|
+
body.tracklistJson = JSON.parse(readFileSync3(options.tracklistFile, "utf8"));
|
|
3976
4216
|
} catch {
|
|
3977
4217
|
throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
|
|
3978
4218
|
}
|
|
@@ -4017,12 +4257,12 @@ async function recordingReplaceCuesCommand(id, options = {}) {
|
|
|
4017
4257
|
if (!options.cuesFile) {
|
|
4018
4258
|
throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
|
|
4019
4259
|
}
|
|
4020
|
-
if (!
|
|
4260
|
+
if (!existsSync3(options.cuesFile)) {
|
|
4021
4261
|
throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
|
|
4022
4262
|
}
|
|
4023
4263
|
let cues;
|
|
4024
4264
|
try {
|
|
4025
|
-
cues = JSON.parse(
|
|
4265
|
+
cues = JSON.parse(readFileSync3(options.cuesFile, "utf8"));
|
|
4026
4266
|
} catch {
|
|
4027
4267
|
throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
|
|
4028
4268
|
}
|
|
@@ -4057,9 +4297,9 @@ __export(exports_clips, {
|
|
|
4057
4297
|
CLIP_AUDIO_BITRATE: () => CLIP_AUDIO_BITRATE
|
|
4058
4298
|
});
|
|
4059
4299
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
4060
|
-
import { rmSync as
|
|
4300
|
+
import { rmSync as rmSync4, statSync as statSync4 } from "node:fs";
|
|
4061
4301
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
4062
|
-
import { join as
|
|
4302
|
+
import { join as join6 } from "node:path";
|
|
4063
4303
|
function clipFootageKey(clipId) {
|
|
4064
4304
|
return `${clipId}/footage.mp4`;
|
|
4065
4305
|
}
|
|
@@ -4142,7 +4382,7 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
|
|
|
4142
4382
|
}
|
|
4143
4383
|
const source = await resolveClipSource(clip);
|
|
4144
4384
|
await assertFfmpeg2();
|
|
4145
|
-
const outputPath =
|
|
4385
|
+
const outputPath = join6(tmpdir2(), `fluncle-clip-${randomUUID2()}.mp4`);
|
|
4146
4386
|
try {
|
|
4147
4387
|
onProgress(`Clip ${clipId}: cutting [${clip.inMs}–${clip.outMs}ms]…`);
|
|
4148
4388
|
await runClipCut({
|
|
@@ -4168,7 +4408,7 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
|
|
|
4168
4408
|
url: `${FOUND_BASE3}/${presign.key}`
|
|
4169
4409
|
};
|
|
4170
4410
|
} finally {
|
|
4171
|
-
|
|
4411
|
+
rmSync4(outputPath, { force: true });
|
|
4172
4412
|
}
|
|
4173
4413
|
}
|
|
4174
4414
|
async function putClip(url, contentType, path2) {
|
|
@@ -4226,7 +4466,7 @@ __export(exports_recordings2, {
|
|
|
4226
4466
|
recordingDeleteCommand: () => recordingDeleteCommand2,
|
|
4227
4467
|
recordingCreateCommand: () => recordingCreateCommand2
|
|
4228
4468
|
});
|
|
4229
|
-
import { existsSync as
|
|
4469
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
|
|
4230
4470
|
async function recordingGet2(id) {
|
|
4231
4471
|
const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
|
|
4232
4472
|
return response.recording;
|
|
@@ -4295,7 +4535,7 @@ async function recordingCreateCommand2(options = {}) {
|
|
|
4295
4535
|
if (!options.video) {
|
|
4296
4536
|
throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
|
|
4297
4537
|
}
|
|
4298
|
-
if (!
|
|
4538
|
+
if (!existsSync4(options.video)) {
|
|
4299
4539
|
throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
|
|
4300
4540
|
}
|
|
4301
4541
|
const created = await adminApiPost("/api/admin/recordings", {
|
|
@@ -4331,11 +4571,11 @@ async function recordingUpdateCommand2(id, options = {}) {
|
|
|
4331
4571
|
body.parentId = options.parentId;
|
|
4332
4572
|
}
|
|
4333
4573
|
if (options.tracklistFile !== undefined) {
|
|
4334
|
-
if (!
|
|
4574
|
+
if (!existsSync4(options.tracklistFile)) {
|
|
4335
4575
|
throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
|
|
4336
4576
|
}
|
|
4337
4577
|
try {
|
|
4338
|
-
body.tracklistJson = JSON.parse(
|
|
4578
|
+
body.tracklistJson = JSON.parse(readFileSync4(options.tracklistFile, "utf8"));
|
|
4339
4579
|
} catch {
|
|
4340
4580
|
throw new CliError2("invalid_tracklist", `Tracklist file is not valid JSON: ${options.tracklistFile}`);
|
|
4341
4581
|
}
|
|
@@ -4380,12 +4620,12 @@ async function recordingReplaceCuesCommand2(id, options = {}) {
|
|
|
4380
4620
|
if (!options.cuesFile) {
|
|
4381
4621
|
throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
|
|
4382
4622
|
}
|
|
4383
|
-
if (!
|
|
4623
|
+
if (!existsSync4(options.cuesFile)) {
|
|
4384
4624
|
throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
|
|
4385
4625
|
}
|
|
4386
4626
|
let cues;
|
|
4387
4627
|
try {
|
|
4388
|
-
cues = JSON.parse(
|
|
4628
|
+
cues = JSON.parse(readFileSync4(options.cuesFile, "utf8"));
|
|
4389
4629
|
} catch {
|
|
4390
4630
|
throw new CliError2("invalid_cues", `Cues file is not valid JSON: ${options.cuesFile}`);
|
|
4391
4631
|
}
|
|
@@ -4411,7 +4651,7 @@ __export(exports_newsletter, {
|
|
|
4411
4651
|
newsletterDraftCommand: () => newsletterDraftCommand,
|
|
4412
4652
|
newsletterDeleteCommand: () => newsletterDeleteCommand
|
|
4413
4653
|
});
|
|
4414
|
-
import { existsSync as
|
|
4654
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
|
|
4415
4655
|
function buildBody2(options, { requireContent }) {
|
|
4416
4656
|
const body = {};
|
|
4417
4657
|
if (options.contentFile !== undefined) {
|
|
@@ -4431,10 +4671,10 @@ function buildBody2(options, { requireContent }) {
|
|
|
4431
4671
|
return body;
|
|
4432
4672
|
}
|
|
4433
4673
|
function readContentFile(filePath) {
|
|
4434
|
-
if (!
|
|
4674
|
+
if (!existsSync5(filePath)) {
|
|
4435
4675
|
throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
|
|
4436
4676
|
}
|
|
4437
|
-
const text =
|
|
4677
|
+
const text = readFileSync5(filePath, "utf-8");
|
|
4438
4678
|
try {
|
|
4439
4679
|
return JSON.parse(text);
|
|
4440
4680
|
} catch (error) {
|
|
@@ -4823,7 +5063,7 @@ async function selectWithKeyboard2(items, options) {
|
|
|
4823
5063
|
let renderedLines = 0;
|
|
4824
5064
|
let done = false;
|
|
4825
5065
|
const wasRaw = stdin.isRaw === true;
|
|
4826
|
-
return await new Promise((
|
|
5066
|
+
return await new Promise((resolve2) => {
|
|
4827
5067
|
function cleanup() {
|
|
4828
5068
|
if (done) {
|
|
4829
5069
|
return;
|
|
@@ -4838,14 +5078,14 @@ async function selectWithKeyboard2(items, options) {
|
|
|
4838
5078
|
cleanup();
|
|
4839
5079
|
stdout.write(renderedLines > 0 ? `
|
|
4840
5080
|
` : "");
|
|
4841
|
-
|
|
5081
|
+
resolve2(item);
|
|
4842
5082
|
}
|
|
4843
5083
|
function cancel() {
|
|
4844
5084
|
cleanup();
|
|
4845
5085
|
clearRendered2(stdout, renderedLines);
|
|
4846
5086
|
stdout.write(`Cancelled.
|
|
4847
5087
|
`);
|
|
4848
|
-
|
|
5088
|
+
resolve2(undefined);
|
|
4849
5089
|
}
|
|
4850
5090
|
function render() {
|
|
4851
5091
|
clearRendered2(stdout, renderedLines);
|
|
@@ -4901,7 +5141,7 @@ async function paginateWithKeyboard(options) {
|
|
|
4901
5141
|
let busy = false;
|
|
4902
5142
|
let done = false;
|
|
4903
5143
|
const wasRaw = stdin.isRaw === true;
|
|
4904
|
-
return await new Promise((
|
|
5144
|
+
return await new Promise((resolve2) => {
|
|
4905
5145
|
function cleanup() {
|
|
4906
5146
|
if (done) {
|
|
4907
5147
|
return;
|
|
@@ -4916,7 +5156,7 @@ async function paginateWithKeyboard(options) {
|
|
|
4916
5156
|
cleanup();
|
|
4917
5157
|
stdout.write(`
|
|
4918
5158
|
`);
|
|
4919
|
-
|
|
5159
|
+
resolve2();
|
|
4920
5160
|
}
|
|
4921
5161
|
function render() {
|
|
4922
5162
|
clearRendered2(stdout, renderedLines);
|
|
@@ -5069,7 +5309,7 @@ var init_format2 = __esm(() => {
|
|
|
5069
5309
|
});
|
|
5070
5310
|
|
|
5071
5311
|
// src/cli.ts
|
|
5072
|
-
import { existsSync as
|
|
5312
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
5073
5313
|
import path2 from "path";
|
|
5074
5314
|
|
|
5075
5315
|
// ../../node_modules/commander/lib/error.js
|
|
@@ -7215,6 +7455,7 @@ ${fluncleAsciiLogo}
|
|
|
7215
7455
|
addMetaCommands(program2);
|
|
7216
7456
|
addTrackCommands(program2);
|
|
7217
7457
|
addAdminCommands(program2);
|
|
7458
|
+
addHelmCommands(program2);
|
|
7218
7459
|
return program2;
|
|
7219
7460
|
}
|
|
7220
7461
|
async function main(args = process.argv.slice(2)) {
|
|
@@ -7313,6 +7554,21 @@ function addTrackCommands(program2) {
|
|
|
7313
7554
|
await runTrackGet(idOrLogId, options, trackGetCommand2);
|
|
7314
7555
|
});
|
|
7315
7556
|
}
|
|
7557
|
+
function addHelmCommands(program2) {
|
|
7558
|
+
const helm = configureCommand(program2.command("helm", { hidden: true }).description("Fluncle's Helm \u2014 the operator's mission control"));
|
|
7559
|
+
helm.action(async () => {
|
|
7560
|
+
const { helmOpenCommand: helmOpenCommand2 } = await Promise.resolve().then(() => (init_helm(), exports_helm));
|
|
7561
|
+
await helmOpenCommand2();
|
|
7562
|
+
});
|
|
7563
|
+
helm.command("install").description("Install the launchd LaunchAgent (the daemon rises at login)").action(async () => {
|
|
7564
|
+
const { helmInstallCommand: helmInstallCommand2 } = await Promise.resolve().then(() => (init_helm(), exports_helm));
|
|
7565
|
+
await helmInstallCommand2();
|
|
7566
|
+
});
|
|
7567
|
+
helm.command("uninstall").description("Remove the LaunchAgent and stand the daemon down").action(async () => {
|
|
7568
|
+
const { helmUninstallCommand: helmUninstallCommand2 } = await Promise.resolve().then(() => (init_helm(), exports_helm));
|
|
7569
|
+
await helmUninstallCommand2();
|
|
7570
|
+
});
|
|
7571
|
+
}
|
|
7316
7572
|
function addAdminCommands(program2) {
|
|
7317
7573
|
const admin = configureCommand(program2.command("admin", { hidden: true }).description("Operator commands"));
|
|
7318
7574
|
admin.action(() => {
|
|
@@ -7590,7 +7846,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
|
|
|
7590
7846
|
console.log(` mime: ${result.mime}`);
|
|
7591
7847
|
}
|
|
7592
7848
|
async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
|
|
7593
|
-
const script = options.scriptFile ?
|
|
7849
|
+
const script = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
|
|
7594
7850
|
if (!idOrLogId || !script || !script.trim()) {
|
|
7595
7851
|
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]");
|
|
7596
7852
|
}
|
|
@@ -7646,7 +7902,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
|
|
|
7646
7902
|
}
|
|
7647
7903
|
}
|
|
7648
7904
|
async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
|
|
7649
|
-
const note = options.scriptFile ?
|
|
7905
|
+
const note = options.scriptFile ? readFileSync6(options.scriptFile, "utf8") : options.script;
|
|
7650
7906
|
if (!idOrLogId || !note || !note.trim()) {
|
|
7651
7907
|
throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--json]");
|
|
7652
7908
|
}
|
|
@@ -7792,7 +8048,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
|
|
|
7792
8048
|
return;
|
|
7793
8049
|
}
|
|
7794
8050
|
const candidate = path2.join(dir, name);
|
|
7795
|
-
return
|
|
8051
|
+
return existsSync6(candidate) ? candidate : undefined;
|
|
7796
8052
|
};
|
|
7797
8053
|
const resolveFile = (explicit, name) => {
|
|
7798
8054
|
if (explicit) {
|
package/package.json
CHANGED