fluncle 0.258.0 → 0.260.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 +287 -9
  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.258.0".trim() ? "0.258.0".trim() : "0.1.0";
625
+ currentVersion = "0.260.0".trim() ? "0.260.0".trim() : "0.1.0";
626
626
  });
627
627
 
628
628
  // src/update-notifier.ts
@@ -2258,7 +2258,7 @@ var init_about = __esm(() => {
2258
2258
  heading: "For the nerds",
2259
2259
  links: [
2260
2260
  { label: "The Galaxy (game)", url: "https://galaxy.fluncle.com" },
2261
- { label: "SSH (this terminal)", url: "ssh rave.fluncle.com" },
2261
+ { label: "SSH (the rave terminal)", url: "ssh rave.fluncle.com" },
2262
2262
  { label: "Source", url: "https://github.com/mauricekleine/fluncle" }
2263
2263
  ]
2264
2264
  }
@@ -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.258.0".trim() ? "0.258.0".trim() : "0.1.0";
2338
+ currentVersion2 = "0.260.0".trim() ? "0.260.0".trim() : "0.1.0";
2339
2339
  });
2340
2340
 
2341
2341
  // ../../packages/registry/src/index.ts
@@ -3293,7 +3293,7 @@ var init_src = __esm(() => {
3293
3293
  },
3294
3294
  {
3295
3295
  command: "fluncle random",
3296
- exposedContent: ["the archive throws one back"],
3296
+ exposedContent: ["the archive throws a banger back"],
3297
3297
  kind: "cli",
3298
3298
  name: "cli.random",
3299
3299
  weights: { cli: "secondary" }
@@ -3879,6 +3879,24 @@ var init_src = __esm(() => {
3879
3879
  title: "Healthcheck prober",
3880
3880
  weights: { status: "hidden" }
3881
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
+ },
3882
3900
  {
3883
3901
  command: "fluncle admin newsletter draft",
3884
3902
  exposedContent: [
@@ -10776,6 +10794,7 @@ var init_admin_catalogue = __esm(() => {
10776
10794
  gateReason: _enum([
10777
10795
  "friday_window",
10778
10796
  "flag_off",
10797
+ "quota_hold",
10779
10798
  "breaker_quota",
10780
10799
  "breaker_throttle",
10781
10800
  "shared_meter",
@@ -15742,6 +15761,113 @@ var init_editions = __esm(() => {
15742
15761
  };
15743
15762
  });
15744
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
+
15745
15871
  // ../../packages/contracts/src/orpc/health.ts
15746
15872
  var getHealth, healthContract;
15747
15873
  var init_health = __esm(() => {
@@ -15759,6 +15885,74 @@ var init_health = __esm(() => {
15759
15885
  };
15760
15886
  });
15761
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
+
15762
15956
  // ../../packages/contracts/src/orpc/me-galaxy.ts
15763
15957
  var GalaxyProgressSchema, GalaxyMergeBodySchema, GalaxyLogBodySchema, getPrivateGalaxyProgress, mergePrivateGalaxyProgress, collectPrivateGalaxyLog, GalaxyCollectionItemSchema, GalaxyCompletionSchema, listPrivateGalaxyCollection, meGalaxyContract;
15764
15958
  var init_me_galaxy = __esm(() => {
@@ -15971,6 +16165,7 @@ var init_me_saved = __esm(() => {
15971
16165
  init_zod();
15972
16166
  SavedFindingSchema = object({
15973
16167
  artists: array(string2()),
16168
+ href: string2().optional(),
15974
16169
  imageUrl: string2().optional(),
15975
16170
  logId: string2().optional(),
15976
16171
  note: string2().optional(),
@@ -16078,6 +16273,7 @@ var PrivateSubmissionSchema, ProfileBodySchema, getCurrentPrivateUser, getPrivat
16078
16273
  var init_me2 = __esm(() => {
16079
16274
  init_dist3();
16080
16275
  init_zod();
16276
+ init_me_follows();
16081
16277
  init_me_galaxy();
16082
16278
  init_me_preferences();
16083
16279
  init_me_recs();
@@ -16135,6 +16331,8 @@ var init_me2 = __esm(() => {
16135
16331
  ok: literal(true),
16136
16332
  summary: object({
16137
16333
  credentials: string2(),
16334
+ followDigest: string2().optional(),
16335
+ follows: string2().optional(),
16138
16336
  galaxyProgress: string2(),
16139
16337
  preferences: string2().optional(),
16140
16338
  recSeeds: string2().optional(),
@@ -16155,6 +16353,23 @@ var init_me2 = __esm(() => {
16155
16353
  }).output(object({
16156
16354
  export: object({
16157
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(),
16158
16373
  generatedAt: string2(),
16159
16374
  id: string2(),
16160
16375
  preferences: UserPreferencesSchema,
@@ -16298,6 +16513,7 @@ var init_me_watches = __esm(() => {
16298
16513
  kind: unknown().optional()
16299
16514
  });
16300
16515
  listPrivateWatches = oc.route({
16516
+ deprecated: true,
16301
16517
  method: "GET",
16302
16518
  operationId: "listPrivateWatches",
16303
16519
  path: "/me/watches",
@@ -16305,6 +16521,7 @@ var init_me_watches = __esm(() => {
16305
16521
  tags: ["Me"]
16306
16522
  }).output(object({ ok: literal(true), watches: array(WatchSchema) }));
16307
16523
  savePrivateWatch = oc.route({
16524
+ deprecated: true,
16308
16525
  method: "POST",
16309
16526
  operationId: "savePrivateWatch",
16310
16527
  path: "/me/watches",
@@ -16321,6 +16538,7 @@ var init_me_watches = __esm(() => {
16321
16538
  })
16322
16539
  }));
16323
16540
  deletePrivateWatch = oc.route({
16541
+ deprecated: true,
16324
16542
  method: "DELETE",
16325
16543
  operationId: "deletePrivateWatch",
16326
16544
  path: "/me/watches/{id}",
@@ -16838,6 +17056,7 @@ var init_orpc = __esm(() => {
16838
17056
  init_admin_users();
16839
17057
  init_devices();
16840
17058
  init_editions();
17059
+ init_follow_digest();
16841
17060
  init_health();
16842
17061
  init_me2();
16843
17062
  init_me_frontier();
@@ -16846,6 +17065,7 @@ var init_orpc = __esm(() => {
16846
17065
  init_me_recs();
16847
17066
  init_me_saved();
16848
17067
  init_me_sets();
17068
+ init_me_follows();
16849
17069
  init_me_watches();
16850
17070
  init_mix();
16851
17071
  init_mixtapes2();
@@ -16858,6 +17078,7 @@ var init_orpc = __esm(() => {
16858
17078
  init_submissions();
16859
17079
  init_tracks();
16860
17080
  init_albums2();
17081
+ init_follow_digest();
16861
17082
  init_artists2();
16862
17083
  init_labels2();
16863
17084
  init_mix();
@@ -16908,6 +17129,7 @@ var init_orpc = __esm(() => {
16908
17129
  init_me_recs();
16909
17130
  init_me_saved();
16910
17131
  init_me_sets();
17132
+ init_me_follows();
16911
17133
  init_me_watches();
16912
17134
  init_mixtapes2();
16913
17135
  init_newsletter();
@@ -16946,6 +17168,7 @@ var init_orpc = __esm(() => {
16946
17168
  ...adminFunnelContract,
16947
17169
  ...adminHealthContract,
16948
17170
  ...adminHubCountsContract,
17171
+ ...followDigestContract,
16949
17172
  ...adminLabelsContract,
16950
17173
  ...adminLogbookContract,
16951
17174
  ...adminMigrationsContract,
@@ -16972,6 +17195,7 @@ var init_orpc = __esm(() => {
16972
17195
  ...meRecsContract,
16973
17196
  ...meSavedContract,
16974
17197
  ...meSetsContract,
17198
+ ...meFollowsContract,
16975
17199
  ...meWatchesContract,
16976
17200
  ...mixContract,
16977
17201
  ...mixtapesContract,
@@ -17883,6 +18107,22 @@ var init_publish = __esm(() => {
17883
18107
  init_api();
17884
18108
  });
17885
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
+
17886
18126
  // src/commands/capture.ts
17887
18127
  async function captureBudgetCommand() {
17888
18128
  return adminApiGet("/api/v1/admin/catalogue/capture-budget");
@@ -21816,7 +22056,7 @@ function artistRuleLines(rules) {
21816
22056
  }
21817
22057
  function createProgram() {
21818
22058
  const program = configureCommand(new Command);
21819
- program.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) => {
22059
+ program.name("fluncle").description(fluncleTagline).option("--env <local|production>", "Config profile to load (default: production)").showSuggestionAfterError(false).addHelpCommand("help [command]", "Display help for command").hook("preAction", (_thisCommand, actionCommand) => {
21820
22060
  const options = actionCommand.optsWithGlobals();
21821
22061
  setEnvProfile(options.env);
21822
22062
  }).addHelpText("before", `
@@ -21857,7 +22097,7 @@ async function main(args = process.argv.slice(2)) {
21857
22097
  await notifyIfUpdateAvailable(args);
21858
22098
  }
21859
22099
  function configureCommand(command) {
21860
- return command.exitOverride().addHelpCommand(false).configureOutput({
22100
+ return command.exitOverride().helpOption("-h, --help", "Display help for command").addHelpCommand(false).configureOutput({
21861
22101
  writeErr: () => {}
21862
22102
  });
21863
22103
  }
@@ -21906,7 +22146,7 @@ function addListenCommands(program) {
21906
22146
  const openCommands = await Promise.resolve().then(() => (init_open(), exports_open));
21907
22147
  await runOpen(target, [], options, openCommands);
21908
22148
  });
21909
- program.command("random").description("The archive throws one back").option("--json", "Print JSON", false).action(async (options) => {
22149
+ program.command("random").description("The archive throws a banger back").option("--json", "Print JSON", false).action(async (options) => {
21910
22150
  await Promise.resolve().then(() => init_random());
21911
22151
  await runRandom(options, randomCommand);
21912
22152
  });
@@ -21968,7 +22208,7 @@ function addAdminCommands(program) {
21968
22208
  admin.action(() => {
21969
22209
  admin.outputHelp();
21970
22210
  });
21971
- admin.command("help", { hidden: true }).description("display help for command").action(() => {
22211
+ admin.command("help", { hidden: true }).description("Display help for command").action(() => {
21972
22212
  admin.outputHelp();
21973
22213
  });
21974
22214
  admin.command("queue").description("The attention queue: what's waiting, and the day's dispatch").option("--json", "Print JSON", false).action(async (options) => {
@@ -22441,6 +22681,44 @@ JSON field reference:
22441
22681
  await Promise.resolve().then(() => init_publish());
22442
22682
  await runPublishAdvancePause(false, options, publishAdvancePauseCommand);
22443
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
+ });
22444
22722
  const adminCapture = configureCommand(admin.command("capture").description("The metered audio-capture budget and its kill switch"));
22445
22723
  adminCapture.action(() => {
22446
22724
  adminCapture.outputHelp();
@@ -26243,7 +26521,7 @@ Listen:
26243
26521
  fluncle open [--limit 20] [--browser|--app] Pick a track, open it in Spotify
26244
26522
  fluncle open playlist [--browser|--app] Open Fluncle's Findings in Spotify
26245
26523
  fluncle open telegram [--browser|--app] Open the Telegram feed
26246
- fluncle random [--json] The archive throws one back
26524
+ fluncle random [--json] The archive throws a banger back
26247
26525
  fluncle subscribe [email] Fresh bangers, every Friday
26248
26526
 
26249
26527
  Browse:
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.258.0"
34
+ "version": "0.260.0"
35
35
  }