fluncle 0.209.0 → 0.211.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 +173 -6
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -610,7 +610,7 @@ function parseVersion(version) {
610
610
  var currentVersion;
611
611
  var init_version = __esm(() => {
612
612
  init_output();
613
- currentVersion = "0.209.0".trim() ? "0.209.0".trim() : "0.1.0";
613
+ currentVersion = "0.211.0".trim() ? "0.211.0".trim() : "0.1.0";
614
614
  });
615
615
 
616
616
  // src/update-notifier.ts
@@ -2452,7 +2452,7 @@ function parseVersion2(version) {
2452
2452
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
2453
2453
  var init_version2 = __esm(() => {
2454
2454
  init_output();
2455
- currentVersion2 = "0.209.0".trim() ? "0.209.0".trim() : "0.1.0";
2455
+ currentVersion2 = "0.211.0".trim() ? "0.211.0".trim() : "0.1.0";
2456
2456
  });
2457
2457
 
2458
2458
  // ../../packages/registry/src/index.ts
@@ -4469,6 +4469,101 @@ var init_admin_attention = __esm(() => {
4469
4469
  };
4470
4470
  });
4471
4471
 
4472
+ // src/commands/admin-telemetry.ts
4473
+ var exports_admin_telemetry = {};
4474
+ __export(exports_admin_telemetry, {
4475
+ telemetryLines: () => telemetryLines,
4476
+ telemetryCommand: () => telemetryCommand
4477
+ });
4478
+ async function telemetryCommand(options) {
4479
+ const params = new URLSearchParams({ limit: String(options.limit) });
4480
+ if (options.cursor !== undefined) {
4481
+ params.set("cursor", options.cursor);
4482
+ }
4483
+ if (options.ok !== undefined) {
4484
+ params.set("ok", String(options.ok));
4485
+ }
4486
+ if (options.since !== undefined) {
4487
+ params.set("since", options.since);
4488
+ }
4489
+ if (options.unit !== undefined) {
4490
+ params.set("unit", options.unit);
4491
+ }
4492
+ if (options.until !== undefined) {
4493
+ params.set("until", options.until);
4494
+ }
4495
+ return adminApiGet(`/api/v1/admin/telemetry/runs?${params.toString()}`);
4496
+ }
4497
+ function cell(value) {
4498
+ if (value === null) {
4499
+ return "-";
4500
+ }
4501
+ if (typeof value === "boolean") {
4502
+ return value ? "1" : "0";
4503
+ }
4504
+ return String(value);
4505
+ }
4506
+ function padded(columns, widths) {
4507
+ return columns.map((column, index) => column.padEnd(widths[index] ?? 0)).join(" ").trimEnd();
4508
+ }
4509
+ function telemetryLines(page) {
4510
+ if (!page.available) {
4511
+ return ["Telemetry database is not configured."];
4512
+ }
4513
+ if (page.rows.length === 0) {
4514
+ return ["No run events matched."];
4515
+ }
4516
+ const rollupHeader = ["UNIT", "RUNS", "LAST", "OK=0", "LIAR", "BLIND"];
4517
+ const rollupRows = page.rollups.map((rollup) => [
4518
+ rollup.unit,
4519
+ String(rollup.runCount),
4520
+ rollup.lastOccurredAt,
4521
+ String(rollup.failedCount),
4522
+ String(rollup.liarCount),
4523
+ String(rollup.blindCount)
4524
+ ]);
4525
+ const rollupWidths = rollupHeader.map((header, index) => Math.max(header.length, ...rollupRows.map((row) => row[index]?.length ?? 0)));
4526
+ const runHeader = [
4527
+ "OCCURRED",
4528
+ "UNIT",
4529
+ "OK",
4530
+ "SELF",
4531
+ "CHECKED",
4532
+ "PRODUCED",
4533
+ "QUEUE",
4534
+ "EXIT",
4535
+ "SUMMARY"
4536
+ ];
4537
+ const runRows = page.rows.map((row) => [
4538
+ row.occurredAt,
4539
+ row.unit,
4540
+ cell(row.ok),
4541
+ cell(row.selfAssertedOk),
4542
+ cell(row.checked),
4543
+ cell(row.produced),
4544
+ cell(row.queueDepth),
4545
+ String(row.exitCode),
4546
+ row.summaryStatus
4547
+ ]);
4548
+ const runWidths = runHeader.map((header, index) => Math.max(header.length, ...runRows.map((row) => row[index]?.length ?? 0)));
4549
+ const lines = [
4550
+ `Unit rollups (${page.totalCount} matching runs)`,
4551
+ padded(rollupHeader, rollupWidths),
4552
+ ...rollupRows.map((row) => padded(row, rollupWidths)),
4553
+ "",
4554
+ `Runs (${page.rows.length} on this page)`,
4555
+ padded(runHeader, runWidths),
4556
+ ...runRows.map((row) => padded(row, runWidths))
4557
+ ];
4558
+ if (page.nextCursor !== null) {
4559
+ lines.push("", `Next cursor: ${page.nextCursor}`);
4560
+ }
4561
+ return lines;
4562
+ }
4563
+ var init_admin_telemetry = __esm(() => {
4564
+ init_api();
4565
+ });
4566
+
4472
4567
  // src/commands/add.ts
4473
4568
  var exports_add = {};
4474
4569
  __export(exports_add, {
@@ -9660,6 +9755,14 @@ function addAdminCommands(program2) {
9660
9755
  const { attentionQueueCommand: attentionQueueCommand2 } = await Promise.resolve().then(() => (init_admin_attention(), exports_admin_attention));
9661
9756
  await runAdminAttention(options, attentionQueueCommand2);
9662
9757
  });
9758
+ const adminTelemetry = configureCommand(admin.command("telemetry").description("Run-ledger telemetry"));
9759
+ adminTelemetry.action(() => {
9760
+ adminTelemetry.outputHelp();
9761
+ });
9762
+ adminTelemetry.command("read").description("Run-ledger rows plus per-unit rollups").option("--unit <unit>", "Only this systemd unit").option("--since <iso>", "Only runs at or after this occurred-at instant").option("--until <iso>", "Only runs at or before this occurred-at instant").option("--ok <true|false>", "Filter by the Worker's derived run verdict").option("--limit <limit>", "Rows to show (1-100)", "50").option("--cursor <cursor>", "Opaque cursor from the previous page").option("--json", "Print the lossless 20-column rows as JSON", false).action(async (options) => {
9763
+ const { telemetryCommand: telemetryCommand2 } = await Promise.resolve().then(() => (init_admin_telemetry(), exports_admin_telemetry));
9764
+ await runAdminTelemetry(options, telemetryCommand2);
9765
+ });
9663
9766
  const adminTracks = configureCommand(admin.command("tracks").description("Track admin commands"));
9664
9767
  adminTracks.action(() => {
9665
9768
  adminTracks.outputHelp();
@@ -11082,36 +11185,48 @@ async function runBackfillArtistImages(options, backfillArtistImagesCommand2) {
11082
11185
  const failed = [];
11083
11186
  const skipped = [];
11084
11187
  let cursor;
11188
+ let budgetLimited = false;
11189
+ let checkedCount = 0;
11085
11190
  let dryRun = options.dryRun;
11086
- while (filled.length + failed.length + skipped.length < limit) {
11087
- const remaining = limit - (filled.length + failed.length + skipped.length);
11191
+ let queueDepth = 0;
11192
+ let rateLimited = false;
11193
+ while (filled.length < limit) {
11194
+ const remaining = limit - filled.length;
11088
11195
  const result = await backfillArtistImagesCommand2(remaining, options.dryRun, cursor);
11196
+ budgetLimited ||= result.budgetLimited;
11197
+ checkedCount += result.checkedCount;
11089
11198
  dryRun = result.dryRun;
11090
11199
  filled.push(...result.filled);
11091
11200
  failed.push(...result.failed);
11201
+ queueDepth = result.queueDepth;
11202
+ rateLimited ||= result.rateLimited;
11092
11203
  skipped.push(...result.skipped);
11093
11204
  if (!options.json) {
11094
11205
  const verb2 = result.dryRun ? "would fill" : "filled";
11095
11206
  console.log(` \u2026${verb2} ${result.filledCount}; ${result.failedCount} failed; ${result.skippedCount} without an image`);
11096
11207
  }
11097
- if (result.nextCursor === null) {
11208
+ if (result.nextCursor === null || result.rateLimited || result.budgetLimited) {
11098
11209
  break;
11099
11210
  }
11100
11211
  cursor = result.nextCursor;
11101
11212
  }
11102
11213
  if (options.json) {
11103
11214
  printSweepJson({
11215
+ budgetLimited,
11216
+ checkedCount,
11104
11217
  dryRun,
11105
11218
  failed,
11106
11219
  filled,
11107
11220
  filledCount: filled.length,
11221
+ queueDepth,
11222
+ rateLimited,
11108
11223
  skipped,
11109
11224
  skippedCount: skipped.length
11110
11225
  }, failed.length);
11111
11226
  return;
11112
11227
  }
11113
11228
  const verb = dryRun ? "Would fill" : "Filled";
11114
- console.log(`${verb} ${filled.length} artist avatar(s); ${failed.length} failed; ${skipped.length} without a Spotify image.`);
11229
+ console.log(`${verb} ${filled.length} artist avatar(s); checked ${checkedCount}; ${failed.length} failed; ${skipped.length} without a Spotify image; ${queueDepth} queued.`);
11115
11230
  for (const artistId of filled) {
11116
11231
  console.log(` ${artistId}`);
11117
11232
  }
@@ -12473,6 +12588,32 @@ function parseListLimit(value) {
12473
12588
  }
12474
12589
  return limit;
12475
12590
  }
12591
+ function parseTelemetryLimit(value) {
12592
+ const limit = Number.parseInt(value ?? "50", 10);
12593
+ if (!Number.isInteger(limit) || limit < 1 || limit > 100) {
12594
+ throw new Error("Limit must be an integer between 1 and 100");
12595
+ }
12596
+ return limit;
12597
+ }
12598
+ function parseTelemetryOk(value) {
12599
+ if (value === undefined) {
12600
+ return;
12601
+ }
12602
+ if (value !== "true" && value !== "false") {
12603
+ throw new Error("--ok must be true or false");
12604
+ }
12605
+ return value === "true";
12606
+ }
12607
+ function parseTelemetryTimestamp(value, flag) {
12608
+ if (value === undefined) {
12609
+ return;
12610
+ }
12611
+ const timestamp = new Date(value);
12612
+ if (Number.isNaN(timestamp.getTime())) {
12613
+ throw new Error(`${flag} must be an ISO-8601 timestamp`);
12614
+ }
12615
+ return timestamp.toISOString();
12616
+ }
12476
12617
  function parsePage(value) {
12477
12618
  const page = Number.parseInt(value ?? "1", 10);
12478
12619
  if (!Number.isInteger(page) || page < 1) {
@@ -12540,6 +12681,28 @@ async function runAdminAttention(options, attentionQueueCommand2) {
12540
12681
  console.log(attentionQueueLines2(queue).join(`
12541
12682
  `));
12542
12683
  }
12684
+ async function runAdminTelemetry(options, telemetryCommand2) {
12685
+ const since = parseTelemetryTimestamp(options.since, "--since");
12686
+ const until = parseTelemetryTimestamp(options.until, "--until");
12687
+ if (since !== undefined && until !== undefined && since > until) {
12688
+ throw new Error("--since must be before or equal to --until");
12689
+ }
12690
+ const page = await telemetryCommand2({
12691
+ cursor: options.cursor,
12692
+ limit: parseTelemetryLimit(options.limit),
12693
+ ok: parseTelemetryOk(options.ok),
12694
+ since,
12695
+ unit: options.unit,
12696
+ until
12697
+ });
12698
+ if (options.json) {
12699
+ printJson({ ok: true, ...page });
12700
+ return;
12701
+ }
12702
+ const { telemetryLines: telemetryLines2 } = await Promise.resolve().then(() => (init_admin_telemetry(), exports_admin_telemetry));
12703
+ console.log(telemetryLines2(page).join(`
12704
+ `));
12705
+ }
12543
12706
  async function runAdminQueue(options, queueCommand2) {
12544
12707
  const limit = parseListLimit(options.limit);
12545
12708
  const tracks = await queueCommand2(limit, {
@@ -13008,6 +13171,7 @@ var stringOptions = new Set([
13008
13171
  "--min-phrase-words",
13009
13172
  "--model",
13010
13173
  "--note",
13174
+ "--ok",
13011
13175
  "--order",
13012
13176
  "--page",
13013
13177
  "--parent-id",
@@ -13029,6 +13193,7 @@ var stringOptions = new Set([
13029
13193
  "--script-file",
13030
13194
  "--seed",
13031
13195
  "--seed-state",
13196
+ "--since",
13032
13197
  "--soundcloud-url",
13033
13198
  "--source",
13034
13199
  "--status",
@@ -13037,6 +13202,8 @@ var stringOptions = new Set([
13037
13202
  "--token",
13038
13203
  "--tracklist-file",
13039
13204
  "--tracks",
13205
+ "--unit",
13206
+ "--until",
13040
13207
  "--url",
13041
13208
  "--verdict",
13042
13209
  "--verdict-file",
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.209.0"
34
+ "version": "0.211.0"
35
35
  }