fluncle 0.93.0 → 0.94.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 +263 -29
  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.93.0".trim() ? "0.93.0".trim() : "0.1.0";
559
+ currentVersion = "0.94.0".trim() ? "0.94.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.93.0".trim() ? "0.93.0".trim() : "0.1.0";
2189
+ currentVersion2 = "0.94.0".trim() ? "0.94.0".trim() : "0.1.0";
2190
2190
  });
2191
2191
 
2192
2192
  // ../../packages/registry/src/index.ts
@@ -3144,6 +3144,224 @@ 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 } 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 resolveHelmDir() {
3166
+ const fromEnv = process.env.FLUNCLE_HELM_DIR;
3167
+ if (fromEnv) {
3168
+ if (!existsSync(join4(fromEnv, "src/server.ts"))) {
3169
+ throw new CliError2("helm_not_found", `FLUNCLE_HELM_DIR points at ${fromEnv}, but there's no helm there (src/server.ts missing).`);
3170
+ }
3171
+ return resolve(fromEnv);
3172
+ }
3173
+ let dir = import.meta.dir;
3174
+ for (let depth = 0;depth < 8; depth++) {
3175
+ const candidate = join4(dir, "apps/helm");
3176
+ if (existsSync(join4(candidate, "src/server.ts"))) {
3177
+ return candidate;
3178
+ }
3179
+ const parent = dirname3(dir);
3180
+ if (parent === dir) {
3181
+ break;
3182
+ }
3183
+ dir = parent;
3184
+ }
3185
+ throw new CliError2("helm_not_found", "No helm aboard. Run this from the fluncle repo, or set FLUNCLE_HELM_DIR to the apps/helm checkout.");
3186
+ }
3187
+ function resolveBun() {
3188
+ const bun = Bun.which("bun");
3189
+ if (!bun) {
3190
+ throw new CliError2("no_bun", "No bun on the PATH. The helm daemon runs under bun.");
3191
+ }
3192
+ return bun;
3193
+ }
3194
+ async function daemonHealthy() {
3195
+ try {
3196
+ const response = await fetch(`${HELM_URL}/api/health`, {
3197
+ signal: AbortSignal.timeout(1500)
3198
+ });
3199
+ return response.ok;
3200
+ } catch {
3201
+ return false;
3202
+ }
3203
+ }
3204
+ async function ensureBuilt(helmDir) {
3205
+ if (existsSync(join4(helmDir, "dist/index.html"))) {
3206
+ return;
3207
+ }
3208
+ console.log("The glass isn't built yet. Building it once…");
3209
+ const proc = Bun.spawn([resolveBun(), "run", "build"], {
3210
+ cwd: helmDir,
3211
+ stderr: "inherit",
3212
+ stdin: "ignore",
3213
+ stdout: "inherit"
3214
+ });
3215
+ const exitCode = await proc.exited;
3216
+ if (exitCode !== 0) {
3217
+ throw new CliError2("helm_build_failed", `The glass build failed (exit ${exitCode}).`);
3218
+ }
3219
+ }
3220
+ function bootDaemon(helmDir) {
3221
+ const logPath = helmLogPath();
3222
+ mkdirSync2(dirname3(logPath), { recursive: true });
3223
+ const logFd = openSync(logPath, "a");
3224
+ const proc = Bun.spawn([resolveBun(), "src/server.ts"], {
3225
+ cwd: helmDir,
3226
+ stderr: logFd,
3227
+ stdin: "ignore",
3228
+ stdout: logFd
3229
+ });
3230
+ proc.unref();
3231
+ }
3232
+ async function waitForHealth(timeoutMs) {
3233
+ const deadline = Date.now() + timeoutMs;
3234
+ while (Date.now() < deadline) {
3235
+ if (await daemonHealthy()) {
3236
+ return true;
3237
+ }
3238
+ await Bun.sleep(250);
3239
+ }
3240
+ return false;
3241
+ }
3242
+ async function openAppWindow() {
3243
+ if (process.platform === "darwin" && chromeInstalled()) {
3244
+ const proc = Bun.spawn(["open", "-na", "Google Chrome", "--args", `--app=${HELM_URL}`], {
3245
+ stderr: "ignore",
3246
+ stdin: "ignore",
3247
+ stdout: "ignore"
3248
+ });
3249
+ if (await proc.exited === 0) {
3250
+ return;
3251
+ }
3252
+ }
3253
+ await openExternal(HELM_URL);
3254
+ }
3255
+ function chromeInstalled() {
3256
+ return existsSync("/Applications/Google Chrome.app") || existsSync(join4(homedir4(), "Applications/Google Chrome.app"));
3257
+ }
3258
+ async function helmOpenCommand() {
3259
+ if (await daemonHealthy()) {
3260
+ console.log(`The helm holds on :${HELM_PORT}. Opening the window.`);
3261
+ await openAppWindow();
3262
+ return;
3263
+ }
3264
+ const helmDir = resolveHelmDir();
3265
+ await ensureBuilt(helmDir);
3266
+ bootDaemon(helmDir);
3267
+ if (!await waitForHealth(15000)) {
3268
+ throw new CliError2("helm_no_answer", `The daemon didn't answer on :${HELM_PORT}. Its log is at ${helmLogPath()}.`);
3269
+ }
3270
+ console.log(`Helm raised on :${HELM_PORT}. Opening the window.`);
3271
+ await openAppWindow();
3272
+ }
3273
+ function xmlEscape(text) {
3274
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3275
+ }
3276
+ function buildLaunchAgentPlist(helmDir, bunPath, logPath) {
3277
+ const path2 = `${dirname3(bunPath)}:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin`;
3278
+ return `<?xml version="1.0" encoding="UTF-8"?>
3279
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3280
+ <plist version="1.0">
3281
+ <dict>
3282
+ <key>EnvironmentVariables</key>
3283
+ <dict>
3284
+ <key>PATH</key>
3285
+ <string>${xmlEscape(path2)}</string>
3286
+ </dict>
3287
+ <key>KeepAlive</key>
3288
+ <dict>
3289
+ <key>SuccessfulExit</key>
3290
+ <false/>
3291
+ </dict>
3292
+ <key>Label</key>
3293
+ <string>${LAUNCH_AGENT_LABEL}</string>
3294
+ <key>ProgramArguments</key>
3295
+ <array>
3296
+ <string>${xmlEscape(bunPath)}</string>
3297
+ <string>src/server.ts</string>
3298
+ </array>
3299
+ <key>RunAtLoad</key>
3300
+ <true/>
3301
+ <key>StandardErrorPath</key>
3302
+ <string>${xmlEscape(logPath)}</string>
3303
+ <key>StandardOutPath</key>
3304
+ <string>${xmlEscape(logPath)}</string>
3305
+ <key>WorkingDirectory</key>
3306
+ <string>${xmlEscape(helmDir)}</string>
3307
+ </dict>
3308
+ </plist>
3309
+ `;
3310
+ }
3311
+ function currentUid() {
3312
+ const uid = typeof process.getuid === "function" ? process.getuid() : undefined;
3313
+ if (uid === undefined) {
3314
+ throw new CliError2("no_uid", "Couldn't read the user id — launchd needs a gui domain.");
3315
+ }
3316
+ return uid;
3317
+ }
3318
+ async function launchctl(args) {
3319
+ const proc = Bun.spawn(["launchctl", ...args], {
3320
+ stderr: "ignore",
3321
+ stdin: "ignore",
3322
+ stdout: "ignore"
3323
+ });
3324
+ return proc.exited;
3325
+ }
3326
+ async function helmInstallCommand() {
3327
+ if (process.platform !== "darwin") {
3328
+ throw new CliError2("unsupported_platform", "launchd is a macOS thing. Nothing to install here.");
3329
+ }
3330
+ const helmDir = resolveHelmDir();
3331
+ await ensureBuilt(helmDir);
3332
+ const plistPath = launchAgentPlistPath();
3333
+ mkdirSync2(dirname3(plistPath), { recursive: true });
3334
+ mkdirSync2(dirname3(helmLogPath()), { recursive: true });
3335
+ writeFileSync2(plistPath, buildLaunchAgentPlist(helmDir, resolveBun(), helmLogPath()));
3336
+ const uid = currentUid();
3337
+ await launchctl(["bootout", `gui/${uid}/${LAUNCH_AGENT_LABEL}`]);
3338
+ const exitCode = await launchctl(["bootstrap", `gui/${uid}`, plistPath]);
3339
+ if (exitCode !== 0) {
3340
+ throw new CliError2("launchctl_refused", `launchctl bootstrap refused (exit ${exitCode}). The plist is at ${plistPath}.`);
3341
+ }
3342
+ console.log(`LaunchAgent installed (${LAUNCH_AGENT_LABEL}).`);
3343
+ console.log(`The daemon rises at login and holds :${HELM_PORT}. Log: ${helmLogPath()}`);
3344
+ }
3345
+ async function helmUninstallCommand() {
3346
+ if (process.platform !== "darwin") {
3347
+ throw new CliError2("unsupported_platform", "launchd is a macOS thing. Nothing to remove here.");
3348
+ }
3349
+ const plistPath = launchAgentPlistPath();
3350
+ await launchctl(["bootout", `gui/${currentUid()}/${LAUNCH_AGENT_LABEL}`]);
3351
+ if (existsSync(plistPath)) {
3352
+ rmSync2(plistPath);
3353
+ console.log("LaunchAgent removed and the daemon stood down.");
3354
+ return;
3355
+ }
3356
+ console.log("No LaunchAgent on file. Nothing stood down.");
3357
+ }
3358
+ var HELM_PORT = 4190, HELM_URL, LAUNCH_AGENT_LABEL = "com.fluncle.helm";
3359
+ var init_helm = __esm(() => {
3360
+ init_open_external();
3361
+ init_output();
3362
+ HELM_URL = `http://127.0.0.1:${HELM_PORT}`;
3363
+ });
3364
+
3147
3365
  // src/commands/add.ts
3148
3366
  var exports_add = {};
3149
3367
  __export(exports_add, {
@@ -3675,9 +3893,9 @@ var init_mixtape_youtube2 = __esm(() => {
3675
3893
 
3676
3894
  // src/commands/mixtape-set-video.ts
3677
3895
  import { randomUUID } from "node:crypto";
3678
- import { existsSync, rmSync as rmSync2, statSync as statSync3 } from "node:fs";
3896
+ import { existsSync as existsSync2, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
3679
3897
  import { tmpdir } from "node:os";
3680
- import { join as join4 } from "node:path";
3898
+ import { join as join5 } from "node:path";
3681
3899
  function planMultipart(contentLength, partSize = DEFAULT_PART_SIZE) {
3682
3900
  if (!Number.isInteger(contentLength) || contentLength <= 0) {
3683
3901
  throw new CliError2("invalid_size", `The rendition size must be a positive integer (got ${contentLength})`);
@@ -3732,11 +3950,11 @@ function renditionFfmpegArgs(inputPath, outputPath) {
3732
3950
  ];
3733
3951
  }
3734
3952
  async function uploadRenditionMultipart(masterPath, presignPath, onProgress = () => {}) {
3735
- if (!existsSync(masterPath)) {
3953
+ if (!existsSync2(masterPath)) {
3736
3954
  throw new CliError2("file_not_found", `Set-video master not found: ${masterPath}`);
3737
3955
  }
3738
3956
  await assertFfmpeg();
3739
- const renditionPath = join4(tmpdir(), `fluncle-set-${randomUUID()}.mp4`);
3957
+ const renditionPath = join5(tmpdir(), `fluncle-set-${randomUUID()}.mp4`);
3740
3958
  onProgress("Set video: deriving the 1080p faststart rendition (ffmpeg)…");
3741
3959
  await deriveRendition(masterPath, renditionPath);
3742
3960
  try {
@@ -3765,7 +3983,7 @@ async function uploadRenditionMultipart(masterPath, presignPath, onProgress = ()
3765
3983
  }
3766
3984
  return { key: presign.key, url: `${FOUND_BASE2}/${presign.key}` };
3767
3985
  } finally {
3768
- rmSync2(renditionPath, { force: true });
3986
+ rmSync3(renditionPath, { force: true });
3769
3987
  }
3770
3988
  }
3771
3989
  async function putPart(url, path2, part, onProgress = () => {}) {
@@ -3782,7 +4000,7 @@ async function putPart(url, path2, part, onProgress = () => {}) {
3782
4000
  }
3783
4001
  const backoffMs = 500 * 2 ** (attempt - 1);
3784
4002
  onProgress(`Set video: part ${part.partNumber} dropped — retry ${attempt}/${MAX_PART_ATTEMPTS - 1} in ${backoffMs}ms…`);
3785
- await new Promise((resolve) => setTimeout(resolve, backoffMs));
4003
+ await new Promise((resolve2) => setTimeout(resolve2, backoffMs));
3786
4004
  }
3787
4005
  }
3788
4006
  }
@@ -3863,7 +4081,7 @@ __export(exports_recordings, {
3863
4081
  recordingDeleteCommand: () => recordingDeleteCommand,
3864
4082
  recordingCreateCommand: () => recordingCreateCommand
3865
4083
  });
3866
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
4084
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
3867
4085
  async function recordingGet(id) {
3868
4086
  const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
3869
4087
  return response.recording;
@@ -3932,7 +4150,7 @@ async function recordingCreateCommand(options = {}) {
3932
4150
  if (!options.video) {
3933
4151
  throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
3934
4152
  }
3935
- if (!existsSync2(options.video)) {
4153
+ if (!existsSync3(options.video)) {
3936
4154
  throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
3937
4155
  }
3938
4156
  const created = await adminApiPost("/api/admin/recordings", {
@@ -3968,7 +4186,7 @@ async function recordingUpdateCommand(id, options = {}) {
3968
4186
  body.parentId = options.parentId;
3969
4187
  }
3970
4188
  if (options.tracklistFile !== undefined) {
3971
- if (!existsSync2(options.tracklistFile)) {
4189
+ if (!existsSync3(options.tracklistFile)) {
3972
4190
  throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
3973
4191
  }
3974
4192
  try {
@@ -4017,7 +4235,7 @@ async function recordingReplaceCuesCommand(id, options = {}) {
4017
4235
  if (!options.cuesFile) {
4018
4236
  throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
4019
4237
  }
4020
- if (!existsSync2(options.cuesFile)) {
4238
+ if (!existsSync3(options.cuesFile)) {
4021
4239
  throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
4022
4240
  }
4023
4241
  let cues;
@@ -4057,9 +4275,9 @@ __export(exports_clips, {
4057
4275
  CLIP_AUDIO_BITRATE: () => CLIP_AUDIO_BITRATE
4058
4276
  });
4059
4277
  import { randomUUID as randomUUID2 } from "node:crypto";
4060
- import { rmSync as rmSync3, statSync as statSync4 } from "node:fs";
4278
+ import { rmSync as rmSync4, statSync as statSync4 } from "node:fs";
4061
4279
  import { tmpdir as tmpdir2 } from "node:os";
4062
- import { join as join5 } from "node:path";
4280
+ import { join as join6 } from "node:path";
4063
4281
  function clipFootageKey(clipId) {
4064
4282
  return `${clipId}/footage.mp4`;
4065
4283
  }
@@ -4142,7 +4360,7 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
4142
4360
  }
4143
4361
  const source = await resolveClipSource(clip);
4144
4362
  await assertFfmpeg2();
4145
- const outputPath = join5(tmpdir2(), `fluncle-clip-${randomUUID2()}.mp4`);
4363
+ const outputPath = join6(tmpdir2(), `fluncle-clip-${randomUUID2()}.mp4`);
4146
4364
  try {
4147
4365
  onProgress(`Clip ${clipId}: cutting [${clip.inMs}–${clip.outMs}ms]…`);
4148
4366
  await runClipCut({
@@ -4168,7 +4386,7 @@ async function clipCutCommand(clipId, onProgress = () => {}) {
4168
4386
  url: `${FOUND_BASE3}/${presign.key}`
4169
4387
  };
4170
4388
  } finally {
4171
- rmSync3(outputPath, { force: true });
4389
+ rmSync4(outputPath, { force: true });
4172
4390
  }
4173
4391
  }
4174
4392
  async function putClip(url, contentType, path2) {
@@ -4226,7 +4444,7 @@ __export(exports_recordings2, {
4226
4444
  recordingDeleteCommand: () => recordingDeleteCommand2,
4227
4445
  recordingCreateCommand: () => recordingCreateCommand2
4228
4446
  });
4229
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
4447
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "node:fs";
4230
4448
  async function recordingGet2(id) {
4231
4449
  const response = await adminApiGet(`/api/admin/recordings/${encodeURIComponent(id)}`);
4232
4450
  return response.recording;
@@ -4295,7 +4513,7 @@ async function recordingCreateCommand2(options = {}) {
4295
4513
  if (!options.video) {
4296
4514
  throw new CliError2("missing_video", "A recording needs a --video <file> to stage");
4297
4515
  }
4298
- if (!existsSync3(options.video)) {
4516
+ if (!existsSync4(options.video)) {
4299
4517
  throw new CliError2("file_not_found", `Set-video master not found: ${options.video}`);
4300
4518
  }
4301
4519
  const created = await adminApiPost("/api/admin/recordings", {
@@ -4331,7 +4549,7 @@ async function recordingUpdateCommand2(id, options = {}) {
4331
4549
  body.parentId = options.parentId;
4332
4550
  }
4333
4551
  if (options.tracklistFile !== undefined) {
4334
- if (!existsSync3(options.tracklistFile)) {
4552
+ if (!existsSync4(options.tracklistFile)) {
4335
4553
  throw new CliError2("file_not_found", `Tracklist file not found: ${options.tracklistFile}`);
4336
4554
  }
4337
4555
  try {
@@ -4380,7 +4598,7 @@ async function recordingReplaceCuesCommand2(id, options = {}) {
4380
4598
  if (!options.cuesFile) {
4381
4599
  throw new CliError2("missing_cues_file", "replace-cues needs a --cues-file <file> (the ordered cue array)");
4382
4600
  }
4383
- if (!existsSync3(options.cuesFile)) {
4601
+ if (!existsSync4(options.cuesFile)) {
4384
4602
  throw new CliError2("file_not_found", `Cues file not found: ${options.cuesFile}`);
4385
4603
  }
4386
4604
  let cues;
@@ -4411,7 +4629,7 @@ __export(exports_newsletter, {
4411
4629
  newsletterDraftCommand: () => newsletterDraftCommand,
4412
4630
  newsletterDeleteCommand: () => newsletterDeleteCommand
4413
4631
  });
4414
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "node:fs";
4632
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "node:fs";
4415
4633
  function buildBody2(options, { requireContent }) {
4416
4634
  const body = {};
4417
4635
  if (options.contentFile !== undefined) {
@@ -4431,7 +4649,7 @@ function buildBody2(options, { requireContent }) {
4431
4649
  return body;
4432
4650
  }
4433
4651
  function readContentFile(filePath) {
4434
- if (!existsSync4(filePath)) {
4652
+ if (!existsSync5(filePath)) {
4435
4653
  throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
4436
4654
  }
4437
4655
  const text = readFileSync4(filePath, "utf-8");
@@ -4823,7 +5041,7 @@ async function selectWithKeyboard2(items, options) {
4823
5041
  let renderedLines = 0;
4824
5042
  let done = false;
4825
5043
  const wasRaw = stdin.isRaw === true;
4826
- return await new Promise((resolve) => {
5044
+ return await new Promise((resolve2) => {
4827
5045
  function cleanup() {
4828
5046
  if (done) {
4829
5047
  return;
@@ -4838,14 +5056,14 @@ async function selectWithKeyboard2(items, options) {
4838
5056
  cleanup();
4839
5057
  stdout.write(renderedLines > 0 ? `
4840
5058
  ` : "");
4841
- resolve(item);
5059
+ resolve2(item);
4842
5060
  }
4843
5061
  function cancel() {
4844
5062
  cleanup();
4845
5063
  clearRendered2(stdout, renderedLines);
4846
5064
  stdout.write(`Cancelled.
4847
5065
  `);
4848
- resolve(undefined);
5066
+ resolve2(undefined);
4849
5067
  }
4850
5068
  function render() {
4851
5069
  clearRendered2(stdout, renderedLines);
@@ -4901,7 +5119,7 @@ async function paginateWithKeyboard(options) {
4901
5119
  let busy = false;
4902
5120
  let done = false;
4903
5121
  const wasRaw = stdin.isRaw === true;
4904
- return await new Promise((resolve) => {
5122
+ return await new Promise((resolve2) => {
4905
5123
  function cleanup() {
4906
5124
  if (done) {
4907
5125
  return;
@@ -4916,7 +5134,7 @@ async function paginateWithKeyboard(options) {
4916
5134
  cleanup();
4917
5135
  stdout.write(`
4918
5136
  `);
4919
- resolve();
5137
+ resolve2();
4920
5138
  }
4921
5139
  function render() {
4922
5140
  clearRendered2(stdout, renderedLines);
@@ -5069,7 +5287,7 @@ var init_format2 = __esm(() => {
5069
5287
  });
5070
5288
 
5071
5289
  // src/cli.ts
5072
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
5290
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
5073
5291
  import path2 from "path";
5074
5292
 
5075
5293
  // ../../node_modules/commander/lib/error.js
@@ -7215,6 +7433,7 @@ ${fluncleAsciiLogo}
7215
7433
  addMetaCommands(program2);
7216
7434
  addTrackCommands(program2);
7217
7435
  addAdminCommands(program2);
7436
+ addHelmCommands(program2);
7218
7437
  return program2;
7219
7438
  }
7220
7439
  async function main(args = process.argv.slice(2)) {
@@ -7313,6 +7532,21 @@ function addTrackCommands(program2) {
7313
7532
  await runTrackGet(idOrLogId, options, trackGetCommand2);
7314
7533
  });
7315
7534
  }
7535
+ function addHelmCommands(program2) {
7536
+ const helm = configureCommand(program2.command("helm", { hidden: true }).description("Fluncle's Helm \u2014 the operator's mission control"));
7537
+ helm.action(async () => {
7538
+ const { helmOpenCommand: helmOpenCommand2 } = await Promise.resolve().then(() => (init_helm(), exports_helm));
7539
+ await helmOpenCommand2();
7540
+ });
7541
+ helm.command("install").description("Install the launchd LaunchAgent (the daemon rises at login)").action(async () => {
7542
+ const { helmInstallCommand: helmInstallCommand2 } = await Promise.resolve().then(() => (init_helm(), exports_helm));
7543
+ await helmInstallCommand2();
7544
+ });
7545
+ helm.command("uninstall").description("Remove the LaunchAgent and stand the daemon down").action(async () => {
7546
+ const { helmUninstallCommand: helmUninstallCommand2 } = await Promise.resolve().then(() => (init_helm(), exports_helm));
7547
+ await helmUninstallCommand2();
7548
+ });
7549
+ }
7316
7550
  function addAdminCommands(program2) {
7317
7551
  const admin = configureCommand(program2.command("admin", { hidden: true }).description("Operator commands"));
7318
7552
  admin.action(() => {
@@ -7792,7 +8026,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
7792
8026
  return;
7793
8027
  }
7794
8028
  const candidate = path2.join(dir, name);
7795
- return existsSync5(candidate) ? candidate : undefined;
8029
+ return existsSync6(candidate) ? candidate : undefined;
7796
8030
  };
7797
8031
  const resolveFile = (explicit, name) => {
7798
8032
  if (explicit) {
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.93.0"
34
+ "version": "0.94.0"
35
35
  }