fluncle 0.46.0 → 0.48.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 +160 -34
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -2497,7 +2497,24 @@ var require_main = __commonJS((exports, module) => {
2497
2497
  module.exports = DotenvModule;
2498
2498
  });
2499
2499
 
2500
+ // ../../packages/contracts/src/util.ts
2501
+ function formatDuration(durationMs) {
2502
+ const totalSeconds = Math.round(durationMs / 1000);
2503
+ const minutes = Math.floor(totalSeconds / 60);
2504
+ const seconds = totalSeconds % 60;
2505
+ return `${minutes}:${seconds.toString().padStart(2, "0")}`;
2506
+ }
2507
+ function formatError(error) {
2508
+ if (error instanceof Error) {
2509
+ return error.message;
2510
+ }
2511
+ return String(error);
2512
+ }
2513
+
2500
2514
  // src/output.ts
2515
+ function isJsonFailure(value) {
2516
+ return typeof value === "object" && value !== null && typeof value.code === "string" && typeof value.message === "string";
2517
+ }
2501
2518
  function printJson2(value) {
2502
2519
  console.log(JSON.stringify(value, null, 2));
2503
2520
  }
@@ -2539,7 +2556,7 @@ function parseVersion(version) {
2539
2556
  var currentVersion;
2540
2557
  var init_version = __esm(() => {
2541
2558
  init_output();
2542
- currentVersion = "0.46.0".trim() ? "0.46.0".trim() : "0.1.0";
2559
+ currentVersion = "0.48.0".trim() ? "0.48.0".trim() : "0.1.0";
2543
2560
  });
2544
2561
 
2545
2562
  // src/update-notifier.ts
@@ -2727,11 +2744,20 @@ import { homedir as homedir2 } from "node:os";
2727
2744
  import { join as join2 } from "node:path";
2728
2745
  function loadEnv(keys) {
2729
2746
  loadConfig();
2730
- const missing = keys.filter((key) => !process.env[key]);
2747
+ const result = {};
2748
+ const missing = [];
2749
+ for (const key of keys) {
2750
+ const value = process.env[key];
2751
+ if (value === undefined) {
2752
+ missing.push(key);
2753
+ continue;
2754
+ }
2755
+ result[key] = value;
2756
+ }
2731
2757
  if (missing.length > 0) {
2732
2758
  throw new Error(`Missing required env vars: ${missing.join(", ")}`);
2733
2759
  }
2734
- return Object.fromEntries(keys.map((key) => [key, process.env[key]]));
2760
+ return result;
2735
2761
  }
2736
2762
  function getApiBaseUrl() {
2737
2763
  loadConfig();
@@ -2833,7 +2859,7 @@ async function apiRequest(path, init = {}) {
2833
2859
  const text = await response.text();
2834
2860
  const data = parseJson(text);
2835
2861
  if (!response.ok) {
2836
- const failure = data;
2862
+ const failure = isJsonFailure(data) ? data : undefined;
2837
2863
  throw new CliError2(failure?.code ?? `http_${response.status}`, failure?.message ?? `${response.status} ${response.statusText}`);
2838
2864
  }
2839
2865
  return data;
@@ -3485,6 +3511,7 @@ function artistTitle(track) {
3485
3511
  return `${track.artists.join(", ")} — ${track.title}`;
3486
3512
  }
3487
3513
  var COORD_FALLBACK = "—";
3514
+ var init_format = () => {};
3488
3515
 
3489
3516
  // src/interactive.ts
3490
3517
  import { createInterface } from "node:readline/promises";
@@ -3732,6 +3759,7 @@ function buildSelectorLines(tracks, selectedIndex, columns) {
3732
3759
  }
3733
3760
  var TELEGRAM_APP_URI = "tg://resolve?domain=fluncle", SPOTIFY_PLAYLIST_OPEN_URL, SELECT_NON_INTERACTIVE_MESSAGE = "fluncle open requires an interactive terminal. Use fluncle recent to list tracks.";
3734
3761
  var init_open = __esm(() => {
3762
+ init_format();
3735
3763
  init_interactive();
3736
3764
  init_output();
3737
3765
  init_recent2();
@@ -4001,7 +4029,7 @@ function parseVersion2(version) {
4001
4029
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
4002
4030
  var init_version2 = __esm(() => {
4003
4031
  init_output();
4004
- currentVersion2 = "0.46.0".trim() ? "0.46.0".trim() : "0.1.0";
4032
+ currentVersion2 = "0.48.0".trim() ? "0.48.0".trim() : "0.1.0";
4005
4033
  });
4006
4034
 
4007
4035
  // src/commands/track.ts
@@ -4013,7 +4041,8 @@ __export(exports_track, {
4013
4041
  trackSocialShowCommand: () => trackSocialShowCommand,
4014
4042
  trackObserveCommand: () => trackObserveCommand,
4015
4043
  trackGetCommand: () => trackGetCommand,
4016
- trackDraftCommand: () => trackDraftCommand
4044
+ trackDraftCommand: () => trackDraftCommand,
4045
+ trackContextCommand: () => trackContextCommand
4017
4046
  });
4018
4047
  async function trackGetCommand(idOrLogId) {
4019
4048
  return publicApiGet(`/api/tracks/${encodeURIComponent(idOrLogId)}`);
@@ -4126,6 +4155,13 @@ async function trackObserveCommand(idOrLogId, options) {
4126
4155
  }
4127
4156
  return adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/observe`, body);
4128
4157
  }
4158
+ async function trackContextCommand(idOrLogId, options = {}) {
4159
+ const body = {};
4160
+ if (options.query !== undefined) {
4161
+ body.query = options.query;
4162
+ }
4163
+ return adminApiPost(`/api/admin/tracks/${encodeURIComponent(idOrLogId)}/context`, body);
4164
+ }
4129
4165
  var DEFAULT_VIDEO_MODEL = "anthropic/claude-opus-4-8", DEFAULT_VIDEO_REASONING = "high", FOUND_BASE = "https://found.fluncle.com", VIDEO_FIELDS;
4130
4166
  var init_track = __esm(() => {
4131
4167
  init_api();
@@ -4174,13 +4210,15 @@ var exports_admin_tracks = {};
4174
4210
  __export(exports_admin_tracks, {
4175
4211
  vehiclesCommand: () => vehiclesCommand,
4176
4212
  queueCommand: () => queueCommand,
4213
+ observeQueueCommand: () => observeQueueCommand,
4177
4214
  enrichSweepCommand: () => enrichSweepCommand,
4178
4215
  enrichQueueCommand: () => enrichQueueCommand,
4216
+ contextQueueCommand: () => contextQueueCommand,
4179
4217
  backfillLastfmCommand: () => backfillLastfmCommand,
4180
4218
  backfillDiscogsCommand: () => backfillDiscogsCommand
4181
4219
  });
4182
4220
  async function fetchAdminTracks(options) {
4183
- const { hasVideo, max, order, status } = options;
4221
+ const { hasContext, hasObservation, hasVideo, max, order, status } = options;
4184
4222
  const results = [];
4185
4223
  let cursor;
4186
4224
  do {
@@ -4188,6 +4226,12 @@ async function fetchAdminTracks(options) {
4188
4226
  if (hasVideo !== undefined) {
4189
4227
  params.set("hasVideo", String(hasVideo));
4190
4228
  }
4229
+ if (hasContext !== undefined) {
4230
+ params.set("hasContext", String(hasContext));
4231
+ }
4232
+ if (hasObservation !== undefined) {
4233
+ params.set("hasObservation", String(hasObservation));
4234
+ }
4191
4235
  if (status !== undefined) {
4192
4236
  params.set("status", status);
4193
4237
  }
@@ -4209,12 +4253,29 @@ async function fetchAdminTracks(options) {
4209
4253
  } while (cursor);
4210
4254
  return results;
4211
4255
  }
4212
- async function queueCommand(limit) {
4213
- return fetchAdminTracks({ hasVideo: false, max: limit, order: "asc" });
4256
+ async function queueCommand(limit, filters = {}) {
4257
+ return fetchAdminTracks({
4258
+ hasContext: filters.hasContext,
4259
+ hasObservation: filters.hasObservation,
4260
+ hasVideo: false,
4261
+ max: limit,
4262
+ order: "asc"
4263
+ });
4214
4264
  }
4215
4265
  async function enrichQueueCommand(limit) {
4216
4266
  return fetchAdminTracks({ max: limit, order: "asc", status: "queue" });
4217
4267
  }
4268
+ async function contextQueueCommand(limit) {
4269
+ return fetchAdminTracks({ hasContext: false, max: limit, order: "asc" });
4270
+ }
4271
+ async function observeQueueCommand(limit) {
4272
+ return fetchAdminTracks({
4273
+ hasContext: true,
4274
+ hasObservation: false,
4275
+ max: limit,
4276
+ order: "asc"
4277
+ });
4278
+ }
4218
4279
  async function enrichSweepCommand(limit) {
4219
4280
  return adminApiPost(`/api/admin/tracks/enrich?limit=${limit}`);
4220
4281
  }
@@ -5188,12 +5249,6 @@ function vehicleRows(rows) {
5188
5249
  return `${coordinate2(row).padEnd(coordWidth)} ${foundDate(row.addedAt)} ${row.vehicle ?? COORD_FALLBACK2}`;
5189
5250
  });
5190
5251
  }
5191
- function formatDuration(durationMs) {
5192
- const totalSeconds = Math.round(durationMs / 1000);
5193
- const minutes = Math.floor(totalSeconds / 60);
5194
- const seconds = totalSeconds % 60;
5195
- return `${minutes}:${seconds.toString().padStart(2, "0")}`;
5196
- }
5197
5252
  function trackDetailLines(track) {
5198
5253
  const lines = [`${coordinate2(track)} ${artistTitle2(track)}`];
5199
5254
  const meta = [];
@@ -5209,6 +5264,7 @@ function trackDetailLines(track) {
5209
5264
  return lines;
5210
5265
  }
5211
5266
  var COORD_FALLBACK2 = "—";
5267
+ var init_format2 = () => {};
5212
5268
 
5213
5269
  // src/cli.ts
5214
5270
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
@@ -5292,15 +5348,6 @@ function toJsonFailure(error) {
5292
5348
  ok: false
5293
5349
  };
5294
5350
  }
5295
-
5296
- // src/retry.ts
5297
- function formatError(error) {
5298
- if (error instanceof Error) {
5299
- return error.message;
5300
- }
5301
- return String(error);
5302
- }
5303
-
5304
5351
  // src/cli.ts
5305
5352
  function createProgram() {
5306
5353
  const program2 = configureCommand(new Command);
@@ -5415,14 +5462,22 @@ function addAdminCommands(program2) {
5415
5462
  const { addCommand: addCommand3 } = await Promise.resolve().then(() => (init_add(), exports_add));
5416
5463
  await runAdd(spotifyUrl, options, addCommand3);
5417
5464
  });
5418
- adminTracks.command("queue").description("Findings awaiting a video, oldest first (the next to film is first)").option("--limit <limit>", "Number of findings to show", "10").option("--json", "Print JSON", false).action(async (options) => {
5465
+ adminTracks.command("queue").description("Findings awaiting a video, oldest first (the next to film is first)").option("--limit <limit>", "Number of findings to show", "10").option("--has-context", "Only findings whose field notes are already gathered").option("--has-observation", "Only findings that already have a spoken observation").option("--json", "Print JSON", false).action(async (options) => {
5419
5466
  const { queueCommand: queueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
5420
5467
  await runAdminQueue(options, queueCommand2);
5421
5468
  });
5422
- admin.command("queue", { hidden: true }).description("Render queue (alias of `admin tracks queue`)").option("--limit <limit>", "Number of findings to show", "10").option("--json", "Print JSON", false).action(async (options) => {
5469
+ admin.command("queue", { hidden: true }).description("Render queue (alias of `admin tracks queue`)").option("--limit <limit>", "Number of findings to show", "10").option("--has-context", "Only findings whose field notes are already gathered").option("--has-observation", "Only findings that already have a spoken observation").option("--json", "Print JSON", false).action(async (options) => {
5423
5470
  const { queueCommand: queueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
5424
5471
  await runAdminQueue(options, queueCommand2);
5425
5472
  });
5473
+ adminTracks.command("context-queue").description("Findings missing their field notes, oldest first (the context cron's worklist)").option("--limit <limit>", "Number of findings to show", "10").option("--json", "Print JSON", false).action(async (options) => {
5474
+ const { contextQueueCommand: contextQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
5475
+ await runAdminContextQueue(options, contextQueueCommand2);
5476
+ });
5477
+ adminTracks.command("observe-queue").description("Findings with notes but no observation yet, oldest first (the observe cron's worklist)").option("--limit <limit>", "Number of findings to show", "10").option("--json", "Print JSON", false).action(async (options) => {
5478
+ const { observeQueueCommand: observeQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
5479
+ await runAdminObserveQueue(options, observeQueueCommand2);
5480
+ });
5426
5481
  adminTracks.command("enrich-queue").description("Findings needing (re-)enrichment: pending, failed, or stuck processing").option("--limit <limit>", "Number of findings to show", "10").option("--json", "Print JSON", false).action(async (options) => {
5427
5482
  const { enrichQueueCommand: enrichQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
5428
5483
  await runAdminEnrichQueue(options, enrichQueueCommand2);
@@ -5476,6 +5531,10 @@ function addAdminCommands(program2) {
5476
5531
  const { trackObserveCommand: trackObserveCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
5477
5532
  await runTrackObserve(idOrLogId, options, trackObserveCommand2);
5478
5533
  });
5534
+ adminTrack.command("context").description("Gather the field notes for a finding (facts only; observe speaks from them)").argument("[idOrLogId]").option("--query <text>", "Override the fact-search query (else the Worker builds one)").option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
5535
+ const { trackContextCommand: trackContextCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
5536
+ await runTrackContext(idOrLogId, options, trackContextCommand2);
5537
+ });
5479
5538
  const adminMixtapes = configureCommand(admin.command("mixtapes").description("Mixtape admin commands"));
5480
5539
  adminMixtapes.action(() => {
5481
5540
  adminMixtapes.outputHelp();
@@ -5624,6 +5683,29 @@ async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
5624
5683
  console.log(` length: ${Math.round(result.durationMs / 1000)}s`);
5625
5684
  console.log(` voice: ${result.voiceId}`);
5626
5685
  }
5686
+ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
5687
+ if (!idOrLogId) {
5688
+ throw new Error("Usage: fluncle admin tracks context <track_id|log_id> [--query <text>] [--json]");
5689
+ }
5690
+ const result = await trackContextCommand2(idOrLogId, { query: options.query });
5691
+ if (options.json) {
5692
+ printJson(result);
5693
+ return;
5694
+ }
5695
+ if (result.skipped) {
5696
+ console.log(`Field notes already on file for ${result.logId}. Nothing to gather.`);
5697
+ return;
5698
+ }
5699
+ if (!result.contextNote.trim()) {
5700
+ console.log(`No field notes turned up for ${result.logId}. The queue will swing back around.`);
5701
+ return;
5702
+ }
5703
+ console.log(`Gathered field notes for ${result.logId}:`);
5704
+ console.log(` ${result.contextNote}`);
5705
+ if (result.sources.length > 0) {
5706
+ console.log(` sources: ${result.sources.join(", ")}`);
5707
+ }
5708
+ }
5627
5709
  async function runPreviewArchiveBackfill(options, previewArchiveBackfillCommand2) {
5628
5710
  const limit = options.limit === undefined ? undefined : Number.parseInt(options.limit, 10);
5629
5711
  if (limit !== undefined && (!Number.isInteger(limit) || limit < 1)) {
@@ -6020,7 +6102,7 @@ async function runRecent(options, recentCommand3) {
6020
6102
  if (options.limit === undefined && !options.json && process.stdout.isTTY === true && process.stdin.isTTY === true) {
6021
6103
  const { fetchRecentPage: fetchRecentPage2 } = await Promise.resolve().then(() => (init_recent(), exports_recent));
6022
6104
  const { paginateWithKeyboard: paginateWithKeyboard2 } = await Promise.resolve().then(() => (init_interactive2(), exports_interactive));
6023
- const { trackRows: trackRows3 } = await Promise.resolve().then(() => exports_format);
6105
+ const { trackRows: trackRows3 } = await Promise.resolve().then(() => (init_format2(), exports_format));
6024
6106
  await paginateWithKeyboard2({
6025
6107
  emptyMessage: "No findings logged yet.",
6026
6108
  fetchPage: async (cursor) => {
@@ -6048,7 +6130,7 @@ async function runRecent(options, recentCommand3) {
6048
6130
  console.log("No findings logged yet.");
6049
6131
  return;
6050
6132
  }
6051
- const { trackRows: trackRows2 } = await Promise.resolve().then(() => exports_format);
6133
+ const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
6052
6134
  console.log(trackRows2(tracks).join(`
6053
6135
  `));
6054
6136
  }
@@ -6062,7 +6144,7 @@ async function runMixtapes(options, mixtapesCommand2) {
6062
6144
  console.log("No mixtapes logged yet.");
6063
6145
  return;
6064
6146
  }
6065
- const { trackRows: trackRows2 } = await Promise.resolve().then(() => exports_format);
6147
+ const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
6066
6148
  console.log(trackRows2(mixtapes).join(`
6067
6149
  `));
6068
6150
  }
@@ -6075,7 +6157,10 @@ function parseListLimit(value) {
6075
6157
  }
6076
6158
  async function runAdminQueue(options, queueCommand2) {
6077
6159
  const limit = parseListLimit(options.limit);
6078
- const tracks = await queueCommand2(limit);
6160
+ const tracks = await queueCommand2(limit, {
6161
+ hasContext: options.hasContext ? true : undefined,
6162
+ hasObservation: options.hasObservation ? true : undefined
6163
+ });
6079
6164
  if (options.json) {
6080
6165
  printJson({
6081
6166
  ok: true,
@@ -6087,12 +6172,52 @@ async function runAdminQueue(options, queueCommand2) {
6087
6172
  console.log("Every finding has a video. Nothing in the render queue.");
6088
6173
  return;
6089
6174
  }
6090
- const { trackRows: trackRows2 } = await Promise.resolve().then(() => exports_format);
6175
+ const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
6091
6176
  const noun = tracks.length === 1 ? "finding" : "findings";
6092
6177
  console.log(`${tracks.length} ${noun} awaiting a video, oldest first:`);
6093
6178
  console.log(trackRows2(tracks).join(`
6094
6179
  `));
6095
6180
  }
6181
+ async function runAdminContextQueue(options, contextQueueCommand2) {
6182
+ const limit = parseListLimit(options.limit);
6183
+ const tracks = await contextQueueCommand2(limit);
6184
+ if (options.json) {
6185
+ printJson({
6186
+ ok: true,
6187
+ tracks
6188
+ });
6189
+ return;
6190
+ }
6191
+ if (tracks.length === 0) {
6192
+ console.log("Every finding has its field notes. Nothing waiting on context.");
6193
+ return;
6194
+ }
6195
+ const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
6196
+ const noun = tracks.length === 1 ? "finding" : "findings";
6197
+ console.log(`${tracks.length} ${noun} missing field notes, oldest first:`);
6198
+ console.log(trackRows2(tracks).join(`
6199
+ `));
6200
+ }
6201
+ async function runAdminObserveQueue(options, observeQueueCommand2) {
6202
+ const limit = parseListLimit(options.limit);
6203
+ const tracks = await observeQueueCommand2(limit);
6204
+ if (options.json) {
6205
+ printJson({
6206
+ ok: true,
6207
+ tracks
6208
+ });
6209
+ return;
6210
+ }
6211
+ if (tracks.length === 0) {
6212
+ console.log("Every finding with notes has its observation. Nothing waiting on a voice.");
6213
+ return;
6214
+ }
6215
+ const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
6216
+ const noun = tracks.length === 1 ? "finding" : "findings";
6217
+ console.log(`${tracks.length} ${noun} awaiting an observation, oldest first:`);
6218
+ console.log(trackRows2(tracks).join(`
6219
+ `));
6220
+ }
6096
6221
  async function runAdminEnrichQueue(options, enrichQueueCommand2) {
6097
6222
  const limit = parseListLimit(options.limit);
6098
6223
  const tracks = await enrichQueueCommand2(limit);
@@ -6107,7 +6232,7 @@ async function runAdminEnrichQueue(options, enrichQueueCommand2) {
6107
6232
  console.log("Nothing awaiting enrichment. Every finding is enriched.");
6108
6233
  return;
6109
6234
  }
6110
- const { trackRows: trackRows2 } = await Promise.resolve().then(() => exports_format);
6235
+ const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
6111
6236
  const noun = tracks.length === 1 ? "finding" : "findings";
6112
6237
  console.log(`${tracks.length} ${noun} needing (re-)enrichment, oldest first:`);
6113
6238
  console.log(trackRows2(tracks).join(`
@@ -6156,7 +6281,7 @@ async function runAdminVehicles(options, vehiclesCommand2) {
6156
6281
  console.log("No videos rendered yet.");
6157
6282
  return;
6158
6283
  }
6159
- const { vehicleRows: vehicleRows2 } = await Promise.resolve().then(() => exports_format);
6284
+ const { vehicleRows: vehicleRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
6160
6285
  console.log("Recent video vehicles, newest first:");
6161
6286
  console.log(vehicleRows2(vehicles).join(`
6162
6287
  `));
@@ -6198,7 +6323,7 @@ async function runRandom(options, randomCommand2) {
6198
6323
  });
6199
6324
  return;
6200
6325
  }
6201
- const { trackDetailLines: trackDetailLines2 } = await Promise.resolve().then(() => exports_format);
6326
+ const { trackDetailLines: trackDetailLines2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
6202
6327
  console.log(trackDetailLines2(track).join(`
6203
6328
  `));
6204
6329
  if (track.note) {
@@ -6348,6 +6473,7 @@ var stringOptions = new Set([
6348
6473
  "--platform",
6349
6474
  "--poster",
6350
6475
  "--props",
6476
+ "--query",
6351
6477
  "--recorded-at",
6352
6478
  "--render",
6353
6479
  "--scheduled-for",
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.46.0"
34
+ "version": "0.48.0"
35
35
  }