fluncle 0.219.0 → 0.220.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.
- package/bin/fluncle.mjs +185 -8
- 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.
|
|
613
|
+
currentVersion = "0.220.0".trim() ? "0.220.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.
|
|
2455
|
+
currentVersion2 = "0.220.0".trim() ? "0.220.0".trim() : "0.1.0";
|
|
2456
2456
|
});
|
|
2457
2457
|
|
|
2458
2458
|
// ../../packages/registry/src/index.ts
|
|
@@ -7016,15 +7016,48 @@ var init_admin_reach = __esm(() => {
|
|
|
7016
7016
|
var exports_admin_artists = {};
|
|
7017
7017
|
__export(exports_admin_artists, {
|
|
7018
7018
|
resolveArtistCommand: () => resolveArtistCommand,
|
|
7019
|
+
removeArtistRuleCommand: () => removeArtistRuleCommand,
|
|
7019
7020
|
rankArtistsCommand: () => rankArtistsCommand,
|
|
7020
7021
|
listArtistsCommand: () => listArtistsCommand,
|
|
7022
|
+
listArtistRulesCommand: () => listArtistRulesCommand,
|
|
7021
7023
|
draftArtistBioCommand: () => draftArtistBioCommand,
|
|
7022
7024
|
describeArtistCommand: () => describeArtistCommand,
|
|
7023
7025
|
buildBioBody: () => buildBioBody,
|
|
7024
7026
|
backfillArtistsCommand: () => backfillArtistsCommand,
|
|
7025
7027
|
backfillArtistImagesCommand: () => backfillArtistImagesCommand,
|
|
7026
|
-
artistsBioQueueCommand: () => artistsBioQueueCommand
|
|
7028
|
+
artistsBioQueueCommand: () => artistsBioQueueCommand,
|
|
7029
|
+
artistRuleInput: () => artistRuleInput,
|
|
7030
|
+
addArtistRuleCommand: () => addArtistRuleCommand
|
|
7027
7031
|
});
|
|
7032
|
+
function artistRuleInput(artistMbid, verdict, artistName) {
|
|
7033
|
+
const cleanMbid = artistMbid.trim();
|
|
7034
|
+
if (!MBID_PATTERN.test(cleanMbid)) {
|
|
7035
|
+
throw new Error("Artist MBID must be a MusicBrainz artist MBID");
|
|
7036
|
+
}
|
|
7037
|
+
if (verdict !== "allow" && verdict !== "block") {
|
|
7038
|
+
throw new Error("Pass --verdict allow|block");
|
|
7039
|
+
}
|
|
7040
|
+
const cleanName = artistName?.trim();
|
|
7041
|
+
if (artistName !== undefined && !cleanName) {
|
|
7042
|
+
throw new Error("--name must be a non-empty artist name");
|
|
7043
|
+
}
|
|
7044
|
+
return {
|
|
7045
|
+
artistMbid: cleanMbid.toLowerCase(),
|
|
7046
|
+
...cleanName ? { artistName: cleanName } : {},
|
|
7047
|
+
verdict
|
|
7048
|
+
};
|
|
7049
|
+
}
|
|
7050
|
+
async function listArtistRulesCommand() {
|
|
7051
|
+
const response = await adminApiGet("/api/v1/admin/artist-rules");
|
|
7052
|
+
return response.rules;
|
|
7053
|
+
}
|
|
7054
|
+
async function addArtistRuleCommand(input) {
|
|
7055
|
+
const response = await adminApiPost("/api/v1/admin/artist-rules", input);
|
|
7056
|
+
return response.rule;
|
|
7057
|
+
}
|
|
7058
|
+
async function removeArtistRuleCommand(id) {
|
|
7059
|
+
return adminApiDelete(`/api/v1/admin/artist-rules/${encodeURIComponent(id)}`);
|
|
7060
|
+
}
|
|
7028
7061
|
function buildBioBody(options) {
|
|
7029
7062
|
const body = { bio: options.bio };
|
|
7030
7063
|
if (options.dryRun) {
|
|
@@ -7077,8 +7110,10 @@ async function backfillArtistsCommand(limit, dryRun, cursor) {
|
|
|
7077
7110
|
}
|
|
7078
7111
|
return adminApiPost(`/api/v1/admin/backfill/artists?${params.toString()}`);
|
|
7079
7112
|
}
|
|
7113
|
+
var MBID_PATTERN;
|
|
7080
7114
|
var init_admin_artists = __esm(() => {
|
|
7081
7115
|
init_api();
|
|
7116
|
+
MBID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7082
7117
|
});
|
|
7083
7118
|
|
|
7084
7119
|
// src/commands/admin-artists.ts
|
|
@@ -7103,23 +7138,84 @@ var init_admin_artists2 = __esm(() => {
|
|
|
7103
7138
|
var exports_admin_labels = {};
|
|
7104
7139
|
__export(exports_admin_labels, {
|
|
7105
7140
|
updateLabelCommand: () => updateLabelCommand,
|
|
7141
|
+
replaceLabelArtistRulesCommand: () => replaceLabelArtistRulesCommand,
|
|
7142
|
+
parseLabelArtistRulesJson: () => parseLabelArtistRulesJson,
|
|
7106
7143
|
mergeLabelCommand: () => mergeLabelCommand,
|
|
7144
|
+
listLabelArtistRulesCommand: () => listLabelArtistRulesCommand,
|
|
7107
7145
|
labelsBioQueueCommand: () => labelsBioQueueCommand,
|
|
7108
7146
|
draftLabelBioCommand: () => draftLabelBioCommand,
|
|
7109
7147
|
describeLabelCommand: () => describeLabelCommand,
|
|
7110
7148
|
backfillLabelLineageCommand: () => backfillLabelLineageCommand,
|
|
7111
7149
|
backfillLabelImagesCommand: () => backfillLabelImagesCommand
|
|
7112
7150
|
});
|
|
7113
|
-
|
|
7114
|
-
|
|
7115
|
-
|
|
7151
|
+
function requireArtistMbid(value, at) {
|
|
7152
|
+
if (typeof value !== "string" || !MBID_PATTERN2.test(value.trim())) {
|
|
7153
|
+
throw new Error(`${at}.artistMbid must be a MusicBrainz artist MBID`);
|
|
7154
|
+
}
|
|
7155
|
+
return value.trim().toLowerCase();
|
|
7116
7156
|
}
|
|
7117
|
-
|
|
7157
|
+
function requireArtistName(value, at) {
|
|
7158
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
7159
|
+
throw new Error(`${at}.artistName must be a non-empty string`);
|
|
7160
|
+
}
|
|
7161
|
+
return value.trim();
|
|
7162
|
+
}
|
|
7163
|
+
function requireArtistVerdict(value, at) {
|
|
7164
|
+
if (value === "allow" || value === "block") {
|
|
7165
|
+
return value;
|
|
7166
|
+
}
|
|
7167
|
+
throw new Error(`${at}.verdict must be 'allow' or 'block'`);
|
|
7168
|
+
}
|
|
7169
|
+
function parseLabelArtistRulesJson(source) {
|
|
7170
|
+
let parsed;
|
|
7171
|
+
try {
|
|
7172
|
+
parsed = JSON.parse(source);
|
|
7173
|
+
} catch {
|
|
7174
|
+
throw new Error("Rules file must contain valid JSON");
|
|
7175
|
+
}
|
|
7176
|
+
if (!Array.isArray(parsed)) {
|
|
7177
|
+
throw new Error("Rules file must contain a JSON array");
|
|
7178
|
+
}
|
|
7179
|
+
if (parsed.length > 100) {
|
|
7180
|
+
throw new Error("Rules file may contain at most 100 artist rules");
|
|
7181
|
+
}
|
|
7182
|
+
return parsed.map((entry, index) => {
|
|
7183
|
+
const at = `Rules file entry ${index + 1}`;
|
|
7184
|
+
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
7185
|
+
throw new Error(`${at} must be an object`);
|
|
7186
|
+
}
|
|
7187
|
+
const value = entry;
|
|
7188
|
+
return {
|
|
7189
|
+
artistMbid: requireArtistMbid(value.artistMbid, at),
|
|
7190
|
+
artistName: requireArtistName(value.artistName, at),
|
|
7191
|
+
verdict: requireArtistVerdict(value.verdict, at)
|
|
7192
|
+
};
|
|
7193
|
+
});
|
|
7194
|
+
}
|
|
7195
|
+
async function resolveLabel(slugOrId) {
|
|
7118
7196
|
const { labels } = await adminApiGet("/api/v1/admin/labels");
|
|
7119
7197
|
const match = labels.find((label) => label.slug === slugOrId || label.id === slugOrId);
|
|
7120
7198
|
if (!match) {
|
|
7121
7199
|
throw new Error(`No label with slug or id '${slugOrId}' — check \`fluncle labels\``);
|
|
7122
7200
|
}
|
|
7201
|
+
return match;
|
|
7202
|
+
}
|
|
7203
|
+
async function listLabelArtistRulesCommand(slugOrId) {
|
|
7204
|
+
const label = await resolveLabel(slugOrId);
|
|
7205
|
+
const response = await adminApiGet(`/api/v1/admin/labels/${encodeURIComponent(label.id)}/artists`);
|
|
7206
|
+
return { label, rules: response.rules };
|
|
7207
|
+
}
|
|
7208
|
+
async function replaceLabelArtistRulesCommand(slugOrId, rules) {
|
|
7209
|
+
const label = await resolveLabel(slugOrId);
|
|
7210
|
+
const response = await adminApiPut(`/api/v1/admin/labels/${encodeURIComponent(label.id)}/artists`, { rules });
|
|
7211
|
+
return { label, rules: response.rules };
|
|
7212
|
+
}
|
|
7213
|
+
async function mergeLabelCommand(losingSlug, canonicalSlug) {
|
|
7214
|
+
const response = await adminApiPost(`/api/v1/admin/labels/${encodeURIComponent(losingSlug)}/merge`, { canonicalSlug });
|
|
7215
|
+
return response.result;
|
|
7216
|
+
}
|
|
7217
|
+
async function updateLabelCommand(slugOrId, seedState, rewalk = false) {
|
|
7218
|
+
const match = await resolveLabel(slugOrId);
|
|
7123
7219
|
const response = await adminApiPatch(`/api/v1/admin/labels/${encodeURIComponent(match.id)}`, {
|
|
7124
7220
|
...rewalk ? { rewalk: true } : {},
|
|
7125
7221
|
...seedState === undefined ? {} : { seedState }
|
|
@@ -7150,9 +7246,11 @@ async function backfillLabelLineageCommand(limit, dryRun, cursor) {
|
|
|
7150
7246
|
}
|
|
7151
7247
|
return adminApiPost(`/api/v1/admin/backfill/label-lineage?${params.toString()}`);
|
|
7152
7248
|
}
|
|
7249
|
+
var MBID_PATTERN2;
|
|
7153
7250
|
var init_admin_labels = __esm(() => {
|
|
7154
7251
|
init_api();
|
|
7155
7252
|
init_admin_artists2();
|
|
7253
|
+
MBID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
7156
7254
|
});
|
|
7157
7255
|
|
|
7158
7256
|
// src/commands/admin-covers.ts
|
|
@@ -9693,6 +9791,26 @@ function labelUpdateLines(label, options) {
|
|
|
9693
9791
|
}
|
|
9694
9792
|
return lines;
|
|
9695
9793
|
}
|
|
9794
|
+
var ARTIST_RULE_BOUNDARY = "Rules change what the next crawl takes. Everything already here stays.";
|
|
9795
|
+
function artistRuleWriteLine(prefix) {
|
|
9796
|
+
return `${prefix} \u2014 ${ARTIST_RULE_BOUNDARY.replace(/^R/, "r")}`;
|
|
9797
|
+
}
|
|
9798
|
+
function artistRuleLines(rules) {
|
|
9799
|
+
if (rules.length === 0) {
|
|
9800
|
+
return ["No artist rules."];
|
|
9801
|
+
}
|
|
9802
|
+
const rows = rules.map((rule) => [
|
|
9803
|
+
rule.id,
|
|
9804
|
+
rule.verdict,
|
|
9805
|
+
rule.resolvedName ?? rule.artistName,
|
|
9806
|
+
rule.resolvedMbid ?? rule.artistMbid,
|
|
9807
|
+
rule.artistSpotifyId ?? "unresolved"
|
|
9808
|
+
]);
|
|
9809
|
+
const headings = ["ID", "VERDICT", "ARTIST", "MUSICBRAINZ", "SPOTIFY"];
|
|
9810
|
+
const widths = headings.map((heading, index) => Math.max(heading.length, ...rows.map((row) => row[index]?.length ?? 0)));
|
|
9811
|
+
const line = (row) => row.map((value, index) => value.padEnd(widths[index] ?? value.length)).join(" ").trimEnd();
|
|
9812
|
+
return [line(headings), line(widths.map((width) => "-".repeat(width))), ...rows.map(line)];
|
|
9813
|
+
}
|
|
9696
9814
|
function createProgram() {
|
|
9697
9815
|
const program2 = configureCommand(new Command);
|
|
9698
9816
|
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) => {
|
|
@@ -10586,6 +10704,34 @@ JSON field reference:
|
|
|
10586
10704
|
const { rankArtistsCommand: rankArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
|
|
10587
10705
|
await runArtistsRank(options, rankArtistsCommand2);
|
|
10588
10706
|
});
|
|
10707
|
+
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) => {
|
|
10708
|
+
const { addArtistRuleCommand: addArtistRuleCommand2, artistRuleInput: artistRuleInput2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
|
|
10709
|
+
const rule = await addArtistRuleCommand2(artistRuleInput2(artistMbid, options.verdict, options.name));
|
|
10710
|
+
if (options.json) {
|
|
10711
|
+
printJson({ ok: true, rule });
|
|
10712
|
+
return;
|
|
10713
|
+
}
|
|
10714
|
+
console.log(artistRuleWriteLine(`Rule set for ${rule.resolvedName ?? rule.artistName}: ${rule.verdict.toUpperCase()}`));
|
|
10715
|
+
});
|
|
10716
|
+
artists.command("rules").description("List the global artist crawl rules").option("--json", "Print JSON", false).action(async (options) => {
|
|
10717
|
+
const { listArtistRulesCommand: listArtistRulesCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
|
|
10718
|
+
const rules = await listArtistRulesCommand2();
|
|
10719
|
+
if (options.json) {
|
|
10720
|
+
printJson({ ok: true, rules });
|
|
10721
|
+
return;
|
|
10722
|
+
}
|
|
10723
|
+
console.log(artistRuleLines(rules).join(`
|
|
10724
|
+
`));
|
|
10725
|
+
});
|
|
10726
|
+
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) => {
|
|
10727
|
+
const { removeArtistRuleCommand: removeArtistRuleCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
|
|
10728
|
+
const result = await removeArtistRuleCommand2(id);
|
|
10729
|
+
if (options.json) {
|
|
10730
|
+
printJson(result);
|
|
10731
|
+
return;
|
|
10732
|
+
}
|
|
10733
|
+
console.log(artistRuleWriteLine(`Rule removed: ${id}`));
|
|
10734
|
+
});
|
|
10589
10735
|
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
10736
|
const { draftArtistBioCommand: draftArtistBioCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
|
|
10591
10737
|
await runEntityBioDraft("artist", slug, options, draftArtistBioCommand2);
|
|
@@ -10607,6 +10753,31 @@ JSON field reference:
|
|
|
10607
10753
|
const { draftLabelBioCommand: draftLabelBioCommand2 } = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
|
|
10608
10754
|
await runEntityBioDraft("label", slug, options, draftLabelBioCommand2);
|
|
10609
10755
|
});
|
|
10756
|
+
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) => {
|
|
10757
|
+
const {
|
|
10758
|
+
listLabelArtistRulesCommand: listLabelArtistRulesCommand2,
|
|
10759
|
+
parseLabelArtistRulesJson: parseLabelArtistRulesJson2,
|
|
10760
|
+
replaceLabelArtistRulesCommand: replaceLabelArtistRulesCommand2
|
|
10761
|
+
} = await Promise.resolve().then(() => (init_admin_labels(), exports_admin_labels));
|
|
10762
|
+
if (!options.replace && options.rulesFile !== undefined) {
|
|
10763
|
+
throw new Error("Pass --replace with --rules-file");
|
|
10764
|
+
}
|
|
10765
|
+
if (options.replace && options.rulesFile === undefined) {
|
|
10766
|
+
throw new Error("Pass --rules-file <json> with --replace");
|
|
10767
|
+
}
|
|
10768
|
+
const result = options.replace ? await replaceLabelArtistRulesCommand2(slug, parseLabelArtistRulesJson2(readFileSync7(options.rulesFile ?? "", "utf8"))) : await listLabelArtistRulesCommand2(slug);
|
|
10769
|
+
if (options.json) {
|
|
10770
|
+
printJson({ ok: true, rules: result.rules });
|
|
10771
|
+
return;
|
|
10772
|
+
}
|
|
10773
|
+
if (options.replace) {
|
|
10774
|
+
const noun = result.rules.length === 1 ? "rule" : "rules";
|
|
10775
|
+
console.log(artistRuleWriteLine(`${result.rules.length} ${noun} set`));
|
|
10776
|
+
return;
|
|
10777
|
+
}
|
|
10778
|
+
console.log(artistRuleLines(result.rules).join(`
|
|
10779
|
+
`));
|
|
10780
|
+
});
|
|
10610
10781
|
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
10782
|
const seedState = options.seedState;
|
|
10612
10783
|
if (seedState !== undefined && seedState !== "enabled" && seedState !== "disabled" && seedState !== "undecided") {
|
|
@@ -10635,12 +10806,14 @@ JSON field reference:
|
|
|
10635
10806
|
const {
|
|
10636
10807
|
aliasWritten,
|
|
10637
10808
|
canonicalSlug: canon,
|
|
10809
|
+
droppedRules,
|
|
10638
10810
|
losingSlug: loser,
|
|
10639
10811
|
reconciled,
|
|
10640
10812
|
repointed
|
|
10641
10813
|
} = result;
|
|
10642
10814
|
console.log(`Merged ${loser} \u2192 ${canon}.`);
|
|
10643
10815
|
console.log(` Re-pointed: ${repointed.tracks} track(s), ${repointed.childLabels} sublabel(s), ${repointed.aliases} alias(es).`);
|
|
10816
|
+
console.log(` Dropped: ${droppedRules} loser-scoped artist rule(s).`);
|
|
10644
10817
|
console.log(reconciled.length > 0 ? ` Filled onto ${canon} (was empty): ${reconciled.join(", ")}.` : ` Nothing to fill \u2014 ${canon} already carried every fact.`);
|
|
10645
10818
|
console.log(` Seed state resolved to: ${result.seedState}.`);
|
|
10646
10819
|
console.log(` Alias written: "${aliasWritten.alias}" (${aliasWritten.aliasSlug}) \u2192 confirmed, so it can never re-mint.`);
|
|
@@ -13553,6 +13726,7 @@ var stringOptions = new Set([
|
|
|
13553
13726
|
"--mime",
|
|
13554
13727
|
"--min-phrase-words",
|
|
13555
13728
|
"--model",
|
|
13729
|
+
"--name",
|
|
13556
13730
|
"--note",
|
|
13557
13731
|
"--ok",
|
|
13558
13732
|
"--order",
|
|
@@ -13568,6 +13742,7 @@ var stringOptions = new Set([
|
|
|
13568
13742
|
"--reasoning",
|
|
13569
13743
|
"--recorded-at",
|
|
13570
13744
|
"--recording",
|
|
13745
|
+
"--rules-file",
|
|
13571
13746
|
"--render",
|
|
13572
13747
|
"--scene",
|
|
13573
13748
|
"--scheduled-for",
|
|
@@ -13635,5 +13810,7 @@ if (__require.main == __require.module) {
|
|
|
13635
13810
|
export {
|
|
13636
13811
|
parseTelemetryTimestamp,
|
|
13637
13812
|
labelUpdateLines,
|
|
13638
|
-
createProgram
|
|
13813
|
+
createProgram,
|
|
13814
|
+
artistRuleLines,
|
|
13815
|
+
ARTIST_RULE_BOUNDARY
|
|
13639
13816
|
};
|
package/package.json
CHANGED