fluncle 0.219.0 → 0.221.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 +202 -11
  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.219.0".trim() ? "0.219.0".trim() : "0.1.0";
613
+ currentVersion = "0.221.0".trim() ? "0.221.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.219.0".trim() ? "0.219.0".trim() : "0.1.0";
2455
+ currentVersion2 = "0.221.0".trim() ? "0.221.0".trim() : "0.1.0";
2456
2456
  });
2457
2457
 
2458
2458
  // ../../packages/registry/src/index.ts
@@ -4934,11 +4934,14 @@ async function backfillAppleCatalogueCommand(limit, dryRun) {
4934
4934
  const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
4935
4935
  return adminApiPost(`/api/v1/admin/backfill/apple-catalogue?${params.toString()}`);
4936
4936
  }
4937
- async function backfillRecordingMbidsCommand(limit, dryRun, cursor) {
4937
+ async function backfillRecordingMbidsCommand(limit, dryRun, cursor, isrcRefreshLimit) {
4938
4938
  const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
4939
4939
  if (cursor) {
4940
4940
  params.set("cursor", cursor);
4941
4941
  }
4942
+ if (isrcRefreshLimit !== undefined) {
4943
+ params.set("isrcRefreshLimit", String(isrcRefreshLimit));
4944
+ }
4942
4945
  return adminApiPost(`/api/v1/admin/backfill/recording-mbids?${params.toString()}`);
4943
4946
  }
4944
4947
  async function backfillArtistEdgesCommand(limit, dryRun, cursor) {
@@ -7016,15 +7019,48 @@ var init_admin_reach = __esm(() => {
7016
7019
  var exports_admin_artists = {};
7017
7020
  __export(exports_admin_artists, {
7018
7021
  resolveArtistCommand: () => resolveArtistCommand,
7022
+ removeArtistRuleCommand: () => removeArtistRuleCommand,
7019
7023
  rankArtistsCommand: () => rankArtistsCommand,
7020
7024
  listArtistsCommand: () => listArtistsCommand,
7025
+ listArtistRulesCommand: () => listArtistRulesCommand,
7021
7026
  draftArtistBioCommand: () => draftArtistBioCommand,
7022
7027
  describeArtistCommand: () => describeArtistCommand,
7023
7028
  buildBioBody: () => buildBioBody,
7024
7029
  backfillArtistsCommand: () => backfillArtistsCommand,
7025
7030
  backfillArtistImagesCommand: () => backfillArtistImagesCommand,
7026
- artistsBioQueueCommand: () => artistsBioQueueCommand
7031
+ artistsBioQueueCommand: () => artistsBioQueueCommand,
7032
+ artistRuleInput: () => artistRuleInput,
7033
+ addArtistRuleCommand: () => addArtistRuleCommand
7027
7034
  });
7035
+ function artistRuleInput(artistMbid, verdict, artistName) {
7036
+ const cleanMbid = artistMbid.trim();
7037
+ if (!MBID_PATTERN.test(cleanMbid)) {
7038
+ throw new Error("Artist MBID must be a MusicBrainz artist MBID");
7039
+ }
7040
+ if (verdict !== "allow" && verdict !== "block") {
7041
+ throw new Error("Pass --verdict allow|block");
7042
+ }
7043
+ const cleanName = artistName?.trim();
7044
+ if (artistName !== undefined && !cleanName) {
7045
+ throw new Error("--name must be a non-empty artist name");
7046
+ }
7047
+ return {
7048
+ artistMbid: cleanMbid.toLowerCase(),
7049
+ ...cleanName ? { artistName: cleanName } : {},
7050
+ verdict
7051
+ };
7052
+ }
7053
+ async function listArtistRulesCommand() {
7054
+ const response = await adminApiGet("/api/v1/admin/artist-rules");
7055
+ return response.rules;
7056
+ }
7057
+ async function addArtistRuleCommand(input) {
7058
+ const response = await adminApiPost("/api/v1/admin/artist-rules", input);
7059
+ return response.rule;
7060
+ }
7061
+ async function removeArtistRuleCommand(id) {
7062
+ return adminApiDelete(`/api/v1/admin/artist-rules/${encodeURIComponent(id)}`);
7063
+ }
7028
7064
  function buildBioBody(options) {
7029
7065
  const body = { bio: options.bio };
7030
7066
  if (options.dryRun) {
@@ -7077,8 +7113,10 @@ async function backfillArtistsCommand(limit, dryRun, cursor) {
7077
7113
  }
7078
7114
  return adminApiPost(`/api/v1/admin/backfill/artists?${params.toString()}`);
7079
7115
  }
7116
+ var MBID_PATTERN;
7080
7117
  var init_admin_artists = __esm(() => {
7081
7118
  init_api();
7119
+ MBID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7082
7120
  });
7083
7121
 
7084
7122
  // src/commands/admin-artists.ts
@@ -7103,23 +7141,84 @@ var init_admin_artists2 = __esm(() => {
7103
7141
  var exports_admin_labels = {};
7104
7142
  __export(exports_admin_labels, {
7105
7143
  updateLabelCommand: () => updateLabelCommand,
7144
+ replaceLabelArtistRulesCommand: () => replaceLabelArtistRulesCommand,
7145
+ parseLabelArtistRulesJson: () => parseLabelArtistRulesJson,
7106
7146
  mergeLabelCommand: () => mergeLabelCommand,
7147
+ listLabelArtistRulesCommand: () => listLabelArtistRulesCommand,
7107
7148
  labelsBioQueueCommand: () => labelsBioQueueCommand,
7108
7149
  draftLabelBioCommand: () => draftLabelBioCommand,
7109
7150
  describeLabelCommand: () => describeLabelCommand,
7110
7151
  backfillLabelLineageCommand: () => backfillLabelLineageCommand,
7111
7152
  backfillLabelImagesCommand: () => backfillLabelImagesCommand
7112
7153
  });
7113
- async function mergeLabelCommand(losingSlug, canonicalSlug) {
7114
- const response = await adminApiPost(`/api/v1/admin/labels/${encodeURIComponent(losingSlug)}/merge`, { canonicalSlug });
7115
- return response.result;
7154
+ function requireArtistMbid(value, at) {
7155
+ if (typeof value !== "string" || !MBID_PATTERN2.test(value.trim())) {
7156
+ throw new Error(`${at}.artistMbid must be a MusicBrainz artist MBID`);
7157
+ }
7158
+ return value.trim().toLowerCase();
7116
7159
  }
7117
- async function updateLabelCommand(slugOrId, seedState, rewalk = false) {
7160
+ function requireArtistName(value, at) {
7161
+ if (typeof value !== "string" || value.trim().length === 0) {
7162
+ throw new Error(`${at}.artistName must be a non-empty string`);
7163
+ }
7164
+ return value.trim();
7165
+ }
7166
+ function requireArtistVerdict(value, at) {
7167
+ if (value === "allow" || value === "block") {
7168
+ return value;
7169
+ }
7170
+ throw new Error(`${at}.verdict must be 'allow' or 'block'`);
7171
+ }
7172
+ function parseLabelArtistRulesJson(source) {
7173
+ let parsed;
7174
+ try {
7175
+ parsed = JSON.parse(source);
7176
+ } catch {
7177
+ throw new Error("Rules file must contain valid JSON");
7178
+ }
7179
+ if (!Array.isArray(parsed)) {
7180
+ throw new Error("Rules file must contain a JSON array");
7181
+ }
7182
+ if (parsed.length > 100) {
7183
+ throw new Error("Rules file may contain at most 100 artist rules");
7184
+ }
7185
+ return parsed.map((entry, index) => {
7186
+ const at = `Rules file entry ${index + 1}`;
7187
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
7188
+ throw new Error(`${at} must be an object`);
7189
+ }
7190
+ const value = entry;
7191
+ return {
7192
+ artistMbid: requireArtistMbid(value.artistMbid, at),
7193
+ artistName: requireArtistName(value.artistName, at),
7194
+ verdict: requireArtistVerdict(value.verdict, at)
7195
+ };
7196
+ });
7197
+ }
7198
+ async function resolveLabel(slugOrId) {
7118
7199
  const { labels } = await adminApiGet("/api/v1/admin/labels");
7119
7200
  const match = labels.find((label) => label.slug === slugOrId || label.id === slugOrId);
7120
7201
  if (!match) {
7121
7202
  throw new Error(`No label with slug or id '${slugOrId}' — check \`fluncle labels\``);
7122
7203
  }
7204
+ return match;
7205
+ }
7206
+ async function listLabelArtistRulesCommand(slugOrId) {
7207
+ const label = await resolveLabel(slugOrId);
7208
+ const response = await adminApiGet(`/api/v1/admin/labels/${encodeURIComponent(label.id)}/artists`);
7209
+ return { label, rules: response.rules };
7210
+ }
7211
+ async function replaceLabelArtistRulesCommand(slugOrId, rules) {
7212
+ const label = await resolveLabel(slugOrId);
7213
+ const response = await adminApiPut(`/api/v1/admin/labels/${encodeURIComponent(label.id)}/artists`, { rules });
7214
+ return { label, rules: response.rules };
7215
+ }
7216
+ async function mergeLabelCommand(losingSlug, canonicalSlug) {
7217
+ const response = await adminApiPost(`/api/v1/admin/labels/${encodeURIComponent(losingSlug)}/merge`, { canonicalSlug });
7218
+ return response.result;
7219
+ }
7220
+ async function updateLabelCommand(slugOrId, seedState, rewalk = false) {
7221
+ const match = await resolveLabel(slugOrId);
7123
7222
  const response = await adminApiPatch(`/api/v1/admin/labels/${encodeURIComponent(match.id)}`, {
7124
7223
  ...rewalk ? { rewalk: true } : {},
7125
7224
  ...seedState === undefined ? {} : { seedState }
@@ -7150,9 +7249,11 @@ async function backfillLabelLineageCommand(limit, dryRun, cursor) {
7150
7249
  }
7151
7250
  return adminApiPost(`/api/v1/admin/backfill/label-lineage?${params.toString()}`);
7152
7251
  }
7252
+ var MBID_PATTERN2;
7153
7253
  var init_admin_labels = __esm(() => {
7154
7254
  init_api();
7155
7255
  init_admin_artists2();
7256
+ MBID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7156
7257
  });
7157
7258
 
7158
7259
  // src/commands/admin-covers.ts
@@ -9693,6 +9794,26 @@ function labelUpdateLines(label, options) {
9693
9794
  }
9694
9795
  return lines;
9695
9796
  }
9797
+ var ARTIST_RULE_BOUNDARY = "Rules change what the next crawl takes. Everything already here stays.";
9798
+ function artistRuleWriteLine(prefix) {
9799
+ return `${prefix} \u2014 ${ARTIST_RULE_BOUNDARY.replace(/^R/, "r")}`;
9800
+ }
9801
+ function artistRuleLines(rules) {
9802
+ if (rules.length === 0) {
9803
+ return ["No artist rules."];
9804
+ }
9805
+ const rows = rules.map((rule) => [
9806
+ rule.id,
9807
+ rule.verdict,
9808
+ rule.resolvedName ?? rule.artistName,
9809
+ rule.resolvedMbid ?? rule.artistMbid,
9810
+ rule.artistSpotifyId ?? "unresolved"
9811
+ ]);
9812
+ const headings = ["ID", "VERDICT", "ARTIST", "MUSICBRAINZ", "SPOTIFY"];
9813
+ const widths = headings.map((heading, index) => Math.max(heading.length, ...rows.map((row) => row[index]?.length ?? 0)));
9814
+ const line = (row) => row.map((value, index) => value.padEnd(widths[index] ?? value.length)).join(" ").trimEnd();
9815
+ return [line(headings), line(widths.map((width) => "-".repeat(width))), ...rows.map(line)];
9816
+ }
9696
9817
  function createProgram() {
9697
9818
  const program2 = configureCommand(new Command);
9698
9819
  program2.name("fluncle").description(fluncleTagline.toLowerCase()).option("--env <local|production>", "Config profile to load (default: production)").showSuggestionAfterError(false).addHelpCommand("help [command]", "display help for command").hook("preAction", (_thisCommand, actionCommand) => {
@@ -10548,7 +10669,7 @@ JSON field reference:
10548
10669
  const { backfillLabelLineageCommand: backfillLabelLineageCommand2 } = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
10549
10670
  await runBackfillLabelLineage(options, backfillLabelLineageCommand2);
10550
10671
  });
10551
- backfill.command("recording-mbids").description("Fill MusicBrainz recording MBIDs (crawler PK strip + ISRC resolve) over tracks").option("--dry-run", "Report the eligible worklist without any vendor call or write", false).option("--limit <limit>", "Max ISRC lookups to process", "50").option("--json", "Print JSON", false).action(async (options) => {
10672
+ backfill.command("recording-mbids").description("Fill MusicBrainz recording MBIDs (crawler PK strip + ISRC resolve) over tracks").option("--dry-run", "Report the eligible worklist without any vendor call or write", false).option("--limit <limit>", "Max ISRC lookups to process", "50").option("--isrc-refresh-limit <limit>", "Max MusicBrainz ISRC re-reads to process", "25").option("--json", "Print JSON", false).action(async (options) => {
10552
10673
  const { backfillRecordingMbidsCommand: backfillRecordingMbidsCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
10553
10674
  await runBackfillRecordingMbids(options, backfillRecordingMbidsCommand2);
10554
10675
  });
@@ -10586,6 +10707,34 @@ JSON field reference:
10586
10707
  const { rankArtistsCommand: rankArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
10587
10708
  await runArtistsRank(options, rankArtistsCommand2);
10588
10709
  });
10710
+ artists.command("rule").description("Always or never take one MusicBrainz artist's records (operator)").argument("<artist-mbid>", "The MusicBrainz artist MBID").requiredOption("--verdict <verdict>", "The ruling: allow or block").option("--name <name>", "Artist name (optional; the server resolves it when omitted)").option("--json", "Print JSON", false).action(async (artistMbid, options) => {
10711
+ const { addArtistRuleCommand: addArtistRuleCommand2, artistRuleInput: artistRuleInput2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
10712
+ const rule = await addArtistRuleCommand2(artistRuleInput2(artistMbid, options.verdict, options.name));
10713
+ if (options.json) {
10714
+ printJson({ ok: true, rule });
10715
+ return;
10716
+ }
10717
+ console.log(artistRuleWriteLine(`Rule set for ${rule.resolvedName ?? rule.artistName}: ${rule.verdict.toUpperCase()}`));
10718
+ });
10719
+ artists.command("rules").description("List the global artist crawl rules").option("--json", "Print JSON", false).action(async (options) => {
10720
+ const { listArtistRulesCommand: listArtistRulesCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
10721
+ const rules = await listArtistRulesCommand2();
10722
+ if (options.json) {
10723
+ printJson({ ok: true, rules });
10724
+ return;
10725
+ }
10726
+ console.log(artistRuleLines(rules).join(`
10727
+ `));
10728
+ });
10729
+ artists.command("unrule").description("Remove one global artist crawl rule (operator)").argument("<id>", "The artist rule id").option("--json", "Print JSON", false).action(async (id, options) => {
10730
+ const { removeArtistRuleCommand: removeArtistRuleCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
10731
+ const result = await removeArtistRuleCommand2(id);
10732
+ if (options.json) {
10733
+ printJson(result);
10734
+ return;
10735
+ }
10736
+ console.log(artistRuleWriteLine(`Rule removed: ${id}`));
10737
+ });
10589
10738
  artists.command("draft-bio").description("Assemble the Worker-grounded bio prompt for an artist (the sweep's trigger)").argument("<slug>").option("--json", "Print JSON", false).allowExcessArguments().action(async (slug, options) => {
10590
10739
  const { draftArtistBioCommand: draftArtistBioCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
10591
10740
  await runEntityBioDraft("artist", slug, options, draftArtistBioCommand2);
@@ -10607,6 +10756,31 @@ JSON field reference:
10607
10756
  const { draftLabelBioCommand: draftLabelBioCommand2 } = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
10608
10757
  await runEntityBioDraft("label", slug, options, draftLabelBioCommand2);
10609
10758
  });
10759
+ labels.command("artists").description("List or replace one label's artist crawl rules").argument("<slug>", "The label's slug (an exact lbl_\u2026 id also works)").option("--replace", "Replace the complete rule set from --rules-file", false).option("--rules-file <file>", "JSON array of { artistMbid, artistName, verdict } rules").option("--json", "Print JSON", false).action(async (slug, options) => {
10760
+ const {
10761
+ listLabelArtistRulesCommand: listLabelArtistRulesCommand2,
10762
+ parseLabelArtistRulesJson: parseLabelArtistRulesJson2,
10763
+ replaceLabelArtistRulesCommand: replaceLabelArtistRulesCommand2
10764
+ } = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
10765
+ if (!options.replace && options.rulesFile !== undefined) {
10766
+ throw new Error("Pass --replace with --rules-file");
10767
+ }
10768
+ if (options.replace && options.rulesFile === undefined) {
10769
+ throw new Error("Pass --rules-file <json> with --replace");
10770
+ }
10771
+ const result = options.replace ? await replaceLabelArtistRulesCommand2(slug, parseLabelArtistRulesJson2(readFileSync7(options.rulesFile ?? "", "utf8"))) : await listLabelArtistRulesCommand2(slug);
10772
+ if (options.json) {
10773
+ printJson({ ok: true, rules: result.rules });
10774
+ return;
10775
+ }
10776
+ if (options.replace) {
10777
+ const noun = result.rules.length === 1 ? "rule" : "rules";
10778
+ console.log(artistRuleWriteLine(`${result.rules.length} ${noun} set`));
10779
+ return;
10780
+ }
10781
+ console.log(artistRuleLines(result.rules).join(`
10782
+ `));
10783
+ });
10610
10784
  labels.command("update").description("Rule on a label's crawl scope or arm a scoped re-walk (operator)").argument("<slug>", "The label's slug (an exact lbl_\u2026 id also works)").option("--seed-state <state>", "The ruling: enabled, disabled, or undecided").option("--rewalk", "Arm a scoped re-walk without changing the ruling", false).option("--json", "Print JSON", false).action(async (slug, options) => {
10611
10785
  const seedState = options.seedState;
10612
10786
  if (seedState !== undefined && seedState !== "enabled" && seedState !== "disabled" && seedState !== "undecided") {
@@ -10635,12 +10809,14 @@ JSON field reference:
10635
10809
  const {
10636
10810
  aliasWritten,
10637
10811
  canonicalSlug: canon,
10812
+ droppedRules,
10638
10813
  losingSlug: loser,
10639
10814
  reconciled,
10640
10815
  repointed
10641
10816
  } = result;
10642
10817
  console.log(`Merged ${loser} \u2192 ${canon}.`);
10643
10818
  console.log(` Re-pointed: ${repointed.tracks} track(s), ${repointed.childLabels} sublabel(s), ${repointed.aliases} alias(es).`);
10819
+ console.log(` Dropped: ${droppedRules} loser-scoped artist rule(s).`);
10644
10820
  console.log(reconciled.length > 0 ? ` Filled onto ${canon} (was empty): ${reconciled.join(", ")}.` : ` Nothing to fill \u2014 ${canon} already carried every fact.`);
10645
10821
  console.log(` Seed state resolved to: ${result.seedState}.`);
10646
10822
  console.log(` Alias written: "${aliasWritten.alias}" (${aliasWritten.aliasSlug}) \u2192 confirmed, so it can never re-mint.`);
@@ -11732,21 +11908,26 @@ async function runBackfillLabelImages(options, backfillLabelImagesCommand2) {
11732
11908
  }
11733
11909
  async function runBackfillRecordingMbids(options, backfillRecordingMbidsCommand2) {
11734
11910
  const limit = parseListLimit(options.limit);
11911
+ const isrcRefreshLimit = parseListLimit(options.isrcRefreshLimit);
11735
11912
  const resolved = [];
11736
11913
  const missed = [];
11737
11914
  const failed = [];
11915
+ const isrcRefreshed = [];
11916
+ const isrcRefreshMissed = [];
11738
11917
  let cursor;
11739
11918
  let dryRun = options.dryRun;
11740
11919
  let prefixStripped = 0;
11741
11920
  let throttled = false;
11742
11921
  while (resolved.length + missed.length + failed.length < limit) {
11743
11922
  const remaining = limit - (resolved.length + missed.length + failed.length);
11744
- const result = await backfillRecordingMbidsCommand2(remaining, options.dryRun, cursor);
11923
+ const result = await backfillRecordingMbidsCommand2(remaining, options.dryRun, cursor, isrcRefreshLimit);
11745
11924
  dryRun = result.dryRun;
11746
11925
  prefixStripped += result.prefixStripped;
11747
11926
  resolved.push(...result.resolved);
11748
11927
  missed.push(...result.missed);
11749
11928
  failed.push(...result.failed);
11929
+ isrcRefreshed.push(...result.isrcRefreshed ?? []);
11930
+ isrcRefreshMissed.push(...result.isrcRefreshMissed ?? []);
11750
11931
  if (!options.json) {
11751
11932
  const verb2 = result.dryRun ? "would resolve" : "resolved";
11752
11933
  console.log(` \u2026stripped ${result.prefixStripped} from PK; ${verb2} ${result.resolvedCount} by ISRC; ${result.missedCount} not in MusicBrainz; ${result.failedCount} failed`);
@@ -11765,6 +11946,10 @@ async function runBackfillRecordingMbids(options, backfillRecordingMbidsCommand2
11765
11946
  dryRun,
11766
11947
  failed,
11767
11948
  failedCount: failed.length,
11949
+ isrcRefreshMissed,
11950
+ isrcRefreshMissedCount: isrcRefreshMissed.length,
11951
+ isrcRefreshed,
11952
+ isrcRefreshedCount: isrcRefreshed.length,
11768
11953
  missed,
11769
11954
  missedCount: missed.length,
11770
11955
  ok: true,
@@ -11780,6 +11965,7 @@ async function runBackfillRecordingMbids(options, backfillRecordingMbidsCommand2
11780
11965
  }
11781
11966
  const verb = dryRun ? "Would fill" : "Filled";
11782
11967
  console.log(`${verb} ${prefixStripped} recording MBID(s) from crawler PKs; ${resolved.length} by ISRC; ${missed.length} not in MusicBrainz; ${failed.length} failed.`);
11968
+ console.log(`${dryRun ? "Would re-read" : "Re-read"} ${isrcRefreshed.length + isrcRefreshMissed.length} recording(s) for ISRCs \u2014 ${isrcRefreshed.length} filled, ${isrcRefreshMissed.length} still none.`);
11783
11969
  for (const item of failed) {
11784
11970
  console.log(` ${item.trackId}: ${item.error}`);
11785
11971
  }
@@ -13504,6 +13690,7 @@ function normalizeCommanderError(error) {
13504
13690
  return new Error(message);
13505
13691
  }
13506
13692
  var stringOptions = new Set([
13693
+ "--isrc-refresh-limit",
13507
13694
  "--against",
13508
13695
  "--analyzed-at",
13509
13696
  "--analyzed-from",
@@ -13553,6 +13740,7 @@ var stringOptions = new Set([
13553
13740
  "--mime",
13554
13741
  "--min-phrase-words",
13555
13742
  "--model",
13743
+ "--name",
13556
13744
  "--note",
13557
13745
  "--ok",
13558
13746
  "--order",
@@ -13568,6 +13756,7 @@ var stringOptions = new Set([
13568
13756
  "--reasoning",
13569
13757
  "--recorded-at",
13570
13758
  "--recording",
13759
+ "--rules-file",
13571
13760
  "--render",
13572
13761
  "--scene",
13573
13762
  "--scheduled-for",
@@ -13635,5 +13824,7 @@ if (__require.main == __require.module) {
13635
13824
  export {
13636
13825
  parseTelemetryTimestamp,
13637
13826
  labelUpdateLines,
13638
- createProgram
13827
+ createProgram,
13828
+ artistRuleLines,
13829
+ ARTIST_RULE_BOUNDARY
13639
13830
  };
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.219.0"
34
+ "version": "0.221.0"
35
35
  }