fluncle 0.257.0 → 0.259.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 +373 -4
  2. 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.257.0".trim() ? "0.257.0".trim() : "0.1.0";
625
+ currentVersion = "0.259.0".trim() ? "0.259.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.257.0".trim() ? "0.257.0".trim() : "0.1.0";
2338
+ currentVersion2 = "0.259.0".trim() ? "0.259.0".trim() : "0.1.0";
2339
2339
  });
2340
2340
 
2341
2341
  // ../../packages/registry/src/index.ts
@@ -3711,6 +3711,17 @@ var init_src = __esm(() => {
3711
3711
  title: "Label bios",
3712
3712
  weights: { status: "hidden" }
3713
3713
  },
3714
+ {
3715
+ command: "fluncle admin labels list --seed-state undecided",
3716
+ exposedContent: ["read the undecided crawl-seed pile and report whether a triage round is due"],
3717
+ kind: "cron",
3718
+ name: "cron.label-triage",
3719
+ operatorNotes: "daily. A PURE trigger — zero model tokens, one countless admin read and a sort. Fires only when 40+ NEVER-LOOKED labels have accumulated; stale ones ride along but never trigger a round. It cannot rule: recording a finding is record_label_triage (agent tier) and ruling is update_label (operator tier), which 403s the box token. The batched research leg is deliberately unwired until the gate proves it reports honestly. Source: docs/agents/hermes/scripts/{label-triage-sweep.ts,label-triage-sweep.sh}.",
3720
+ probeConfig: { cadenceMs: 24 * 60 * MINUTE_MS, cronName: "fluncle-label-triage", kind: "cron" },
3721
+ statusDescription: "reports when the label pile needs a round",
3722
+ title: "Label triage gate",
3723
+ weights: { status: "hidden" }
3724
+ },
3714
3725
  {
3715
3726
  command: "fluncle admin albums describe --queue",
3716
3727
  exposedContent: [
@@ -3868,6 +3879,24 @@ var init_src = __esm(() => {
3868
3879
  title: "Healthcheck prober",
3869
3880
  weights: { status: "hidden" }
3870
3881
  },
3882
+ {
3883
+ command: "fluncle admin digests send",
3884
+ exposedContent: [
3885
+ "send new releases from followed artists and labels to signed-in crew each Friday"
3886
+ ],
3887
+ kind: "cron",
3888
+ name: "cron.follow-digest",
3889
+ operatorNotes: "Friday 17:00 Amsterdam, a template-only host-timer sweep. The Worker owns recipient selection, the enabled-by-default kill switch, Resend idempotency keys, and per-call send cap. The box uses only its agent token. Source: docs/agents/hermes/scripts/follow-digest-sweep.*.",
3890
+ probeConfig: {
3891
+ cadenceMs: 7 * 24 * 60 * MINUTE_MS,
3892
+ cronName: "fluncle-follow-digest",
3893
+ kind: "cron",
3894
+ schedule: { time: "17:00", tz: "Europe/Amsterdam", weekday: 5 }
3895
+ },
3896
+ statusDescription: "sends new releases from the artists and labels you follow",
3897
+ title: "Your follows",
3898
+ weights: { status: "secondary" }
3899
+ },
3871
3900
  {
3872
3901
  command: "fluncle admin newsletter draft",
3873
3902
  exposedContent: [
@@ -10765,6 +10794,7 @@ var init_admin_catalogue = __esm(() => {
10765
10794
  gateReason: _enum([
10766
10795
  "friday_window",
10767
10796
  "flag_off",
10797
+ "quota_hold",
10768
10798
  "breaker_quota",
10769
10799
  "breaker_throttle",
10770
10800
  "shared_meter",
@@ -12796,6 +12826,7 @@ var init__shared = __esm(() => {
12796
12826
  postedToTelegram: boolean2(),
12797
12827
  previewUrl: string2().optional(),
12798
12828
  releaseDate: string2().optional(),
12829
+ similar: boolean2().optional(),
12799
12830
  sourceAudioFailures: number2().optional(),
12800
12831
  sourceAudioKey: string2().optional(),
12801
12832
  spotifyUrl: string2(),
@@ -13519,7 +13550,8 @@ var init_admin_hub_counts = __esm(() => {
13519
13550
  init_zod();
13520
13551
  HubCountsTableResultSchema = object({
13521
13552
  corrected: number2(),
13522
- deferred: number2()
13553
+ deferred: number2(),
13554
+ latestCorrected: number2()
13523
13555
  }).meta({ id: "HubCountsTableResult" });
13524
13556
  HubCountsReconcileCursorSchema = object({
13525
13557
  afterId: string2().min(1).max(512).nullable(),
@@ -13549,12 +13581,13 @@ var init_admin_hub_counts = __esm(() => {
13549
13581
  });
13550
13582
 
13551
13583
  // ../../packages/contracts/src/orpc/admin-labels.ts
13552
- var LabelSeedStateSchema, LabelAdminItemSchema, listLabelsAdmin, updateLabel, listLabelArtistRules, replaceLabelArtistRules, MergeLabelResultSchema, mergeLabel, MB_LABEL_MBID_PATTERN, MintLabelOutcomeSchema, LabelTakeOverResultSchema, mintLabel, LabelAliasSourceSchema, LabelAliasKindSchema, LabelAliasCandidateSchema, listLabelAliases, confirmLabelAlias, rejectLabelAlias, DescribeLabelBodySchema, describeLabel, draftLabelBio, LabelBioWorkItemSchema, listLabelsMissingBio, adminLabelsContract;
13584
+ var LabelSeedStateSchema, LabelTriageVerdictSchema, LabelAdminItemSchema, listLabelsAdmin, updateLabel, listLabelArtistRules, replaceLabelArtistRules, MergeLabelResultSchema, mergeLabel, MB_LABEL_MBID_PATTERN, MintLabelOutcomeSchema, LabelTakeOverResultSchema, mintLabel, LabelAliasSourceSchema, LabelAliasKindSchema, LabelAliasCandidateSchema, listLabelAliases, confirmLabelAlias, rejectLabelAlias, DescribeLabelBodySchema, describeLabel, draftLabelBio, LabelBioWorkItemSchema, listLabelsMissingBio, TriageRuleProposalSchema, RecordLabelTriageBodySchema, recordLabelTriage, adminLabelsContract;
13553
13585
  var init_admin_labels = __esm(() => {
13554
13586
  init_dist3();
13555
13587
  init_zod();
13556
13588
  init_admin_artist_rules();
13557
13589
  LabelSeedStateSchema = _enum(["disabled", "enabled", "undecided"]).meta({ id: "LabelSeedState" });
13590
+ LabelTriageVerdictSchema = _enum(["dnb", "dnb_partial", "not_dnb", "unclear"]).meta({ id: "LabelTriageVerdict" });
13558
13591
  LabelAdminItemSchema = object({
13559
13592
  createdAt: string2(),
13560
13593
  disambiguation: string2().nullable().optional(),
@@ -13569,6 +13602,9 @@ var init_admin_labels = __esm(() => {
13569
13602
  scopeChangedAt: string2().nullable().optional(),
13570
13603
  seedState: LabelSeedStateSchema,
13571
13604
  slug: string2(),
13605
+ triageCheckedAt: string2().nullable().optional(),
13606
+ triageReason: string2().nullable().optional(),
13607
+ triageVerdict: LabelTriageVerdictSchema.nullable().optional(),
13572
13608
  updatedAt: string2()
13573
13609
  }).meta({ id: "LabelAdminItem" });
13574
13610
  listLabelsAdmin = oc.route({
@@ -13748,6 +13784,38 @@ var init_admin_labels = __esm(() => {
13748
13784
  summary: "List labels with findings but no bio yet, oldest first (the bio worklist)",
13749
13785
  tags: ["Admin"]
13750
13786
  }).input(object({ limit: string2().optional() })).output(object({ labels: array(LabelBioWorkItemSchema), ok: literal(true) }));
13787
+ TriageRuleProposalSchema = object({
13788
+ artistMbid: string2(),
13789
+ artistName: string2(),
13790
+ evidence: string2().optional(),
13791
+ firstCreditCount: number2().int().min(0),
13792
+ verdict: _enum(["allow", "block"])
13793
+ });
13794
+ RecordLabelTriageBodySchema = object({
13795
+ censusSummary: string2().optional(),
13796
+ confidence: _enum(["high", "medium", "low"]),
13797
+ evidence: string2(),
13798
+ offLaneShare: number2().min(0).max(1).optional(),
13799
+ reason: string2().optional(),
13800
+ residualOffLaneShare: number2().min(0).max(1).optional(),
13801
+ roundId: string2(),
13802
+ rules: array(TriageRuleProposalSchema).optional(),
13803
+ verdict: LabelTriageVerdictSchema,
13804
+ verifyAgrees: boolean2().optional(),
13805
+ verifyEvidence: string2().optional()
13806
+ }).meta({ id: "RecordLabelTriageBody" });
13807
+ recordLabelTriage = oc.route({
13808
+ method: "POST",
13809
+ operationId: "recordLabelTriage",
13810
+ path: "/admin/labels/{slug}/triage",
13811
+ summary: "Record what a triage round found (stamps the cursor; never rules)",
13812
+ tags: ["Admin"]
13813
+ }).input(RecordLabelTriageBodySchema.extend({ slug: string2() })).output(object({
13814
+ droppedInertRules: number2().int(),
13815
+ ok: literal(true),
13816
+ superseded: boolean2(),
13817
+ triageCheckedAt: string2()
13818
+ }));
13751
13819
  adminLabelsContract = {
13752
13820
  confirm_label_alias: confirmLabelAlias,
13753
13821
  describe_label: describeLabel,
@@ -13758,6 +13826,7 @@ var init_admin_labels = __esm(() => {
13758
13826
  list_labels_missing_bio: listLabelsMissingBio,
13759
13827
  merge_label: mergeLabel,
13760
13828
  mint_label: mintLabel,
13829
+ record_label_triage: recordLabelTriage,
13761
13830
  reject_label_alias: rejectLabelAlias,
13762
13831
  replace_label_artist_rules: replaceLabelArtistRules,
13763
13832
  update_label: updateLabel
@@ -15692,6 +15761,113 @@ var init_editions = __esm(() => {
15692
15761
  };
15693
15762
  });
15694
15763
 
15764
+ // ../../packages/contracts/src/orpc/follow-digest.ts
15765
+ var TokenSchema, sendFollowDigests, getFollowDigestState, setFollowDigestState, unsubscribeFollowDigest, subscribeFollowDigest, listDigestFollows, deleteDigestFollow, followDigestContract;
15766
+ var init_follow_digest = __esm(() => {
15767
+ init_dist3();
15768
+ init_zod();
15769
+ TokenSchema = string2().min(10).max(1024);
15770
+ sendFollowDigests = oc.route({
15771
+ method: "POST",
15772
+ operationId: "sendFollowDigests",
15773
+ path: "/admin/follow-digests/send",
15774
+ summary: "Send the weekly digest to eligible followers in a bounded batch",
15775
+ tags: ["Admin"]
15776
+ }).input(object({
15777
+ cursor: string2().optional(),
15778
+ dryRun: boolean2().optional(),
15779
+ limit: number2().int().min(1).max(50).optional()
15780
+ })).output(object({
15781
+ capped: boolean2(),
15782
+ considered: number2(),
15783
+ dryRun: boolean2(),
15784
+ empty: number2(),
15785
+ failed: number2(),
15786
+ nextCursor: string2().optional(),
15787
+ ok: literal(true),
15788
+ paused: boolean2(),
15789
+ sent: number2(),
15790
+ skipped: number2(),
15791
+ unknown: number2(),
15792
+ weekKey: string2()
15793
+ }));
15794
+ getFollowDigestState = oc.route({
15795
+ method: "GET",
15796
+ operationId: "getFollowDigestState",
15797
+ path: "/admin/follow-digests/state",
15798
+ summary: "Read the follow digest kill switch",
15799
+ tags: ["Admin"]
15800
+ }).output(object({ ok: literal(true), paused: boolean2() }));
15801
+ setFollowDigestState = oc.route({
15802
+ method: "PUT",
15803
+ operationId: "setFollowDigestState",
15804
+ path: "/admin/follow-digests/state",
15805
+ summary: "Pause or resume weekly follow digests",
15806
+ tags: ["Admin"]
15807
+ }).input(object({ paused: boolean2() })).output(object({ ok: literal(true), paused: boolean2() }));
15808
+ unsubscribeFollowDigest = oc.route({
15809
+ inputStructure: "detailed",
15810
+ method: "POST",
15811
+ operationId: "unsubscribeFollowDigest",
15812
+ path: "/follow-digest/unsubscribe",
15813
+ summary: "Unsubscribe a signed follow digest recipient",
15814
+ tags: ["Follow digest"]
15815
+ }).input(object({
15816
+ body: object({
15817
+ "List-Unsubscribe": literal("One-Click").optional(),
15818
+ token: TokenSchema.optional()
15819
+ }).optional(),
15820
+ query: object({ token: TokenSchema.optional() })
15821
+ })).output(object({ ok: literal(true), subscribed: literal(false) }));
15822
+ subscribeFollowDigest = oc.route({
15823
+ inputStructure: "detailed",
15824
+ method: "POST",
15825
+ operationId: "subscribeFollowDigest",
15826
+ path: "/follow-digest/subscribe",
15827
+ summary: "Subscribe a signed follow digest recipient",
15828
+ tags: ["Follow digest"]
15829
+ }).input(object({
15830
+ body: object({ token: TokenSchema.optional() }).optional(),
15831
+ query: object({ token: TokenSchema.optional() })
15832
+ })).output(object({ ok: literal(true), subscribed: literal(true) }));
15833
+ listDigestFollows = oc.route({
15834
+ method: "GET",
15835
+ operationId: "listDigestFollows",
15836
+ path: "/follow-digest/follows",
15837
+ summary: "List follows for a signed digest recipient",
15838
+ tags: ["Follow digest"]
15839
+ }).input(object({ token: TokenSchema })).output(object({
15840
+ follows: array(object({
15841
+ id: string2(),
15842
+ kind: _enum(["artist", "label"]),
15843
+ name: string2(),
15844
+ slug: string2()
15845
+ })),
15846
+ ok: literal(true),
15847
+ subscribed: boolean2()
15848
+ }));
15849
+ deleteDigestFollow = oc.route({
15850
+ inputStructure: "detailed",
15851
+ method: "DELETE",
15852
+ operationId: "deleteDigestFollow",
15853
+ path: "/follow-digest/follows/{id}",
15854
+ summary: "Remove one follow for a signed digest recipient",
15855
+ tags: ["Follow digest"]
15856
+ }).input(object({
15857
+ params: object({ id: string2().min(1) }),
15858
+ query: object({ token: TokenSchema })
15859
+ })).output(object({ ok: literal(true) }));
15860
+ followDigestContract = {
15861
+ delete_digest_follow: deleteDigestFollow,
15862
+ get_follow_digest_state: getFollowDigestState,
15863
+ list_digest_follows: listDigestFollows,
15864
+ send_follow_digests: sendFollowDigests,
15865
+ set_follow_digest_state: setFollowDigestState,
15866
+ subscribe_follow_digest: subscribeFollowDigest,
15867
+ unsubscribe_follow_digest: unsubscribeFollowDigest
15868
+ };
15869
+ });
15870
+
15695
15871
  // ../../packages/contracts/src/orpc/health.ts
15696
15872
  var getHealth, healthContract;
15697
15873
  var init_health = __esm(() => {
@@ -15709,6 +15885,74 @@ var init_health = __esm(() => {
15709
15885
  };
15710
15886
  });
15711
15887
 
15888
+ // ../../packages/contracts/src/orpc/me-follows.ts
15889
+ var FollowKindSchema, FollowSchema, SaveFollowBodySchema, listPrivateFollows, savePrivateFollow, deletePrivateFollow, revokePrivateFollowLinkAccess, meFollowsContract;
15890
+ var init_me_follows = __esm(() => {
15891
+ init_dist3();
15892
+ init_zod();
15893
+ FollowKindSchema = _enum(["artist", "label"]).meta({ id: "FollowKind" });
15894
+ FollowSchema = object({
15895
+ createdAt: string2(),
15896
+ entityId: string2(),
15897
+ id: string2(),
15898
+ includeSimilar: boolean2(),
15899
+ kind: FollowKindSchema,
15900
+ name: string2(),
15901
+ slug: string2()
15902
+ }).meta({ id: "Follow" });
15903
+ SaveFollowBodySchema = looseObject({
15904
+ entityId: unknown().optional(),
15905
+ intent: unknown().optional(),
15906
+ kind: unknown().optional()
15907
+ });
15908
+ listPrivateFollows = oc.route({
15909
+ method: "GET",
15910
+ operationId: "listPrivateFollows",
15911
+ path: "/me/follows",
15912
+ summary: "List the artists and labels the signed-in user follows",
15913
+ tags: ["Me"]
15914
+ }).output(object({ follows: array(FollowSchema), ok: literal(true) }));
15915
+ savePrivateFollow = oc.route({
15916
+ method: "POST",
15917
+ operationId: "savePrivateFollow",
15918
+ path: "/me/follows",
15919
+ summary: "Follow an artist or label for the signed-in user",
15920
+ tags: ["Me"]
15921
+ }).input(SaveFollowBodySchema).output(object({
15922
+ follow: object({
15923
+ createdAt: string2(),
15924
+ entityId: string2(),
15925
+ id: string2(),
15926
+ includeSimilar: boolean2(),
15927
+ kind: FollowKindSchema,
15928
+ name: string2(),
15929
+ slug: string2()
15930
+ }),
15931
+ followsEmail: boolean2(),
15932
+ ok: literal(true)
15933
+ }));
15934
+ deletePrivateFollow = oc.route({
15935
+ method: "DELETE",
15936
+ operationId: "deletePrivateFollow",
15937
+ path: "/me/follows/{id}",
15938
+ summary: "Unfollow an artist or label for the signed-in user",
15939
+ tags: ["Me"]
15940
+ }).input(object({ id: string2() })).output(object({ ok: literal(true) }));
15941
+ revokePrivateFollowLinkAccess = oc.route({
15942
+ method: "POST",
15943
+ operationId: "revokePrivateFollowLinkAccess",
15944
+ path: "/me/follow-link-access/revoke",
15945
+ summary: "Revoke emailed follows management links for the signed-in user",
15946
+ tags: ["Me"]
15947
+ }).output(object({ ok: literal(true), token: string2() }));
15948
+ meFollowsContract = {
15949
+ delete_private_follow: deletePrivateFollow,
15950
+ list_private_follows: listPrivateFollows,
15951
+ revoke_private_follow_link_access: revokePrivateFollowLinkAccess,
15952
+ save_private_follow: savePrivateFollow
15953
+ };
15954
+ });
15955
+
15712
15956
  // ../../packages/contracts/src/orpc/me-galaxy.ts
15713
15957
  var GalaxyProgressSchema, GalaxyMergeBodySchema, GalaxyLogBodySchema, getPrivateGalaxyProgress, mergePrivateGalaxyProgress, collectPrivateGalaxyLog, GalaxyCollectionItemSchema, GalaxyCompletionSchema, listPrivateGalaxyCollection, meGalaxyContract;
15714
15958
  var init_me_galaxy = __esm(() => {
@@ -15921,6 +16165,7 @@ var init_me_saved = __esm(() => {
15921
16165
  init_zod();
15922
16166
  SavedFindingSchema = object({
15923
16167
  artists: array(string2()),
16168
+ href: string2().optional(),
15924
16169
  imageUrl: string2().optional(),
15925
16170
  logId: string2().optional(),
15926
16171
  note: string2().optional(),
@@ -16028,6 +16273,7 @@ var PrivateSubmissionSchema, ProfileBodySchema, getCurrentPrivateUser, getPrivat
16028
16273
  var init_me2 = __esm(() => {
16029
16274
  init_dist3();
16030
16275
  init_zod();
16276
+ init_me_follows();
16031
16277
  init_me_galaxy();
16032
16278
  init_me_preferences();
16033
16279
  init_me_recs();
@@ -16085,6 +16331,8 @@ var init_me2 = __esm(() => {
16085
16331
  ok: literal(true),
16086
16332
  summary: object({
16087
16333
  credentials: string2(),
16334
+ followDigest: string2().optional(),
16335
+ follows: string2().optional(),
16088
16336
  galaxyProgress: string2(),
16089
16337
  preferences: string2().optional(),
16090
16338
  recSeeds: string2().optional(),
@@ -16105,6 +16353,23 @@ var init_me2 = __esm(() => {
16105
16353
  }).output(object({
16106
16354
  export: object({
16107
16355
  account: PublicUserSchema,
16356
+ followDigest: object({
16357
+ lastReleaseCount: number2().nullable(),
16358
+ lastSentAt: string2().nullable(),
16359
+ lastWeekKey: string2().nullable(),
16360
+ unsubscribedAt: string2().nullable(),
16361
+ updatedAt: string2()
16362
+ }).nullable().optional(),
16363
+ followDigestDeliveries: array(object({
16364
+ attempts: number2(),
16365
+ claimedAt: string2(),
16366
+ id: string2(),
16367
+ payloadJson: string2(),
16368
+ sentAt: string2().nullable(),
16369
+ status: string2(),
16370
+ weekKey: string2()
16371
+ })).optional(),
16372
+ follows: array(FollowSchema).optional(),
16108
16373
  generatedAt: string2(),
16109
16374
  id: string2(),
16110
16375
  preferences: UserPreferencesSchema,
@@ -16248,6 +16513,7 @@ var init_me_watches = __esm(() => {
16248
16513
  kind: unknown().optional()
16249
16514
  });
16250
16515
  listPrivateWatches = oc.route({
16516
+ deprecated: true,
16251
16517
  method: "GET",
16252
16518
  operationId: "listPrivateWatches",
16253
16519
  path: "/me/watches",
@@ -16255,6 +16521,7 @@ var init_me_watches = __esm(() => {
16255
16521
  tags: ["Me"]
16256
16522
  }).output(object({ ok: literal(true), watches: array(WatchSchema) }));
16257
16523
  savePrivateWatch = oc.route({
16524
+ deprecated: true,
16258
16525
  method: "POST",
16259
16526
  operationId: "savePrivateWatch",
16260
16527
  path: "/me/watches",
@@ -16271,6 +16538,7 @@ var init_me_watches = __esm(() => {
16271
16538
  })
16272
16539
  }));
16273
16540
  deletePrivateWatch = oc.route({
16541
+ deprecated: true,
16274
16542
  method: "DELETE",
16275
16543
  operationId: "deletePrivateWatch",
16276
16544
  path: "/me/watches/{id}",
@@ -16788,6 +17056,7 @@ var init_orpc = __esm(() => {
16788
17056
  init_admin_users();
16789
17057
  init_devices();
16790
17058
  init_editions();
17059
+ init_follow_digest();
16791
17060
  init_health();
16792
17061
  init_me2();
16793
17062
  init_me_frontier();
@@ -16796,6 +17065,7 @@ var init_orpc = __esm(() => {
16796
17065
  init_me_recs();
16797
17066
  init_me_saved();
16798
17067
  init_me_sets();
17068
+ init_me_follows();
16799
17069
  init_me_watches();
16800
17070
  init_mix();
16801
17071
  init_mixtapes2();
@@ -16808,6 +17078,7 @@ var init_orpc = __esm(() => {
16808
17078
  init_submissions();
16809
17079
  init_tracks();
16810
17080
  init_albums2();
17081
+ init_follow_digest();
16811
17082
  init_artists2();
16812
17083
  init_labels2();
16813
17084
  init_mix();
@@ -16858,6 +17129,7 @@ var init_orpc = __esm(() => {
16858
17129
  init_me_recs();
16859
17130
  init_me_saved();
16860
17131
  init_me_sets();
17132
+ init_me_follows();
16861
17133
  init_me_watches();
16862
17134
  init_mixtapes2();
16863
17135
  init_newsletter();
@@ -16896,6 +17168,7 @@ var init_orpc = __esm(() => {
16896
17168
  ...adminFunnelContract,
16897
17169
  ...adminHealthContract,
16898
17170
  ...adminHubCountsContract,
17171
+ ...followDigestContract,
16899
17172
  ...adminLabelsContract,
16900
17173
  ...adminLogbookContract,
16901
17174
  ...adminMigrationsContract,
@@ -16922,6 +17195,7 @@ var init_orpc = __esm(() => {
16922
17195
  ...meRecsContract,
16923
17196
  ...meSavedContract,
16924
17197
  ...meSetsContract,
17198
+ ...meFollowsContract,
16925
17199
  ...meWatchesContract,
16926
17200
  ...mixContract,
16927
17201
  ...mixtapesContract,
@@ -17833,6 +18107,22 @@ var init_publish = __esm(() => {
17833
18107
  init_api();
17834
18108
  });
17835
18109
 
18110
+ // src/commands/digests.ts
18111
+ async function sendFollowDigestsCommand(options) {
18112
+ return adminApiPost("/api/v1/admin/follow-digests/send", options);
18113
+ }
18114
+ async function getFollowDigestStateCommand() {
18115
+ const result = await adminApiGet("/api/v1/admin/follow-digests/state");
18116
+ return result.paused;
18117
+ }
18118
+ async function setFollowDigestStateCommand(paused) {
18119
+ const result = await adminApiPut("/api/v1/admin/follow-digests/state", { paused });
18120
+ return result.paused;
18121
+ }
18122
+ var init_digests = __esm(() => {
18123
+ init_api();
18124
+ });
18125
+
17836
18126
  // src/commands/capture.ts
17837
18127
  async function captureBudgetCommand() {
17838
18128
  return adminApiGet("/api/v1/admin/catalogue/capture-budget");
@@ -19255,6 +19545,19 @@ async function mintLabelCommand(mbLabelId, seedState, takeOverSlug) {
19255
19545
  });
19256
19546
  return { label: response.label, outcome: response.outcome, takenOver: response.takenOver };
19257
19547
  }
19548
+ async function listLabelsAdminCommand(seedState) {
19549
+ const query = seedState === undefined ? "" : `?seedState=${encodeURIComponent(seedState)}`;
19550
+ const response = await adminApiGet(`/api/v1/admin/labels${query}`);
19551
+ return response.labels;
19552
+ }
19553
+ async function recordLabelTriageCommand(slug, payload) {
19554
+ const response = await adminApiPost(`/api/v1/admin/labels/${encodeURIComponent(slug.trim().toLowerCase())}/triage`, payload);
19555
+ return {
19556
+ droppedInertRules: response.droppedInertRules,
19557
+ superseded: response.superseded,
19558
+ triageCheckedAt: response.triageCheckedAt
19559
+ };
19560
+ }
19258
19561
  async function describeLabelCommand(slug, options) {
19259
19562
  return adminApiPost(`/api/v1/admin/labels/${encodeURIComponent(slug)}/bio`, buildBioBody2(options));
19260
19563
  }
@@ -22378,6 +22681,44 @@ JSON field reference:
22378
22681
  await Promise.resolve().then(() => init_publish());
22379
22682
  await runPublishAdvancePause(false, options, publishAdvancePauseCommand);
22380
22683
  });
22684
+ const adminFollowDigests = configureCommand(admin.command("digests").description("Weekly email digest for followed artists and labels"));
22685
+ adminFollowDigests.action(() => adminFollowDigests.outputHelp());
22686
+ adminFollowDigests.command("status").description("Read the follow digest kill switch").option("--json", "Print JSON", false).action(async (options) => {
22687
+ await Promise.resolve().then(() => init_digests());
22688
+ const paused = await getFollowDigestStateCommand();
22689
+ if (options.json) {
22690
+ printJson({ ok: true, paused });
22691
+ } else {
22692
+ console.log(paused ? "Follow digests are paused." : "Follow digests are running.");
22693
+ }
22694
+ });
22695
+ for (const [verb, paused] of [
22696
+ ["pause", true],
22697
+ ["resume", false]
22698
+ ]) {
22699
+ adminFollowDigests.command(verb).description(paused ? "Pause follow digests" : "Resume follow digests").option("--json", "Print JSON", false).action(async (options) => {
22700
+ await Promise.resolve().then(() => init_digests());
22701
+ const result = await setFollowDigestStateCommand(paused);
22702
+ if (options.json) {
22703
+ printJson({ ok: true, paused: result });
22704
+ } else {
22705
+ console.log(result ? "Follow digests are paused." : "Follow digests are running.");
22706
+ }
22707
+ });
22708
+ }
22709
+ adminFollowDigests.command("send").description("Send one bounded follow digest batch").option("--cursor <id>", "Continue after this user id").option("--limit <number>", "Maximum users to consider").option("--dry-run", "Count eligible emails without sending").option("--json", "Print JSON", false).action(async (options) => {
22710
+ await Promise.resolve().then(() => init_digests());
22711
+ const result = await sendFollowDigestsCommand({
22712
+ cursor: options.cursor,
22713
+ dryRun: options.dryRun,
22714
+ limit: options.limit === undefined ? undefined : Number(options.limit)
22715
+ });
22716
+ if (options.json) {
22717
+ printJson(result);
22718
+ } else {
22719
+ console.log(`Considered ${result.considered}; sent ${result.sent}; empty ${result.empty}; next ${result.nextCursor ?? "done"}.`);
22720
+ }
22721
+ });
22381
22722
  const adminCapture = configureCommand(admin.command("capture").description("The metered audio-capture budget and its kill switch"));
22382
22723
  adminCapture.action(() => {
22383
22724
  adminCapture.outputHelp();
@@ -23064,6 +23405,33 @@ JSON field reference:
23064
23405
  console.log(line);
23065
23406
  }
23066
23407
  });
23408
+ labels.command("list").description("List labels with their crawl-seed state and triage cursor").option("--seed-state <state>", "Only this state: enabled, disabled, or undecided").option("--json", "Print JSON", false).action(async (options) => {
23409
+ const seedState = options.seedState;
23410
+ if (seedState !== undefined && seedState !== "enabled" && seedState !== "disabled" && seedState !== "undecided") {
23411
+ throw new Error("Pass --seed-state enabled|disabled|undecided");
23412
+ }
23413
+ await Promise.resolve().then(() => init_admin_labels2());
23414
+ const labels = await listLabelsAdminCommand(seedState);
23415
+ if (options.json) {
23416
+ console.log(JSON.stringify({ labels }, null, 2));
23417
+ return;
23418
+ }
23419
+ for (const label of labels) {
23420
+ const looked = label.triageCheckedAt ? label.triageVerdict : "never looked";
23421
+ console.log(`${label.slug} ${label.seedState} ${looked ?? ""}`.trimEnd());
23422
+ }
23423
+ });
23424
+ labels.command("triage").description("Record what a triage round found for a label (agent; never rules)").argument("<slug>", "The label slug").requiredOption("--payload <file>", "JSON file holding the round's finding for this label").option("--json", "Print JSON", false).action(async (slug, options) => {
23425
+ const payload = JSON.parse(readFileSync6(options.payload, "utf8"));
23426
+ await Promise.resolve().then(() => init_admin_labels2());
23427
+ const result = await recordLabelTriageCommand(slug, payload);
23428
+ if (options.json) {
23429
+ console.log(JSON.stringify(result, null, 2));
23430
+ return;
23431
+ }
23432
+ const dropped = result.droppedInertRules > 0 ? ` \u2014 dropped ${result.droppedInertRules} inert` : "";
23433
+ console.log(`${slug} \u2192 ${payload.verdict}${dropped}${result.superseded ? " (superseded)" : ""}`);
23434
+ });
23067
23435
  labels.command("mint").description("Mint a label from its MusicBrainz MBID (operator; idempotent)").argument("<mbid>", "The MusicBrainz label MBID").option("--seed-state <state>", "Rule it on arrival: enabled, disabled, or undecided").option("--take-over <slug>", "Re-point this trackless row's identity onto the minted entity").option("--json", "Print JSON", false).action(async (mbid, options) => {
23068
23436
  const seedState = options.seedState;
23069
23437
  if (seedState !== undefined && seedState !== "enabled" && seedState !== "disabled" && seedState !== "undecided") {
@@ -26094,6 +26462,7 @@ var stringOptions = new Set([
26094
26462
  "--page-digest",
26095
26463
  "--page-limit",
26096
26464
  "--parent-id",
26465
+ "--payload",
26097
26466
  "--plate",
26098
26467
  "--plate-background",
26099
26468
  "--platform",
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.257.0"
34
+ "version": "0.259.0"
35
35
  }