fluncle 0.52.0 → 0.54.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 +141 -10
- package/package.json +1 -1
package/bin/fluncle.mjs
CHANGED
|
@@ -423,7 +423,7 @@ function parseVersion(version) {
|
|
|
423
423
|
var currentVersion;
|
|
424
424
|
var init_version = __esm(() => {
|
|
425
425
|
init_output();
|
|
426
|
-
currentVersion = "0.
|
|
426
|
+
currentVersion = "0.54.0".trim() ? "0.54.0".trim() : "0.1.0";
|
|
427
427
|
});
|
|
428
428
|
|
|
429
429
|
// src/update-notifier.ts
|
|
@@ -1912,7 +1912,7 @@ function parseVersion2(version) {
|
|
|
1912
1912
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
1913
1913
|
var init_version2 = __esm(() => {
|
|
1914
1914
|
init_output();
|
|
1915
|
-
currentVersion2 = "0.
|
|
1915
|
+
currentVersion2 = "0.54.0".trim() ? "0.54.0".trim() : "0.1.0";
|
|
1916
1916
|
});
|
|
1917
1917
|
|
|
1918
1918
|
// src/commands/track.ts
|
|
@@ -2540,6 +2540,62 @@ var init_mixtape_youtube2 = __esm(() => {
|
|
|
2540
2540
|
init_output();
|
|
2541
2541
|
});
|
|
2542
2542
|
|
|
2543
|
+
// src/commands/newsletter.ts
|
|
2544
|
+
var exports_newsletter = {};
|
|
2545
|
+
__export(exports_newsletter, {
|
|
2546
|
+
newsletterUpdateCommand: () => newsletterUpdateCommand,
|
|
2547
|
+
newsletterSendCommand: () => newsletterSendCommand,
|
|
2548
|
+
newsletterListCommand: () => newsletterListCommand,
|
|
2549
|
+
newsletterDraftCommand: () => newsletterDraftCommand
|
|
2550
|
+
});
|
|
2551
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
2552
|
+
function buildBody2(options, { requireContent }) {
|
|
2553
|
+
const body = {};
|
|
2554
|
+
if (options.contentFile !== undefined) {
|
|
2555
|
+
body.contentJson = readContentFile(options.contentFile);
|
|
2556
|
+
} else if (requireContent) {
|
|
2557
|
+
throw new CliError2("missing_content", "A draft needs the structured content payload via --content-file <edition.json>");
|
|
2558
|
+
}
|
|
2559
|
+
if (options.subject !== undefined) {
|
|
2560
|
+
body.subject = options.subject;
|
|
2561
|
+
}
|
|
2562
|
+
if (options.windowSince !== undefined) {
|
|
2563
|
+
body.windowSince = options.windowSince;
|
|
2564
|
+
}
|
|
2565
|
+
if (options.windowUntil !== undefined) {
|
|
2566
|
+
body.windowUntil = options.windowUntil;
|
|
2567
|
+
}
|
|
2568
|
+
return body;
|
|
2569
|
+
}
|
|
2570
|
+
function readContentFile(filePath) {
|
|
2571
|
+
if (!existsSync2(filePath)) {
|
|
2572
|
+
throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
|
|
2573
|
+
}
|
|
2574
|
+
const text = readFileSync2(filePath, "utf-8");
|
|
2575
|
+
try {
|
|
2576
|
+
return JSON.parse(text);
|
|
2577
|
+
} catch (error) {
|
|
2578
|
+
throw new CliError2("invalid_content_json", `Content JSON parse failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2581
|
+
async function newsletterDraftCommand(options) {
|
|
2582
|
+
return adminApiPost("/api/admin/newsletter/editions", buildBody2(options, { requireContent: true }));
|
|
2583
|
+
}
|
|
2584
|
+
async function newsletterUpdateCommand(id, options) {
|
|
2585
|
+
return adminApiPatch(`/api/admin/newsletter/editions/${encodeURIComponent(id)}`, buildBody2(options, { requireContent: false }));
|
|
2586
|
+
}
|
|
2587
|
+
async function newsletterSendCommand(id) {
|
|
2588
|
+
return adminApiPost(`/api/admin/newsletter/editions/${encodeURIComponent(id)}/send`);
|
|
2589
|
+
}
|
|
2590
|
+
async function newsletterListCommand() {
|
|
2591
|
+
const response = await adminApiGet("/api/admin/newsletter/editions");
|
|
2592
|
+
return response.editions;
|
|
2593
|
+
}
|
|
2594
|
+
var init_newsletter = __esm(() => {
|
|
2595
|
+
init_api();
|
|
2596
|
+
init_output();
|
|
2597
|
+
});
|
|
2598
|
+
|
|
2543
2599
|
// src/commands/add.ts
|
|
2544
2600
|
async function addCommand2(spotifyUrl, options) {
|
|
2545
2601
|
const result = await adminApiPost("/api/admin/tracks", {
|
|
@@ -3149,7 +3205,7 @@ var COORD_FALLBACK2 = "—";
|
|
|
3149
3205
|
var init_format2 = () => {};
|
|
3150
3206
|
|
|
3151
3207
|
// src/cli.ts
|
|
3152
|
-
import { existsSync as
|
|
3208
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
3153
3209
|
import path2 from "path";
|
|
3154
3210
|
|
|
3155
3211
|
// ../../node_modules/commander/lib/error.js
|
|
@@ -5476,6 +5532,26 @@ function addAdminCommands(program2) {
|
|
|
5476
5532
|
const { publishYoutubeCommand: publishYoutubeCommand3 } = await Promise.resolve().then(() => (init_mixtape_youtube2(), exports_mixtape_youtube2));
|
|
5477
5533
|
await runMixtapePublishYoutube(idOrLogId, options, publishYoutubeCommand3);
|
|
5478
5534
|
});
|
|
5535
|
+
const adminNewsletter = configureCommand(admin.command("newsletter").description("Newsletter edition commands"));
|
|
5536
|
+
adminNewsletter.action(() => {
|
|
5537
|
+
adminNewsletter.outputHelp();
|
|
5538
|
+
});
|
|
5539
|
+
adminNewsletter.command("draft").description("Persist a newsletter edition draft (the agent authors it, you send it)").option("--content-file <file>", "Structured edition content payload (JSON)").option("--subject <text>", "Email subject line").option("--window-since <date>", "Discovery-window start (ISO)").option("--window-until <date>", "Discovery-window end (ISO)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
5540
|
+
const { newsletterDraftCommand: newsletterDraftCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
|
|
5541
|
+
await runNewsletterDraft(options, newsletterDraftCommand2);
|
|
5542
|
+
});
|
|
5543
|
+
adminNewsletter.command("update").description("Update a draft edition's payload, subject, or window").argument("[id]").option("--content-file <file>", "Structured edition content payload (JSON)").option("--subject <text>", "Email subject line").option("--window-since <date>", "Discovery-window start (ISO)").option("--window-until <date>", "Discovery-window end (ISO)").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
|
|
5544
|
+
const { newsletterUpdateCommand: newsletterUpdateCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
|
|
5545
|
+
await runNewsletterUpdate(id, options, newsletterUpdateCommand2);
|
|
5546
|
+
});
|
|
5547
|
+
adminNewsletter.command("send").description("Send an edition \u2014 OPERATOR only (Resend broadcast + mint the number)").argument("[id]").option("--json", "Print JSON", false).allowExcessArguments().action(async (id, options) => {
|
|
5548
|
+
const { newsletterSendCommand: newsletterSendCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
|
|
5549
|
+
await runNewsletterSend(id, options, newsletterSendCommand2);
|
|
5550
|
+
});
|
|
5551
|
+
adminNewsletter.command("list").description("List every edition including drafts (the cron's miss-recovery read)").option("--json", "Print JSON", false).allowExcessArguments().action(async (options) => {
|
|
5552
|
+
const { newsletterListCommand: newsletterListCommand2 } = await Promise.resolve().then(() => (init_newsletter(), exports_newsletter));
|
|
5553
|
+
await runNewsletterList(options, newsletterListCommand2);
|
|
5554
|
+
});
|
|
5479
5555
|
const submissions = configureCommand(admin.command("submissions").description("Review listener submissions"));
|
|
5480
5556
|
submissions.option("--json", "Print JSON", false).action(async (options) => {
|
|
5481
5557
|
const { listSubmissionsCommand: listSubmissionsCommand2 } = await Promise.resolve().then(() => (init_submissions(), exports_submissions));
|
|
@@ -5555,7 +5631,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
|
|
|
5555
5631
|
console.log(` mime: ${result.mime}`);
|
|
5556
5632
|
}
|
|
5557
5633
|
async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
|
|
5558
|
-
const script = options.scriptFile ?
|
|
5634
|
+
const script = options.scriptFile ? readFileSync3(options.scriptFile, "utf8") : options.script;
|
|
5559
5635
|
if (!idOrLogId || !script || !script.trim()) {
|
|
5560
5636
|
throw new Error("Usage: fluncle admin tracks observe <track_id|log_id> (--script <text> | --script-file <file>) [--voice-id <id>] [--model <model>] [--duration-ms <ms>] [--context-note <text>] [--json]");
|
|
5561
5637
|
}
|
|
@@ -5631,6 +5707,7 @@ async function runBackfillLastfm(options, backfillLastfmCommand2) {
|
|
|
5631
5707
|
const limit = parseListLimit(options.limit);
|
|
5632
5708
|
const loved = [];
|
|
5633
5709
|
const failed = [];
|
|
5710
|
+
const skipped = [];
|
|
5634
5711
|
let cursor;
|
|
5635
5712
|
let dryRun = options.dryRun;
|
|
5636
5713
|
while (loved.length + failed.length < limit) {
|
|
@@ -5639,9 +5716,10 @@ async function runBackfillLastfm(options, backfillLastfmCommand2) {
|
|
|
5639
5716
|
dryRun = result.dryRun;
|
|
5640
5717
|
loved.push(...result.loved);
|
|
5641
5718
|
failed.push(...result.failed);
|
|
5719
|
+
skipped.push(...result.skipped);
|
|
5642
5720
|
if (!options.json) {
|
|
5643
5721
|
const verb2 = result.dryRun ? "Would love" : "Loved";
|
|
5644
|
-
console.log(` \u2026${verb2.toLowerCase()} ${result.lovedCount}; ${result.failedCount} failed`);
|
|
5722
|
+
console.log(` \u2026${verb2.toLowerCase()} ${result.lovedCount}; ${result.failedCount} failed; ${result.skippedCount} skipped`);
|
|
5645
5723
|
}
|
|
5646
5724
|
if (result.nextCursor === null) {
|
|
5647
5725
|
break;
|
|
@@ -5655,12 +5733,14 @@ async function runBackfillLastfm(options, backfillLastfmCommand2) {
|
|
|
5655
5733
|
failedCount: failed.length,
|
|
5656
5734
|
loved,
|
|
5657
5735
|
lovedCount: loved.length,
|
|
5658
|
-
ok: true
|
|
5736
|
+
ok: true,
|
|
5737
|
+
skipped,
|
|
5738
|
+
skippedCount: skipped.length
|
|
5659
5739
|
});
|
|
5660
5740
|
return;
|
|
5661
5741
|
}
|
|
5662
5742
|
const verb = dryRun ? "Would love" : "Loved";
|
|
5663
|
-
console.log(`${verb} ${loved.length} finding(s) on Last.fm; ${failed.length} failed.`);
|
|
5743
|
+
console.log(`${verb} ${loved.length} finding(s) on Last.fm; ${failed.length} failed; ${skipped.length} skipped.`);
|
|
5664
5744
|
for (const logId of loved) {
|
|
5665
5745
|
console.log(` ${logId}`);
|
|
5666
5746
|
}
|
|
@@ -5672,6 +5752,7 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
|
|
|
5672
5752
|
const limit = parseListLimit(options.limit);
|
|
5673
5753
|
const resolved = [];
|
|
5674
5754
|
const unresolved = [];
|
|
5755
|
+
const skipped = [];
|
|
5675
5756
|
let cursor;
|
|
5676
5757
|
let dryRun = options.dryRun;
|
|
5677
5758
|
while (resolved.length + unresolved.length < limit) {
|
|
@@ -5680,9 +5761,10 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
|
|
|
5680
5761
|
dryRun = result.dryRun;
|
|
5681
5762
|
resolved.push(...result.resolved);
|
|
5682
5763
|
unresolved.push(...result.unresolved);
|
|
5764
|
+
skipped.push(...result.skipped);
|
|
5683
5765
|
if (!options.json) {
|
|
5684
5766
|
const verb2 = result.dryRun ? "would resolve" : "resolved";
|
|
5685
|
-
console.log(` \u2026${verb2} ${result.resolvedCount}; ${result.unresolvedCount} unresolved`);
|
|
5767
|
+
console.log(` \u2026${verb2} ${result.resolvedCount}; ${result.unresolvedCount} unresolved; ${result.skippedCount} skipped`);
|
|
5686
5768
|
}
|
|
5687
5769
|
if (result.nextCursor === null) {
|
|
5688
5770
|
break;
|
|
@@ -5695,13 +5777,15 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
|
|
|
5695
5777
|
ok: true,
|
|
5696
5778
|
resolved,
|
|
5697
5779
|
resolvedCount: resolved.length,
|
|
5780
|
+
skipped,
|
|
5781
|
+
skippedCount: skipped.length,
|
|
5698
5782
|
unresolved,
|
|
5699
5783
|
unresolvedCount: unresolved.length
|
|
5700
5784
|
});
|
|
5701
5785
|
return;
|
|
5702
5786
|
}
|
|
5703
5787
|
const verb = dryRun ? "Would resolve" : "Resolved";
|
|
5704
|
-
console.log(`${verb} ${resolved.length} Discogs release id(s); ${unresolved.length} unresolved.`);
|
|
5788
|
+
console.log(`${verb} ${resolved.length} Discogs release id(s); ${unresolved.length} unresolved; ${skipped.length} skipped.`);
|
|
5705
5789
|
for (const item of resolved) {
|
|
5706
5790
|
const master = item.masterId ? ` (master ${item.masterId})` : "";
|
|
5707
5791
|
console.log(` ${item.logId}: release ${item.releaseId}${master}`);
|
|
@@ -5717,7 +5801,7 @@ async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
|
|
|
5717
5801
|
return;
|
|
5718
5802
|
}
|
|
5719
5803
|
const candidate = path2.join(dir, name);
|
|
5720
|
-
return
|
|
5804
|
+
return existsSync3(candidate) ? candidate : undefined;
|
|
5721
5805
|
};
|
|
5722
5806
|
const resolveFile = (explicit, name) => {
|
|
5723
5807
|
if (explicit) {
|
|
@@ -5983,6 +6067,53 @@ function formatCue(ms) {
|
|
|
5983
6067
|
}
|
|
5984
6068
|
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
|
|
5985
6069
|
}
|
|
6070
|
+
async function runNewsletterDraft(options, newsletterDraftCommand2) {
|
|
6071
|
+
const result = await newsletterDraftCommand2(options);
|
|
6072
|
+
if (options.json) {
|
|
6073
|
+
printJson(result);
|
|
6074
|
+
return;
|
|
6075
|
+
}
|
|
6076
|
+
console.log(`Drafted edition ${result.edition.id}. It stays a draft until the operator sends it.`);
|
|
6077
|
+
}
|
|
6078
|
+
async function runNewsletterUpdate(id, options, newsletterUpdateCommand2) {
|
|
6079
|
+
if (!id) {
|
|
6080
|
+
throw new Error("Missing edition id. Usage: fluncle admin newsletter update <id>");
|
|
6081
|
+
}
|
|
6082
|
+
const result = await newsletterUpdateCommand2(id, options);
|
|
6083
|
+
if (options.json) {
|
|
6084
|
+
printJson(result);
|
|
6085
|
+
return;
|
|
6086
|
+
}
|
|
6087
|
+
console.log(`Saved draft ${result.edition.id}.`);
|
|
6088
|
+
}
|
|
6089
|
+
async function runNewsletterSend(id, options, newsletterSendCommand2) {
|
|
6090
|
+
if (!id) {
|
|
6091
|
+
throw new Error("Missing edition id. Usage: fluncle admin newsletter send <id>");
|
|
6092
|
+
}
|
|
6093
|
+
const result = await newsletterSendCommand2(id);
|
|
6094
|
+
if (options.json) {
|
|
6095
|
+
printJson(result);
|
|
6096
|
+
return;
|
|
6097
|
+
}
|
|
6098
|
+
const number = result.edition.number;
|
|
6099
|
+
console.log(number === undefined ? `Sent edition ${result.edition.id}.` : `Sent edition #${number} \u2014 it's out to the list and in the archive.`);
|
|
6100
|
+
}
|
|
6101
|
+
async function runNewsletterList(options, newsletterListCommand2) {
|
|
6102
|
+
const editions = await newsletterListCommand2();
|
|
6103
|
+
if (options.json) {
|
|
6104
|
+
printJson({ editions, ok: true });
|
|
6105
|
+
return;
|
|
6106
|
+
}
|
|
6107
|
+
if (editions.length === 0) {
|
|
6108
|
+
console.log("No editions yet.");
|
|
6109
|
+
return;
|
|
6110
|
+
}
|
|
6111
|
+
for (const edition of editions) {
|
|
6112
|
+
const label = edition.number === undefined ? "draft" : `#${edition.number}`;
|
|
6113
|
+
const subject = edition.subject ?? "(no subject)";
|
|
6114
|
+
console.log(`${label} ${edition.status} ${edition.id} ${subject}`);
|
|
6115
|
+
}
|
|
6116
|
+
}
|
|
5986
6117
|
async function runAdd(spotifyUrl, options, addCommand3) {
|
|
5987
6118
|
if (!spotifyUrl) {
|
|
5988
6119
|
throw new Error("Missing Spotify track URL");
|
package/package.json
CHANGED