fluncle 0.100.0 → 0.102.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 +269 -2
- package/package.json +1 -1
package/bin/fluncle.mjs
CHANGED
|
@@ -557,7 +557,7 @@ function parseVersion(version) {
|
|
|
557
557
|
var currentVersion;
|
|
558
558
|
var init_version = __esm(() => {
|
|
559
559
|
init_output();
|
|
560
|
-
currentVersion = "0.
|
|
560
|
+
currentVersion = "0.102.0".trim() ? "0.102.0".trim() : "0.1.0";
|
|
561
561
|
});
|
|
562
562
|
|
|
563
563
|
// src/update-notifier.ts
|
|
@@ -1022,6 +1022,24 @@ var init_recent = __esm(() => {
|
|
|
1022
1022
|
init_api();
|
|
1023
1023
|
});
|
|
1024
1024
|
|
|
1025
|
+
// src/commands/artists.ts
|
|
1026
|
+
var exports_artists = {};
|
|
1027
|
+
__export(exports_artists, {
|
|
1028
|
+
artistsListCommand: () => artistsListCommand,
|
|
1029
|
+
artistsGetCommand: () => artistsGetCommand
|
|
1030
|
+
});
|
|
1031
|
+
async function artistsListCommand() {
|
|
1032
|
+
const response = await publicApiGet("/api/v1/artists");
|
|
1033
|
+
return response.artists;
|
|
1034
|
+
}
|
|
1035
|
+
async function artistsGetCommand(slug) {
|
|
1036
|
+
const response = await publicApiGet(`/api/v1/artists/${encodeURIComponent(slug)}`);
|
|
1037
|
+
return response.artist;
|
|
1038
|
+
}
|
|
1039
|
+
var init_artists = __esm(() => {
|
|
1040
|
+
init_api();
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1025
1043
|
// src/commands/mixtape-api.ts
|
|
1026
1044
|
async function mixtapeListCommand() {
|
|
1027
1045
|
const response = await adminApiGet("/api/admin/mixtapes");
|
|
@@ -2187,7 +2205,7 @@ function parseVersion2(version) {
|
|
|
2187
2205
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2188
2206
|
var init_version2 = __esm(() => {
|
|
2189
2207
|
init_output();
|
|
2190
|
-
currentVersion2 = "0.
|
|
2208
|
+
currentVersion2 = "0.102.0".trim() ? "0.102.0".trim() : "0.1.0";
|
|
2191
2209
|
});
|
|
2192
2210
|
|
|
2193
2211
|
// ../../packages/registry/src/index.ts
|
|
@@ -2308,6 +2326,18 @@ var init_src = __esm(() => {
|
|
|
2308
2326
|
url: `${SITE}/radio`,
|
|
2309
2327
|
weights: { web: "secondary" }
|
|
2310
2328
|
},
|
|
2329
|
+
{
|
|
2330
|
+
discoveryUrl: `${SITE}/llms.txt`,
|
|
2331
|
+
exposedContent: [
|
|
2332
|
+
"/artist/:slug — one artist's page: every published finding from that artist, plus their identity links"
|
|
2333
|
+
],
|
|
2334
|
+
kind: "web_route",
|
|
2335
|
+
name: "web.artist",
|
|
2336
|
+
operatorNotes: "Slug is real-name kebab-case (e.g. /artist/dbridge). Read-only; only artists with at least one published finding resolve (others 404). No probeConfig — the route is slug-parameterized, so there is no fixed URL to GET-probe.",
|
|
2337
|
+
route: "/artist",
|
|
2338
|
+
url: `${SITE}/artist`,
|
|
2339
|
+
weights: { ssh: "secondary", web: "secondary" }
|
|
2340
|
+
},
|
|
2311
2341
|
{
|
|
2312
2342
|
exposedContent: ["the privacy policy"],
|
|
2313
2343
|
kind: "web_route",
|
|
@@ -2420,6 +2450,20 @@ var init_src = __esm(() => {
|
|
|
2420
2450
|
url: `${SITE}/api/v1/mixtapes`,
|
|
2421
2451
|
weights: { web: "secondary" }
|
|
2422
2452
|
},
|
|
2453
|
+
{
|
|
2454
|
+
apiFormat: "application/json",
|
|
2455
|
+
discoveryUrl: `${SITE}/api/v1/openapi.json`,
|
|
2456
|
+
exposedContent: [
|
|
2457
|
+
"every artist with at least one published finding, finding-count ordered, as JSON",
|
|
2458
|
+
"/api/v1/artists/{slug} — one artist's identity and finding count, as JSON"
|
|
2459
|
+
],
|
|
2460
|
+
kind: "api",
|
|
2461
|
+
name: "api.artists",
|
|
2462
|
+
probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
|
|
2463
|
+
route: "/api/v1/artists",
|
|
2464
|
+
url: `${SITE}/api/v1/artists`,
|
|
2465
|
+
weights: { web: "secondary" }
|
|
2466
|
+
},
|
|
2423
2467
|
{
|
|
2424
2468
|
apiFormat: "application/json",
|
|
2425
2469
|
discoveryUrl: `${SITE}/api/v1/openapi.json`,
|
|
@@ -2657,6 +2701,13 @@ var init_src = __esm(() => {
|
|
|
2657
2701
|
name: "cli.mixtapes",
|
|
2658
2702
|
weights: { cli: "secondary" }
|
|
2659
2703
|
},
|
|
2704
|
+
{
|
|
2705
|
+
command: "fluncle artists",
|
|
2706
|
+
exposedContent: ["every artist with at least one published finding (bare `slug` looks one up)"],
|
|
2707
|
+
kind: "cli",
|
|
2708
|
+
name: "cli.artists",
|
|
2709
|
+
weights: { cli: "secondary" }
|
|
2710
|
+
},
|
|
2660
2711
|
{
|
|
2661
2712
|
command: "fluncle open",
|
|
2662
2713
|
exposedContent: ["pick a track, open it in Spotify"],
|
|
@@ -2748,6 +2799,17 @@ var init_src = __esm(() => {
|
|
|
2748
2799
|
probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-embed", kind: "cron" },
|
|
2749
2800
|
weights: { status: "hidden" }
|
|
2750
2801
|
},
|
|
2802
|
+
{
|
|
2803
|
+
command: "fluncle admin tracks capture-audio --queue",
|
|
2804
|
+
exposedContent: [
|
|
2805
|
+
"capture each finding's full song once → private R2 (yt-dlp via a residential proxy)"
|
|
2806
|
+
],
|
|
2807
|
+
kind: "cron",
|
|
2808
|
+
name: "cron.capture",
|
|
2809
|
+
operatorNotes: "every 5m, run by a rave-02 HOST systemd timer (docs/agents/hermes/capture-timer/) — NOT a gateway cron: a proxied yt-dlp fetch has an unbounded tail that would starve the 5-min enrich/context/note sweeps on the shared serial runner. A NON-BLOCKING side-channel (never gates enrich/embed). Runs yt-dlp through a residential proxy on a per-track sticky session, duration-guards the match, stores the full song in the PRIVATE fluncle-source-audio bucket (S3-direct), and writes back via the agent-tier update_track (with per-finding backoff). yt-dlp + ffprobe are a box deploy prereq. Newest-first so a fresh add jumps the backfill. Source: docs/agents/hermes/scripts/capture-sweep.*. See docs/full-audio-rfc.md § Unit 1.",
|
|
2810
|
+
probeConfig: { cadenceMs: 5 * MINUTE_MS, cronName: "fluncle-capture", kind: "cron" },
|
|
2811
|
+
weights: { status: "hidden" }
|
|
2812
|
+
},
|
|
2751
2813
|
{
|
|
2752
2814
|
command: "fluncle admin tracks context --queue",
|
|
2753
2815
|
exposedContent: [
|
|
@@ -2790,6 +2852,27 @@ var init_src = __esm(() => {
|
|
|
2790
2852
|
probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-backfill", kind: "cron" },
|
|
2791
2853
|
weights: { status: "hidden" }
|
|
2792
2854
|
},
|
|
2855
|
+
{
|
|
2856
|
+
exposedContent: [
|
|
2857
|
+
"resolve each artist's social identity: MB url-rel walk + Firecrawl gap-fill (TikTok + YouTube)"
|
|
2858
|
+
],
|
|
2859
|
+
kind: "cron",
|
|
2860
|
+
name: "cron.artist-sweep",
|
|
2861
|
+
operatorNotes: "every 60m. --no-agent trigger; the Worker does the MB walk + Firecrawl /v2/extract + YouTube channel resolution. Zero on-box tokens. MB rows land as status=auto (trusted); Firecrawl rows as status=candidate (operator-confirm before public). Source: docs/agents/hermes/scripts/artist-sweep.*",
|
|
2862
|
+
probeConfig: { cadenceMs: 60 * MINUTE_MS, cronName: "fluncle-artist-sweep", kind: "cron" },
|
|
2863
|
+
weights: { status: "hidden" }
|
|
2864
|
+
},
|
|
2865
|
+
{
|
|
2866
|
+
command: "fluncle admin artists follow",
|
|
2867
|
+
exposedContent: [
|
|
2868
|
+
"auto-follow high-confidence artists on Spotify + YouTube — the championing motion (--no-agent, Worker HTTP)"
|
|
2869
|
+
],
|
|
2870
|
+
kind: "cron",
|
|
2871
|
+
name: "cron.artist-follow",
|
|
2872
|
+
operatorNotes: "every 6h. Pure HTTP trigger, zero LLM tokens. Agent tier (the box holds no Spotify/YouTube tokens; the Worker does the PUT /me/following + subscriptions.insert and stamps followed_at). Idempotent by followed_at IS NULL, acting only on status IN (auto, confirmed); quota-paced. Mixcloud is link-only (cut). Source: docs/agents/hermes/scripts/artist-follow-sweep.*. Probed on /status as cron.artist-follow.",
|
|
2873
|
+
probeConfig: { cadenceMs: 6 * 60 * MINUTE_MS, cronName: "fluncle-artist-follow", kind: "cron" },
|
|
2874
|
+
weights: { status: "hidden" }
|
|
2875
|
+
},
|
|
2793
2876
|
{
|
|
2794
2877
|
command: "fluncle admin tracks social --capture",
|
|
2795
2878
|
exposedContent: [
|
|
@@ -3457,11 +3540,13 @@ __export(exports_admin_tracks, {
|
|
|
3457
3540
|
enrichQueueCommand: () => enrichQueueCommand,
|
|
3458
3541
|
embedQueueCommand: () => embedQueueCommand,
|
|
3459
3542
|
contextQueueCommand: () => contextQueueCommand,
|
|
3543
|
+
captureQueueCommand: () => captureQueueCommand,
|
|
3460
3544
|
backfillLastfmCommand: () => backfillLastfmCommand,
|
|
3461
3545
|
backfillDiscogsCommand: () => backfillDiscogsCommand
|
|
3462
3546
|
});
|
|
3463
3547
|
async function fetchAdminTracks(options) {
|
|
3464
3548
|
const {
|
|
3549
|
+
captureQueue,
|
|
3465
3550
|
hasContext,
|
|
3466
3551
|
hasEmbedding,
|
|
3467
3552
|
hasKey,
|
|
@@ -3486,6 +3571,9 @@ async function fetchAdminTracks(options) {
|
|
|
3486
3571
|
if (hasEmbedding !== undefined) {
|
|
3487
3572
|
params.set("hasEmbedding", String(hasEmbedding));
|
|
3488
3573
|
}
|
|
3574
|
+
if (captureQueue) {
|
|
3575
|
+
params.set("captureQueue", "true");
|
|
3576
|
+
}
|
|
3489
3577
|
if (hasContext !== undefined) {
|
|
3490
3578
|
params.set("hasContext", String(hasContext));
|
|
3491
3579
|
}
|
|
@@ -3541,6 +3629,9 @@ async function enrichQueueCommand(limit) {
|
|
|
3541
3629
|
async function embedQueueCommand(limit) {
|
|
3542
3630
|
return fetchAdminTracks({ hasEmbedding: false, max: limit, order: "asc" });
|
|
3543
3631
|
}
|
|
3632
|
+
async function captureQueueCommand(limit) {
|
|
3633
|
+
return fetchAdminTracks({ captureQueue: true, max: limit, order: "desc" });
|
|
3634
|
+
}
|
|
3544
3635
|
async function contextQueueCommand(limit, retryEmptyContext = false) {
|
|
3545
3636
|
return fetchAdminTracks({ hasContext: false, max: limit, order: "asc", retryEmptyContext });
|
|
3546
3637
|
}
|
|
@@ -5100,8 +5191,21 @@ var init_auth_lastfm = __esm(() => {
|
|
|
5100
5191
|
// src/commands/admin-artists.ts
|
|
5101
5192
|
var exports_admin_artists = {};
|
|
5102
5193
|
__export(exports_admin_artists, {
|
|
5194
|
+
resolveArtistCommand: () => resolveArtistCommand,
|
|
5195
|
+
listArtistsCommand: () => listArtistsCommand,
|
|
5196
|
+
followArtistsCommand: () => followArtistsCommand,
|
|
5103
5197
|
backfillArtistsCommand: () => backfillArtistsCommand
|
|
5104
5198
|
});
|
|
5199
|
+
async function listArtistsCommand(limit, cursor) {
|
|
5200
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
5201
|
+
if (cursor) {
|
|
5202
|
+
params.set("cursor", cursor);
|
|
5203
|
+
}
|
|
5204
|
+
return adminApiGet(`/api/admin/artists?${params.toString()}`);
|
|
5205
|
+
}
|
|
5206
|
+
async function resolveArtistCommand(artistId) {
|
|
5207
|
+
return adminApiPost(`/api/admin/artists/${encodeURIComponent(artistId)}/resolve`);
|
|
5208
|
+
}
|
|
5105
5209
|
async function backfillArtistsCommand(limit, dryRun, cursor) {
|
|
5106
5210
|
const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
|
|
5107
5211
|
if (cursor) {
|
|
@@ -5109,6 +5213,10 @@ async function backfillArtistsCommand(limit, dryRun, cursor) {
|
|
|
5109
5213
|
}
|
|
5110
5214
|
return adminApiPost(`/api/admin/backfill/artists?${params.toString()}`);
|
|
5111
5215
|
}
|
|
5216
|
+
async function followArtistsCommand(limit, dryRun) {
|
|
5217
|
+
const params = new URLSearchParams({ dryRun: String(dryRun), limit: String(limit) });
|
|
5218
|
+
return adminApiPost(`/api/admin/artists/follow?${params.toString()}`);
|
|
5219
|
+
}
|
|
5112
5220
|
var init_admin_artists = __esm(() => {
|
|
5113
5221
|
init_api();
|
|
5114
5222
|
});
|
|
@@ -7576,6 +7684,10 @@ function addListenCommands(program2) {
|
|
|
7576
7684
|
const { recentCommand: recentCommand3 } = await Promise.resolve().then(() => (init_recent(), exports_recent));
|
|
7577
7685
|
await runRecent(options, recentCommand3);
|
|
7578
7686
|
});
|
|
7687
|
+
program2.command("artists").description("Browse artists in Fluncle's archive").argument("[slug]", "Artist slug \u2014 omit for the full list").option("--json", "Print JSON", false).action(async (slug, options) => {
|
|
7688
|
+
const { artistsListCommand: artistsListCommand2, artistsGetCommand: artistsGetCommand2 } = await Promise.resolve().then(() => (init_artists(), exports_artists));
|
|
7689
|
+
await runArtists(slug, options, { artistsGetCommand: artistsGetCommand2, artistsListCommand: artistsListCommand2 });
|
|
7690
|
+
});
|
|
7579
7691
|
program2.command("mixtapes").description("Fluncle's checkpoint sets").option("--json", "Print JSON", false).action(async (options) => {
|
|
7580
7692
|
const { mixtapesCommand: mixtapesCommand2 } = await Promise.resolve().then(() => (init_mixtapes(), exports_mixtapes));
|
|
7581
7693
|
await runMixtapes(options, mixtapesCommand2);
|
|
@@ -7694,6 +7806,15 @@ function addAdminCommands(program2) {
|
|
|
7694
7806
|
const { embedQueueCommand: embedQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
7695
7807
|
await runAdminEmbedQueue(options, embedQueueCommand2);
|
|
7696
7808
|
});
|
|
7809
|
+
adminTracks.command("capture-audio").description("Full-song capture worklist (findings with no source audio yet) \u2014 use --queue").option("--queue", "Show the capture worklist, newest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--json", "Print JSON", false).action(async (options) => {
|
|
7810
|
+
if (!options.queue) {
|
|
7811
|
+
console.error("`tracks capture-audio` is a worklist view \u2014 capture runs on the on-box `fluncle-capture` cron.\nUse `tracks capture-audio --queue` to see findings needing a full-song capture.");
|
|
7812
|
+
process.exitCode = 1;
|
|
7813
|
+
return;
|
|
7814
|
+
}
|
|
7815
|
+
const { captureQueueCommand: captureQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
7816
|
+
await runAdminCaptureQueue(options, captureQueueCommand2);
|
|
7817
|
+
});
|
|
7697
7818
|
adminTracks.command("vehicles").description("Recent video vehicles, newest first (the style ledger for diversity)").option("--limit <limit>", "Number of vehicles to show", "10").option("--json", "Print JSON", false).action(async (options) => {
|
|
7698
7819
|
const { vehiclesCommand: vehiclesCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
7699
7820
|
await runAdminVehicles(options, vehiclesCommand2);
|
|
@@ -7914,6 +8035,14 @@ function addAdminCommands(program2) {
|
|
|
7914
8035
|
const { authLastfmCommand: authLastfmCommand2 } = await Promise.resolve().then(() => (init_auth_lastfm(), exports_auth_lastfm));
|
|
7915
8036
|
await authLastfmCommand2(options);
|
|
7916
8037
|
});
|
|
8038
|
+
const artists = configureCommand(admin.command("artists").description("Artist entity + championing commands"));
|
|
8039
|
+
artists.action(() => {
|
|
8040
|
+
artists.outputHelp();
|
|
8041
|
+
});
|
|
8042
|
+
artists.command("follow").description("Auto-follow high-confidence artists on Spotify + YouTube (drains the queue)").option("--dry-run", "Report who WOULD be followed without calling the platforms", false).option("--limit <limit>", "Max artists to follow this run", "20").option("--json", "Print JSON", false).action(async (options) => {
|
|
8043
|
+
const { followArtistsCommand: followArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
|
|
8044
|
+
await runFollowArtists(options, followArtistsCommand2);
|
|
8045
|
+
});
|
|
7917
8046
|
const backfill = configureCommand(admin.command("backfills").description("Backfill operator-only archives"));
|
|
7918
8047
|
backfill.action(() => {
|
|
7919
8048
|
backfill.outputHelp();
|
|
@@ -7934,6 +8063,15 @@ function addAdminCommands(program2) {
|
|
|
7934
8063
|
const { backfillArtistsCommand: backfillArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
|
|
7935
8064
|
await runBackfillArtists(options, backfillArtistsCommand2);
|
|
7936
8065
|
});
|
|
8066
|
+
artists.command("resolve").description("Resolve an artist's social identity (MB url-rels + Firecrawl gap-fill)").argument("[artistId]").option("--queue", "Show the resolve worklist (artists awaiting resolution), oldest first", false).option("--limit <limit>", "Number of artists to show with --queue", "50").option("--json", "Print JSON", false).allowExcessArguments().action(async (artistId, options) => {
|
|
8067
|
+
if (options.queue) {
|
|
8068
|
+
const { listArtistsCommand: listArtistsCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
|
|
8069
|
+
await runArtistResolveQueue(options, listArtistsCommand2);
|
|
8070
|
+
return;
|
|
8071
|
+
}
|
|
8072
|
+
const { resolveArtistCommand: resolveArtistCommand2 } = await Promise.resolve().then(() => (init_admin_artists(), exports_admin_artists));
|
|
8073
|
+
await runArtistResolve(artistId, options, resolveArtistCommand2);
|
|
8074
|
+
});
|
|
7937
8075
|
}
|
|
7938
8076
|
async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCommand2) {
|
|
7939
8077
|
if (!idOrLogId || !options.file || !options.source || !options.mime) {
|
|
@@ -8191,6 +8329,88 @@ async function runBackfillArtists(options, backfillArtistsCommand2) {
|
|
|
8191
8329
|
console.log(` ${item.logId}: ${item.error}`);
|
|
8192
8330
|
}
|
|
8193
8331
|
}
|
|
8332
|
+
async function runArtistResolveQueue(options, listArtistsCommand2) {
|
|
8333
|
+
const limit = parseListLimit(options.limit);
|
|
8334
|
+
const result = await listArtistsCommand2(limit ?? 50);
|
|
8335
|
+
if (options.json) {
|
|
8336
|
+
printJson({ artists: result.artists, ok: true });
|
|
8337
|
+
return;
|
|
8338
|
+
}
|
|
8339
|
+
if (result.artists.length === 0) {
|
|
8340
|
+
console.log("Every artist has been resolved. Nothing waiting on the sweep.");
|
|
8341
|
+
return;
|
|
8342
|
+
}
|
|
8343
|
+
const noun = result.artists.length === 1 ? "artist" : "artists";
|
|
8344
|
+
console.log(`${result.artists.length} ${noun} awaiting resolution, oldest first:`);
|
|
8345
|
+
for (const artist of result.artists) {
|
|
8346
|
+
console.log(` ${artist.id} ${artist.name}`);
|
|
8347
|
+
}
|
|
8348
|
+
}
|
|
8349
|
+
async function runArtistResolve(artistId, options, resolveArtistCommand2) {
|
|
8350
|
+
if (!artistId) {
|
|
8351
|
+
throw new Error("Usage: fluncle admin artists resolve <artist_id> [--json]");
|
|
8352
|
+
}
|
|
8353
|
+
const result = await resolveArtistCommand2(artistId);
|
|
8354
|
+
if (options.json) {
|
|
8355
|
+
printJson(result);
|
|
8356
|
+
return;
|
|
8357
|
+
}
|
|
8358
|
+
if (result.rateLimited) {
|
|
8359
|
+
console.log(`MusicBrainz throttled the walk for ${artistId}. The sweep will swing back around.`);
|
|
8360
|
+
return;
|
|
8361
|
+
}
|
|
8362
|
+
const mbid = result.mbid ?? "(no MusicBrainz match)";
|
|
8363
|
+
const noun = result.socialsCount === 1 ? "social" : "socials";
|
|
8364
|
+
console.log(`Resolved ${artistId}: mbid=${mbid}, ${result.socialsCount} ${noun}.`);
|
|
8365
|
+
for (const social of result.socials) {
|
|
8366
|
+
console.log(` ${social.platform} (${social.source}): ${social.url}`);
|
|
8367
|
+
}
|
|
8368
|
+
if (result.wikidataQid) {
|
|
8369
|
+
console.log(` wikidata: ${result.wikidataQid}`);
|
|
8370
|
+
}
|
|
8371
|
+
}
|
|
8372
|
+
async function runFollowArtists(options, followArtistsCommand2) {
|
|
8373
|
+
const limit = parseListLimit(options.limit);
|
|
8374
|
+
const followed = [];
|
|
8375
|
+
const failed = [];
|
|
8376
|
+
let dryRun = options.dryRun;
|
|
8377
|
+
let remaining = 0;
|
|
8378
|
+
while (followed.length + failed.length < limit) {
|
|
8379
|
+
const batchLimit = Math.min(50, limit - (followed.length + failed.length));
|
|
8380
|
+
const result = await followArtistsCommand2(batchLimit, options.dryRun);
|
|
8381
|
+
dryRun = result.dryRun;
|
|
8382
|
+
remaining = result.remaining;
|
|
8383
|
+
followed.push(...result.followed);
|
|
8384
|
+
failed.push(...result.failed);
|
|
8385
|
+
if (!options.json) {
|
|
8386
|
+
const verb2 = result.dryRun ? "would follow" : "followed";
|
|
8387
|
+
console.log(` \u2026${verb2} ${result.followedCount}; ${result.failedCount} failed; ${result.remaining} remaining`);
|
|
8388
|
+
}
|
|
8389
|
+
if (remaining === 0 || result.dryRun || result.followedCount === 0) {
|
|
8390
|
+
break;
|
|
8391
|
+
}
|
|
8392
|
+
}
|
|
8393
|
+
if (options.json) {
|
|
8394
|
+
printJson({
|
|
8395
|
+
dryRun,
|
|
8396
|
+
failed,
|
|
8397
|
+
failedCount: failed.length,
|
|
8398
|
+
followed,
|
|
8399
|
+
followedCount: followed.length,
|
|
8400
|
+
ok: true,
|
|
8401
|
+
remaining
|
|
8402
|
+
});
|
|
8403
|
+
return;
|
|
8404
|
+
}
|
|
8405
|
+
const verb = dryRun ? "Would follow" : "Followed";
|
|
8406
|
+
console.log(`${verb} ${followed.length} artist(s); ${failed.length} failed; ${remaining} remaining.`);
|
|
8407
|
+
for (const item of followed) {
|
|
8408
|
+
console.log(` ${item.platform}: ${item.artistName}`);
|
|
8409
|
+
}
|
|
8410
|
+
for (const item of failed) {
|
|
8411
|
+
console.log(` ${item.platform} ${item.socialId}: ${item.error}`);
|
|
8412
|
+
}
|
|
8413
|
+
}
|
|
8194
8414
|
async function runTrackVideo(idOrLogId, options, trackVideoCommand2) {
|
|
8195
8415
|
if (!idOrLogId) {
|
|
8196
8416
|
throw new Error("Missing id. Usage: fluncle admin tracks video <track_id|log_id> (--dir <dir> | --footage <file> [--footage-social <file>] [--footage-notext <file>] [--footage-landscape <file>] [--footage-landscape-social <file>] [--poster <file>] [--cover <file>] [--note <file>] [--composition <file>] [--props <file>] [--render <file>] [--intent <file>] [--metrics <file>] [--scene <file>] | --plate <file> [--plate-background <file>]) [--allow-partial]");
|
|
@@ -8694,6 +8914,33 @@ async function runRecent(options, recentCommand3) {
|
|
|
8694
8914
|
console.log(trackRows2(tracks).join(`
|
|
8695
8915
|
`));
|
|
8696
8916
|
}
|
|
8917
|
+
async function runArtists(slug, options, commands) {
|
|
8918
|
+
if (slug) {
|
|
8919
|
+
const artist = await commands.artistsGetCommand(slug);
|
|
8920
|
+
if (options.json) {
|
|
8921
|
+
printJson({ artist, ok: true });
|
|
8922
|
+
return;
|
|
8923
|
+
}
|
|
8924
|
+
console.log(`${artist.name} (${artist.slug})`);
|
|
8925
|
+
console.log(`Findings: ${artist.findingCount}`);
|
|
8926
|
+
if (artist.spotifyUrl) {
|
|
8927
|
+
console.log(`Spotify: ${artist.spotifyUrl}`);
|
|
8928
|
+
}
|
|
8929
|
+
return;
|
|
8930
|
+
}
|
|
8931
|
+
const artists = await commands.artistsListCommand();
|
|
8932
|
+
if (options.json) {
|
|
8933
|
+
printJson({ artists, ok: true });
|
|
8934
|
+
return;
|
|
8935
|
+
}
|
|
8936
|
+
if (artists.length === 0) {
|
|
8937
|
+
console.log("No artists in the archive yet.");
|
|
8938
|
+
return;
|
|
8939
|
+
}
|
|
8940
|
+
for (const a of artists) {
|
|
8941
|
+
console.log(`${a.name.padEnd(40)} ${String(a.findingCount).padStart(3)} finding${a.findingCount === 1 ? "" : "s"}`);
|
|
8942
|
+
}
|
|
8943
|
+
}
|
|
8697
8944
|
async function runMixtapes(options, mixtapesCommand2) {
|
|
8698
8945
|
const mixtapes = await mixtapesCommand2();
|
|
8699
8946
|
if (options.json) {
|
|
@@ -8872,6 +9119,26 @@ async function runAdminEmbedQueue(options, embedQueueCommand2) {
|
|
|
8872
9119
|
console.log(trackRows2(tracks).join(`
|
|
8873
9120
|
`));
|
|
8874
9121
|
}
|
|
9122
|
+
async function runAdminCaptureQueue(options, captureQueueCommand2) {
|
|
9123
|
+
const limit = parseListLimit(options.limit);
|
|
9124
|
+
const tracks = await captureQueueCommand2(limit);
|
|
9125
|
+
if (options.json) {
|
|
9126
|
+
printJson({
|
|
9127
|
+
ok: true,
|
|
9128
|
+
tracks
|
|
9129
|
+
});
|
|
9130
|
+
return;
|
|
9131
|
+
}
|
|
9132
|
+
if (tracks.length === 0) {
|
|
9133
|
+
console.log("Nothing awaiting a capture. Every finding has its full song.");
|
|
9134
|
+
return;
|
|
9135
|
+
}
|
|
9136
|
+
const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
|
|
9137
|
+
const noun = tracks.length === 1 ? "finding" : "findings";
|
|
9138
|
+
console.log(`${tracks.length} ${noun} needing a full-song capture, newest first:`);
|
|
9139
|
+
console.log(trackRows2(tracks).join(`
|
|
9140
|
+
`));
|
|
9141
|
+
}
|
|
8875
9142
|
async function runAdminVehicles(options, vehiclesCommand2) {
|
|
8876
9143
|
const limit = parseListLimit(options.limit);
|
|
8877
9144
|
const vehicles = await vehiclesCommand2(limit);
|
package/package.json
CHANGED