fluncle 0.234.0 → 0.236.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 +89 -16
- package/package.json +1 -1
package/bin/fluncle.mjs
CHANGED
|
@@ -622,7 +622,7 @@ function parseVersion(version) {
|
|
|
622
622
|
var currentVersion;
|
|
623
623
|
var init_version = __esm(() => {
|
|
624
624
|
init_output();
|
|
625
|
-
currentVersion = "0.
|
|
625
|
+
currentVersion = "0.236.0".trim() ? "0.236.0".trim() : "0.1.0";
|
|
626
626
|
});
|
|
627
627
|
|
|
628
628
|
// src/update-notifier.ts
|
|
@@ -2335,7 +2335,7 @@ function parseVersion2(version) {
|
|
|
2335
2335
|
var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
|
|
2336
2336
|
var init_version2 = __esm(() => {
|
|
2337
2337
|
init_output();
|
|
2338
|
-
currentVersion2 = "0.
|
|
2338
|
+
currentVersion2 = "0.236.0".trim() ? "0.236.0".trim() : "0.1.0";
|
|
2339
2339
|
});
|
|
2340
2340
|
|
|
2341
2341
|
// ../../packages/registry/src/index.ts
|
|
@@ -3059,6 +3059,28 @@ var init_src = __esm(() => {
|
|
|
3059
3059
|
url: `${SITE}/robots.txt`,
|
|
3060
3060
|
weights: { web: "tertiary" }
|
|
3061
3061
|
},
|
|
3062
|
+
{
|
|
3063
|
+
apiFormat: "text/plain",
|
|
3064
|
+
exposedContent: [
|
|
3065
|
+
"RFC 9116 security.txt — where to send a vulnerability report, in the one place a researcher looks first"
|
|
3066
|
+
],
|
|
3067
|
+
kind: "discovery",
|
|
3068
|
+
name: "discovery.security-txt",
|
|
3069
|
+
operatorNotes: "A static file, apps/web/public/.well-known/security.txt. It carries an `Expires` field the RFC requires a responder to keep in the future — when it lapses, a reporter reads the contact as stale. No probeConfig, exactly like robots.txt and llms.txt: a static asset the Worker never computes.",
|
|
3070
|
+
route: "/.well-known/security.txt",
|
|
3071
|
+
url: `${SITE}/.well-known/security.txt`,
|
|
3072
|
+
weights: { web: "tertiary" }
|
|
3073
|
+
},
|
|
3074
|
+
{
|
|
3075
|
+
apiFormat: "text/plain",
|
|
3076
|
+
exposedContent: ["humans.txt — who made this, who is thanked, and what it is built with"],
|
|
3077
|
+
kind: "discovery",
|
|
3078
|
+
name: "discovery.humans",
|
|
3079
|
+
operatorNotes: "A static file, apps/web/public/humans.txt — the human counterpart to robots.txt. Machine-fetched by convention, never browsed, which is why it sits under `discovery` beside robots.txt rather than as a web route.",
|
|
3080
|
+
route: "/humans.txt",
|
|
3081
|
+
url: `${SITE}/humans.txt`,
|
|
3082
|
+
weights: { web: "tertiary" }
|
|
3083
|
+
},
|
|
3062
3084
|
{
|
|
3063
3085
|
apiFormat: "text/markdown",
|
|
3064
3086
|
discoveryUrl: `${SITE}/.well-known/api-catalog`,
|
|
@@ -3875,7 +3897,7 @@ var init_src = __esm(() => {
|
|
|
3875
3897
|
],
|
|
3876
3898
|
kind: "cron",
|
|
3877
3899
|
name: "cron.reconcile-hub-counts",
|
|
3878
|
-
operatorNotes: "04:10 Amsterdam (after the 04:00 reach snapshot, before the 04:40 demand tick), a rave-02 host systemd timer (docs/agents/hermes/reconcile-hub-counts-timer/). The SELF-HEALING BACKSTOP under keystone 2's maintained hub counts: they are moved as DELTAS by every edge-writing path (recompute-from-truth measured 27,400 ms at 150k hosted vs ~200 ms for the delta form), and a maintained counter drifts silently — a missed write path, a non-atomic bulk op, or an out-of-band write (the catalogue-prune skill deletes tracks straight out of the database). Keystone 2's own rollout proved it on day one: the deploy-window skew left 44 artists / 3 albums / 1 label wrong until a manual reconcile.
|
|
3900
|
+
operatorNotes: "04:10 Amsterdam (after the 04:00 reach snapshot, before the 04:40 demand tick), a rave-02 host systemd timer (docs/agents/hermes/reconcile-hub-counts-timer/). The SELF-HEALING BACKSTOP under keystone 2's maintained hub counts: they are moved as DELTAS by every edge-writing path (recompute-from-truth measured 27,400 ms at 150k hosted vs ~200 ms for the delta form), and a maintained counter drifts silently — a missed write path, a non-atomic bulk op, or an out-of-band write (the catalogue-prune skill deletes tracks straight out of the database). Keystone 2's own rollout proved it on day one: the deploy-window skew left 44 artists / 3 albums / 1 label wrong until a manual reconcile. Walks the AGENT-tier reconcile_hub_counts op in bounded windows, one admitted database phase each: the Worker reads bounded keyset pages of entity rows beside their truth (never an aggregate inside a write transaction) and rewrites only disagreeing rows as compare-and-set point writes, so a concurrent maintained delta is never overwritten. The artists source is PINNED to `track_artists JOIN tracks` so orphaned edges never count. Idempotent; zero LLM tokens; no new secret. The corrected-row numbers are the operator's DRIFT AUDIT — journalctl -u fluncle-reconcile-hub-counts.service | grep AUDIT. Source: docs/agents/hermes/scripts/reconcile-hub-counts.*.",
|
|
3879
3901
|
probeConfig: {
|
|
3880
3902
|
cadenceMs: 24 * 60 * MINUTE_MS,
|
|
3881
3903
|
cronName: "fluncle-reconcile-hub-counts",
|
|
@@ -10693,7 +10715,10 @@ var init_admin_artists = __esm(() => {
|
|
|
10693
10715
|
path: "/admin/artists/rank",
|
|
10694
10716
|
summary: "One tick of the similar-artists sweep (artist centroids + top-K edges)",
|
|
10695
10717
|
tags: ["Admin"]
|
|
10696
|
-
}).input(object({
|
|
10718
|
+
}).input(object({
|
|
10719
|
+
countRemaining: boolean3().default(false),
|
|
10720
|
+
limit: number3().int().min(1).max(1000).default(50)
|
|
10721
|
+
})).output(object({
|
|
10697
10722
|
ok: literal(true),
|
|
10698
10723
|
summary: object({
|
|
10699
10724
|
centroidsComputed: number2(),
|
|
@@ -11408,7 +11433,7 @@ var init_admin_bios = __esm(() => {
|
|
|
11408
11433
|
});
|
|
11409
11434
|
|
|
11410
11435
|
// ../../packages/contracts/src/orpc/admin-catalogue.ts
|
|
11411
|
-
var CatalogueLensSchema, CapturePriorityReasonSchema, CatalogueMatchSchema, CatalogueTrackItemSchema, CatalogueSummarySchema, listCatalogueTracks, rankCatalogue, recordDemand, clearWrongAudio, requeueUnmatchedCaptures, requeueAnchor, flagWrongAudio, forceCapture, certifyTrack, setTrackDismissed, CaptureVerifyItemSchema, listUnverifiedCaptures, verifyCapture, CrawlPassSchema, CrawlPhaseInitializationSchema, CrawlPhaseInputSchema, CrawlPhaseOutputSchema, CrawlStatusSchema, crawlCatalogue, getCrawlStatus, ANCHOR_CANDIDATE_LIMIT = 100, ANCHOR_ARTIST_LIMIT = 20, ANCHOR_TEXT_MAX = 300, ANCHOR_ISRC_MAX = 64, ANCHOR_ID_MAX = 64, ANCHOR_URL_MAX = 2048, AnchorCandidateSchema, anchorTrack, DEEZER_CANDIDATE_LIMIT = 5, DEEZER_TEXT_MAX = 300, DEEZER_ISRC_MAX = 64, DEEZER_TRACK_ID_MAX = 32, DeezerIsrcCandidateSchema, resolveAnchor, resolveAnchorReview, setAnchorSearch, setAnchorApify, CaptureBudgetStateSchema, getCaptureBudget, setCaptureBudget, SpotifyAnchorBreakerStateSchema, getSpotifyAnchorBreaker, resetSpotifyAnchorBreaker, AppleBreakerStateSchema, resetAppleBreaker, adminCatalogueContract;
|
|
11436
|
+
var CatalogueLensSchema, CapturePriorityReasonSchema, CatalogueMatchSchema, CatalogueTrackItemSchema, CatalogueSummarySchema, listCatalogueTracks, rankCatalogue, recordDemand, clearWrongAudio, requeueUnmatchedCaptures, requeueAnchor, requeueIsrcRecovery, flagWrongAudio, forceCapture, certifyTrack, setTrackDismissed, CaptureVerifyItemSchema, listUnverifiedCaptures, verifyCapture, CrawlPassSchema, CrawlPhaseInitializationSchema, CrawlPhaseInputSchema, CrawlPhaseOutputSchema, CrawlStatusSchema, crawlCatalogue, getCrawlStatus, ANCHOR_CANDIDATE_LIMIT = 100, ANCHOR_ARTIST_LIMIT = 20, ANCHOR_TEXT_MAX = 300, ANCHOR_ISRC_MAX = 64, ANCHOR_ID_MAX = 64, ANCHOR_URL_MAX = 2048, AnchorCandidateSchema, anchorTrack, DEEZER_CANDIDATE_LIMIT = 5, DEEZER_TEXT_MAX = 300, DEEZER_ISRC_MAX = 64, DEEZER_TRACK_ID_MAX = 32, DeezerIsrcCandidateSchema, resolveAnchor, resolveAnchorReview, setAnchorSearch, setAnchorApify, CaptureBudgetStateSchema, getCaptureBudget, setCaptureBudget, SpotifyAnchorBreakerStateSchema, getSpotifyAnchorBreaker, resetSpotifyAnchorBreaker, AppleBreakerStateSchema, resetAppleBreaker, adminCatalogueContract;
|
|
11412
11437
|
var init_admin_catalogue = __esm(() => {
|
|
11413
11438
|
init_dist3();
|
|
11414
11439
|
init_zod();
|
|
@@ -11537,6 +11562,21 @@ var init_admin_catalogue = __esm(() => {
|
|
|
11537
11562
|
summary: "Clear named rows' anchor re-ask backoff so the next tick retries them (operator)",
|
|
11538
11563
|
tags: ["Admin"]
|
|
11539
11564
|
}).input(object({ trackIds: array(string2().min(1)).min(1).max(250) })).output(object({ ok: literal(true), requeued: number2() }));
|
|
11565
|
+
requeueIsrcRecovery = oc.route({
|
|
11566
|
+
method: "POST",
|
|
11567
|
+
operationId: "requeueIsrcRecovery",
|
|
11568
|
+
path: "/admin/catalogue/isrc-recovery/requeue",
|
|
11569
|
+
summary: "Clear Deezer-empty ISRC-recovery stamps from a named window (operator)",
|
|
11570
|
+
tags: ["Admin"]
|
|
11571
|
+
}).input(object({
|
|
11572
|
+
dryRun: boolean2().default(true),
|
|
11573
|
+
since: string2().regex(/^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d{1,3})?Z)?$/, "since must be an ISO date (2026-09-09) or instant (2026-09-09T00:00:00.000Z)")
|
|
11574
|
+
})).output(object({
|
|
11575
|
+
dryRun: boolean2(),
|
|
11576
|
+
matched: number2(),
|
|
11577
|
+
ok: literal(true),
|
|
11578
|
+
requeued: number2()
|
|
11579
|
+
}));
|
|
11540
11580
|
flagWrongAudio = oc.route({
|
|
11541
11581
|
method: "POST",
|
|
11542
11582
|
operationId: "flagWrongAudio",
|
|
@@ -11909,6 +11949,7 @@ var init_admin_catalogue = __esm(() => {
|
|
|
11909
11949
|
rank_catalogue: rankCatalogue,
|
|
11910
11950
|
record_demand: recordDemand,
|
|
11911
11951
|
requeue_anchor: requeueAnchor,
|
|
11952
|
+
requeue_isrc_recovery: requeueIsrcRecovery,
|
|
11912
11953
|
requeue_unmatched_captures: requeueUnmatchedCaptures,
|
|
11913
11954
|
reset_apple_breaker: resetAppleBreaker,
|
|
11914
11955
|
reset_spotify_anchor_breaker: resetSpotifyAnchorBreaker,
|
|
@@ -12282,7 +12323,8 @@ var init_admin_projections = __esm(() => {
|
|
|
12282
12323
|
processed: CountSchema,
|
|
12283
12324
|
scheduled: CountSchema,
|
|
12284
12325
|
status: ProjectionStatusSchema.optional(),
|
|
12285
|
-
target: ProjectionTargetSchema
|
|
12326
|
+
target: ProjectionTargetSchema,
|
|
12327
|
+
trackSourceMarkersPending: boolean2().optional()
|
|
12286
12328
|
}));
|
|
12287
12329
|
setProjectionCutover = oc.route({
|
|
12288
12330
|
method: "PUT",
|
|
@@ -13226,7 +13268,7 @@ var init_admin_operation_receipts = __esm(() => {
|
|
|
13226
13268
|
});
|
|
13227
13269
|
|
|
13228
13270
|
// ../../packages/contracts/src/orpc/admin-health.ts
|
|
13229
|
-
var HEALTH_SNAPSHOT_PRODUCER_MAX = 64, HEALTH_SNAPSHOT_PRODUCER_PATTERN, ServiceHealthStatusSchema, HealthCheckSchema, recordHealth, adminHealthContract;
|
|
13271
|
+
var HEALTH_SNAPSHOT_PRODUCER_MAX = 64, HEALTH_SNAPSHOT_PRODUCER_PATTERN, HEALTH_SNAPSHOT_CHECKS_MAX = 128, HEALTH_SNAPSHOT_SERVICE_MAX = 64, ServiceHealthStatusSchema, HealthCheckSchema, recordHealth, adminHealthContract;
|
|
13230
13272
|
var init_admin_health = __esm(() => {
|
|
13231
13273
|
init_dist3();
|
|
13232
13274
|
init_zod();
|
|
@@ -13236,7 +13278,7 @@ var init_admin_health = __esm(() => {
|
|
|
13236
13278
|
HealthCheckSchema = object({
|
|
13237
13279
|
latencyMs: number2().int().nullable(),
|
|
13238
13280
|
message: string2().nullable(),
|
|
13239
|
-
service: string2().min(1),
|
|
13281
|
+
service: string2().min(1).max(HEALTH_SNAPSHOT_SERVICE_MAX),
|
|
13240
13282
|
status: ServiceHealthStatusSchema,
|
|
13241
13283
|
transitioned: boolean2()
|
|
13242
13284
|
}).meta({ id: "HealthCheck" });
|
|
@@ -13248,7 +13290,7 @@ var init_admin_health = __esm(() => {
|
|
|
13248
13290
|
tags: ["Admin"]
|
|
13249
13291
|
}).input(object({
|
|
13250
13292
|
at: string2().max(64).datetime({ offset: true }),
|
|
13251
|
-
checks: array(HealthCheckSchema),
|
|
13293
|
+
checks: array(HealthCheckSchema).max(HEALTH_SNAPSHOT_CHECKS_MAX),
|
|
13252
13294
|
operationKey: string2().min(1).max(OPERATION_RECEIPT_KEY_MAX).regex(OPERATION_RECEIPT_KEY_PATTERN).optional(),
|
|
13253
13295
|
producer: string2().max(HEALTH_SNAPSHOT_PRODUCER_MAX).regex(HEALTH_SNAPSHOT_PRODUCER_PATTERN).optional(),
|
|
13254
13296
|
requestDigest: string2().regex(OPERATION_RECEIPT_REQUEST_DIGEST_PATTERN).optional()
|
|
@@ -13263,24 +13305,34 @@ var init_admin_health = __esm(() => {
|
|
|
13263
13305
|
});
|
|
13264
13306
|
|
|
13265
13307
|
// ../../packages/contracts/src/orpc/admin-hub-counts.ts
|
|
13266
|
-
var HubCountsTableResultSchema, reconcileHubCounts, adminHubCountsContract;
|
|
13308
|
+
var HubCountsTableResultSchema, HubCountsReconcileCursorSchema, reconcileHubCounts, adminHubCountsContract;
|
|
13267
13309
|
var init_admin_hub_counts = __esm(() => {
|
|
13268
13310
|
init_dist3();
|
|
13269
13311
|
init_zod();
|
|
13270
13312
|
HubCountsTableResultSchema = object({
|
|
13271
|
-
corrected: number2()
|
|
13313
|
+
corrected: number2(),
|
|
13314
|
+
deferred: number2()
|
|
13272
13315
|
}).meta({ id: "HubCountsTableResult" });
|
|
13316
|
+
HubCountsReconcileCursorSchema = object({
|
|
13317
|
+
afterId: string2().min(1).max(512).nullable(),
|
|
13318
|
+
table: _enum(["labels", "albums", "artists"])
|
|
13319
|
+
}).meta({ id: "HubCountsReconcileCursor" });
|
|
13273
13320
|
reconcileHubCounts = oc.route({
|
|
13274
13321
|
method: "POST",
|
|
13275
13322
|
operationId: "reconcileHubCounts",
|
|
13276
13323
|
path: "/admin/hub-counts/reconcile",
|
|
13277
13324
|
summary: "Reconcile the maintained hub counts against truth and report the corrected rows",
|
|
13278
13325
|
tags: ["Admin"]
|
|
13279
|
-
}).input(object({
|
|
13326
|
+
}).input(object({
|
|
13327
|
+
cursor: HubCountsReconcileCursorSchema.optional(),
|
|
13328
|
+
pageLimit: number2().int().min(1).max(20).optional()
|
|
13329
|
+
})).output(object({
|
|
13280
13330
|
albums: HubCountsTableResultSchema,
|
|
13281
13331
|
artists: HubCountsTableResultSchema,
|
|
13282
13332
|
labels: HubCountsTableResultSchema,
|
|
13333
|
+
next: HubCountsReconcileCursorSchema.nullable(),
|
|
13283
13334
|
ok: literal(true),
|
|
13335
|
+
pages: number2(),
|
|
13284
13336
|
tookMs: number2()
|
|
13285
13337
|
}));
|
|
13286
13338
|
adminHubCountsContract = {
|
|
@@ -17199,10 +17251,10 @@ async function resolveDeezerSearch(track) {
|
|
|
17199
17251
|
return;
|
|
17200
17252
|
}
|
|
17201
17253
|
try {
|
|
17202
|
-
const query =
|
|
17254
|
+
const query = `${artist} ${stripVersionSuffix(track.title.trim())}`;
|
|
17203
17255
|
const response = await fetch(`https://api.deezer.com/search?q=${encodeURIComponent(query)}`);
|
|
17204
17256
|
const body = await response.json();
|
|
17205
|
-
const hit = (body.data ?? []).find((item) => item.preview && versionMatches(track.title, item.title ?? "") && baseTitleMatches(track.title, item.title ?? "") && durationAgrees(track.durationMs, item.duration));
|
|
17257
|
+
const hit = (body.data ?? []).find((item) => item.preview && normalize(item.artist?.name ?? "").includes(normalize(artist)) && versionMatches(track.title, item.title ?? "") && baseTitleMatches(track.title, item.title ?? "") && durationAgrees(track.durationMs, item.duration));
|
|
17206
17258
|
return downloadPreview(hit?.preview, "deezer:search");
|
|
17207
17259
|
} catch {
|
|
17208
17260
|
return;
|
|
@@ -18474,6 +18526,9 @@ async function requeueAnchorCommand(trackIds) {
|
|
|
18474
18526
|
trackIds
|
|
18475
18527
|
});
|
|
18476
18528
|
}
|
|
18529
|
+
async function requeueIsrcRecoveryCommand(input) {
|
|
18530
|
+
return adminApiPost("/api/v1/admin/catalogue/isrc-recovery/requeue", input);
|
|
18531
|
+
}
|
|
18477
18532
|
async function forceCaptureCommand(trackId) {
|
|
18478
18533
|
return adminApiPost("/api/v1/admin/catalogue/force-capture", {
|
|
18479
18534
|
trackId
|
|
@@ -18601,7 +18656,10 @@ async function resolveArtistCommand(artistId) {
|
|
|
18601
18656
|
}
|
|
18602
18657
|
async function rankArtistsCommand(options) {
|
|
18603
18658
|
const limit = options.limit ? Number.parseInt(options.limit, 10) : undefined;
|
|
18604
|
-
const response = await adminApiPost("/api/v1/admin/artists/rank",
|
|
18659
|
+
const response = await adminApiPost("/api/v1/admin/artists/rank", {
|
|
18660
|
+
...limit ? { limit } : {},
|
|
18661
|
+
...options.countRemaining ? { countRemaining: true } : {}
|
|
18662
|
+
});
|
|
18605
18663
|
return { summary: response.summary };
|
|
18606
18664
|
}
|
|
18607
18665
|
async function backfillArtistImagesCommand(limit, dryRun, cursor) {
|
|
@@ -22127,6 +22185,18 @@ JSON field reference:
|
|
|
22127
22185
|
}
|
|
22128
22186
|
console.log(`Re-queued ${requeued} of ${trackIds.length} row(s) \u2014 the next anchor tick picks them up at their real priority.`);
|
|
22129
22187
|
});
|
|
22188
|
+
catalogue.command("requeue-isrc-recovery").description("Clear Deezer-empty ISRC-recovery stamps from a window (dry-run; --apply to take)").requiredOption("--since <iso>", "Inclusive lower bound on the stamp (2026-09-09)").option("--apply", "Actually clear the stamps (default is a dry-run count)", false).option("--json", "Print JSON", false).action(async (options) => {
|
|
22189
|
+
await Promise.resolve().then(() => init_admin_catalogue2());
|
|
22190
|
+
const result = await requeueIsrcRecoveryCommand({
|
|
22191
|
+
dryRun: options.apply !== true,
|
|
22192
|
+
since: options.since
|
|
22193
|
+
});
|
|
22194
|
+
if (options.json) {
|
|
22195
|
+
printJson({ ok: true, ...result });
|
|
22196
|
+
return;
|
|
22197
|
+
}
|
|
22198
|
+
console.log(result.dryRun ? `DRY RUN \u2014 ${result.matched} row(s) stamped Deezer-empty since ${options.since}. Re-run with --apply to clear them.` : `Cleared ${result.requeued} of ${result.matched} row(s) \u2014 the next isrc-recovery tick asks them again.`);
|
|
22199
|
+
});
|
|
22130
22200
|
catalogue.command("requeue-unmatched").description("Re-queue terminal-unmatched captures after a matcher improvement (operator)").option("--json", "Print JSON", false).action(async (options) => {
|
|
22131
22201
|
await Promise.resolve().then(() => init_admin_catalogue2());
|
|
22132
22202
|
const { requeued, skippedVetoed } = await requeueUnmatchedCommand();
|
|
@@ -24529,7 +24599,10 @@ async function runGalaxies(slug, options, commands) {
|
|
|
24529
24599
|
}
|
|
24530
24600
|
}
|
|
24531
24601
|
async function runArtistsRank(options, rankArtistsCommand) {
|
|
24532
|
-
const { summary } = await rankArtistsCommand({
|
|
24602
|
+
const { summary } = await rankArtistsCommand({
|
|
24603
|
+
countRemaining: !options.json,
|
|
24604
|
+
limit: options.limit
|
|
24605
|
+
});
|
|
24533
24606
|
if (options.json) {
|
|
24534
24607
|
printJson({ ok: true, summary });
|
|
24535
24608
|
return;
|
package/package.json
CHANGED