fluncle 0.34.0 → 0.36.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 +92 -2
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -3789,7 +3789,7 @@ function parseVersion(version) {
3789
3789
  var currentVersion, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
3790
3790
  var init_version = __esm(() => {
3791
3791
  init_output();
3792
- currentVersion = "0.34.0".trim() ? "0.34.0".trim() : "0.1.0";
3792
+ currentVersion = "0.36.0".trim() ? "0.36.0".trim() : "0.1.0";
3793
3793
  });
3794
3794
 
3795
3795
  // src/commands/track.ts
@@ -3799,6 +3799,7 @@ __export(exports_track, {
3799
3799
  trackUpdateCommand: () => trackUpdateCommand,
3800
3800
  trackSocialUpdateCommand: () => trackSocialUpdateCommand,
3801
3801
  trackSocialShowCommand: () => trackSocialShowCommand,
3802
+ trackObserveCommand: () => trackObserveCommand,
3802
3803
  trackGetCommand: () => trackGetCommand,
3803
3804
  trackDraftCommand: () => trackDraftCommand
3804
3805
  });
@@ -3892,6 +3893,25 @@ async function trackUpdateCommand(trackId, options) {
3892
3893
  }
3893
3894
  return adminApiPatch(`/api/admin/tracks/${encodeURIComponent(trackId)}`, body);
3894
3895
  }
3896
+ async function trackObserveCommand(idOrLogId, options) {
3897
+ const body = { script: options.script };
3898
+ if (options.voiceId !== undefined) {
3899
+ body.voiceId = options.voiceId;
3900
+ }
3901
+ if (options.model !== undefined) {
3902
+ body.model = options.model;
3903
+ }
3904
+ if (options.durationMs !== undefined) {
3905
+ body.durationMs = options.durationMs;
3906
+ }
3907
+ if (options.durationTargetSec !== undefined) {
3908
+ body.durationTargetSec = options.durationTargetSec;
3909
+ }
3910
+ if (options.contextNote !== undefined) {
3911
+ body.contextNote = options.contextNote;
3912
+ }
3913
+ return adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/observe`, body);
3914
+ }
3895
3915
  var DEFAULT_VIDEO_MODEL = "anthropic/claude-opus-4-8", DEFAULT_VIDEO_REASONING = "high", FOUND_BASE = "https://found.fluncle.com", VIDEO_FIELDS;
3896
3916
  var init_track = __esm(() => {
3897
3917
  init_api();
@@ -4618,6 +4638,38 @@ var init_mixtape_mixcloud2 = __esm(() => {
4618
4638
  PICTURE_MAX_BYTES2 = 10 * 1024 * 1024;
4619
4639
  });
4620
4640
 
4641
+ // src/commands/auth-lastfm.ts
4642
+ var exports_auth_lastfm = {};
4643
+ __export(exports_auth_lastfm, {
4644
+ authLastfmCommand: () => authLastfmCommand
4645
+ });
4646
+ async function authLastfmCommand(options) {
4647
+ if (options.token?.trim()) {
4648
+ const response2 = await adminApiPost("/api/admin/lastfm/auth/session", { token: options.token.trim() });
4649
+ console.log(`Last.fm connected as ${response2.name || "fluncle"}.
4650
+
4651
+ Set this as the Worker secret LASTFM_SESSION_KEY (it does not expire):
4652
+
4653
+ ${response2.sessionKey}
4654
+
4655
+ bun run --cwd apps/web wrangler secret put LASTFM_SESSION_KEY
4656
+
4657
+ Also store it in 1Password (Fluncle vault) alongside the API key + shared secret.`);
4658
+ return;
4659
+ }
4660
+ const response = await adminApiGet("/api/admin/lastfm/auth/start");
4661
+ console.log(`Open this Last.fm authorization URL (logged in as fluncle) and click "Yes, allow access":
4662
+
4663
+ ${response.authUrl}
4664
+
4665
+ After approving, run this to mint the durable session key:
4666
+
4667
+ fluncle admin auth lastfm --token ${response.token}`);
4668
+ }
4669
+ var init_auth_lastfm = __esm(() => {
4670
+ init_api();
4671
+ });
4672
+
4621
4673
  // src/interactive.ts
4622
4674
  var exports_interactive = {};
4623
4675
  __export(exports_interactive, {
@@ -4897,7 +4949,7 @@ function trackDetailLines(track) {
4897
4949
  var COORD_FALLBACK2 = "—";
4898
4950
 
4899
4951
  // src/cli.ts
4900
- import { existsSync as existsSync2 } from "fs";
4952
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
4901
4953
  import path from "path";
4902
4954
 
4903
4955
  // ../../node_modules/.bun/commander@14.0.3/node_modules/commander/esm.mjs
@@ -5123,6 +5175,10 @@ function addAdminCommands(program2) {
5123
5175
  const { previewArchiveUploadCommand: previewArchiveUploadCommand2 } = await Promise.resolve().then(() => (init_preview_archive(), exports_preview_archive));
5124
5176
  await runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCommand2);
5125
5177
  });
5178
+ adminTrack.command("observe").description("Render Fluncle's spoken field observation for a track (ElevenLabs, Worker-side)").argument("[idOrLogId]").option("--script <text>", "The voice-gated observation script (the spoken text)").option("--script-file <file>", "Read the observation script from a file (e.g. observation.txt)").option("--voice-id <id>", "Override the configured ElevenLabs voice id").option("--model <model>", "TTS model (eleven_multilingual_v2 | eleven_v3)").option("--duration-ms <ms>", "Probed audio duration in ms (the agent runs ffprobe)").option("--duration-target-sec <sec>", "Target observation length in seconds (20\u201345)").option("--context-note <text>", "Pre-fetched factual context (else the Worker firecrawls)").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
5179
+ const { trackObserveCommand: trackObserveCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
5180
+ await runTrackObserve(idOrLogId, options, trackObserveCommand2);
5181
+ });
5126
5182
  const adminMixtapes = configureCommand(admin.command("mixtapes").description("Mixtape admin commands"));
5127
5183
  adminMixtapes.action(() => {
5128
5184
  adminMixtapes.outputHelp();
@@ -5202,6 +5258,10 @@ function addAdminCommands(program2) {
5202
5258
  const { authMixcloudCommand: authMixcloudCommand3 } = await Promise.resolve().then(() => (init_mixtape_mixcloud2(), exports_mixtape_mixcloud2));
5203
5259
  await authMixcloudCommand3();
5204
5260
  });
5261
+ auth.command("lastfm").description("Authorize Last.fm access (love-on-add). Run once for the URL, then again with --token").option("--token <token>", "The approved request token, to mint the session key").action(async (options) => {
5262
+ const { authLastfmCommand: authLastfmCommand2 } = await Promise.resolve().then(() => (init_auth_lastfm(), exports_auth_lastfm));
5263
+ await authLastfmCommand2(options);
5264
+ });
5205
5265
  const backfill = configureCommand(admin.command("backfill").description("Backfill operator-only archives"));
5206
5266
  backfill.action(() => {
5207
5267
  backfill.outputHelp();
@@ -5229,6 +5289,36 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
5229
5289
  console.log(` source: ${result.source}`);
5230
5290
  console.log(` mime: ${result.mime}`);
5231
5291
  }
5292
+ async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
5293
+ const script = options.scriptFile ? readFileSync2(options.scriptFile, "utf8") : options.script;
5294
+ if (!idOrLogId || !script || !script.trim()) {
5295
+ throw new Error("Usage: fluncle admin track observe <track_id|log_id> (--script <text> | --script-file <file>) [--voice-id <id>] [--model <model>] [--duration-ms <ms>] [--context-note <text>] [--json]");
5296
+ }
5297
+ const durationMs = options.durationMs === undefined ? undefined : Number.parseInt(options.durationMs, 10);
5298
+ if (durationMs !== undefined && (!Number.isInteger(durationMs) || durationMs < 1)) {
5299
+ throw new Error("--duration-ms must be a positive integer");
5300
+ }
5301
+ const durationTargetSec = options.durationTargetSec === undefined ? undefined : Number.parseInt(options.durationTargetSec, 10);
5302
+ if (durationTargetSec !== undefined && (!Number.isInteger(durationTargetSec) || durationTargetSec < 1)) {
5303
+ throw new Error("--duration-target-sec must be a positive integer");
5304
+ }
5305
+ const result = await trackObserveCommand2(idOrLogId, {
5306
+ contextNote: options.contextNote,
5307
+ durationMs,
5308
+ durationTargetSec,
5309
+ model: options.model,
5310
+ script: script.trim(),
5311
+ voiceId: options.voiceId
5312
+ });
5313
+ if (options.json) {
5314
+ printJson(result);
5315
+ return;
5316
+ }
5317
+ console.log(`Recorded observation for ${result.logId}`);
5318
+ console.log(` audio: ${result.audioUrl}`);
5319
+ console.log(` length: ${Math.round(result.durationMs / 1000)}s`);
5320
+ console.log(` voice: ${result.voiceId}`);
5321
+ }
5232
5322
  async function runPreviewArchiveBackfill(options, previewArchiveBackfillCommand2) {
5233
5323
  const limit = options.limit === undefined ? undefined : Number.parseInt(options.limit, 10);
5234
5324
  if (limit !== undefined && (!Number.isInteger(limit) || limit < 1)) {
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.34.0"
34
+ "version": "0.36.0"
35
35
  }