fluncle 0.64.0 → 0.66.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 +310 -34
  2. package/package.json +1 -1
package/bin/fluncle.mjs CHANGED
@@ -423,7 +423,7 @@ function parseVersion(version) {
423
423
  var currentVersion;
424
424
  var init_version = __esm(() => {
425
425
  init_output();
426
- currentVersion = "0.64.0".trim() ? "0.64.0".trim() : "0.1.0";
426
+ currentVersion = "0.66.0".trim() ? "0.66.0".trim() : "0.1.0";
427
427
  });
428
428
 
429
429
  // src/update-notifier.ts
@@ -657,7 +657,68 @@ var init_env = __esm(() => {
657
657
  envProfiles2 = ["local", "production"];
658
658
  });
659
659
 
660
+ // src/user-token.ts
661
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
662
+ import { homedir as homedir3 } from "node:os";
663
+ import { dirname as dirname2, join as join3 } from "node:path";
664
+ function activeProfile() {
665
+ const profile = process.env.FLUNCLE_ENV ?? defaultEnvProfile2;
666
+ return envProfiles3.includes(profile) ? profile : defaultEnvProfile2;
667
+ }
668
+ function userTokenPath() {
669
+ return join3(homedir3(), ".config", "fluncle", `user.${activeProfile()}.json`);
670
+ }
671
+ function isStoredUserToken(value) {
672
+ if (typeof value !== "object" || value === null) {
673
+ return false;
674
+ }
675
+ const record = value;
676
+ return typeof record.token === "string" && typeof record.baseUrl === "string";
677
+ }
678
+ function readUserToken() {
679
+ try {
680
+ const parsed = JSON.parse(readFileSync(userTokenPath(), "utf8"));
681
+ return isStoredUserToken(parsed) ? parsed : undefined;
682
+ } catch {
683
+ return;
684
+ }
685
+ }
686
+ function writeUserToken(value) {
687
+ const path2 = userTokenPath();
688
+ mkdirSync(dirname2(path2), { recursive: true });
689
+ writeFileSync(path2, `${JSON.stringify(value, null, 2)}
690
+ `, { mode: 384 });
691
+ }
692
+ function clearUserToken() {
693
+ const path2 = userTokenPath();
694
+ try {
695
+ readFileSync(path2);
696
+ } catch {
697
+ return false;
698
+ }
699
+ rmSync(path2, { force: true });
700
+ return true;
701
+ }
702
+ var envProfiles3, defaultEnvProfile2 = "production";
703
+ var init_user_token = __esm(() => {
704
+ envProfiles3 = ["local", "production"];
705
+ });
706
+
660
707
  // src/api.ts
708
+ async function userApiGet(path2) {
709
+ return apiRequest(path2, {
710
+ headers: userHeaders()
711
+ });
712
+ }
713
+ function userHeaders() {
714
+ const stored = readUserToken();
715
+ if (!stored) {
716
+ throw new CliError2("not_logged_in", "You're not signed in. Run `fluncle login` to link this device to your account.");
717
+ }
718
+ return {
719
+ Authorization: `Bearer ${stored.token}`
720
+ };
721
+ }
661
722
  async function publicApiGet(path2) {
662
723
  return apiRequest(path2);
663
724
  }
@@ -747,6 +808,7 @@ function parseJson(text) {
747
808
  var init_api = __esm(() => {
748
809
  init_env();
749
810
  init_output();
811
+ init_user_token();
750
812
  });
751
813
 
752
814
  // src/commands/recent.ts
@@ -1163,7 +1225,7 @@ __export(exports_mixtapes, {
1163
1225
  mixtapeDeleteCommand: () => mixtapeDeleteCommand,
1164
1226
  mixtapeCreateCommand: () => mixtapeCreateCommand
1165
1227
  });
1166
- import { existsSync, readFileSync } from "node:fs";
1228
+ import { existsSync, readFileSync as readFileSync2 } from "node:fs";
1167
1229
  async function mixtapesCommand() {
1168
1230
  const response = await publicApiGet("/api/mixtapes");
1169
1231
  return response.mixtapes;
@@ -1294,7 +1356,7 @@ function parseCueFile(filePath) {
1294
1356
  if (!existsSync(filePath)) {
1295
1357
  throw new CliError2("file_not_found", `Cue file not found: ${filePath}`);
1296
1358
  }
1297
- const text = readFileSync(filePath, "utf-8");
1359
+ const text = readFileSync2(filePath, "utf-8");
1298
1360
  const trimmed = text.trim();
1299
1361
  if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
1300
1362
  try {
@@ -1505,6 +1567,36 @@ var init_interactive = __esm(() => {
1505
1567
  // src/links.ts
1506
1568
  var spotifyPlaylistUrl2 = "https://open.spotify.com/playlist/1m5LADqpLjiBERdtqrIiL0", telegramUrl2 = "https://t.me/fluncle";
1507
1569
 
1570
+ // src/open-external.ts
1571
+ async function openExternal(target) {
1572
+ const command = platformOpenCommand();
1573
+ if (!command) {
1574
+ console.log(target);
1575
+ throw new CliError2("unsupported_platform", `Automatic opening is only supported on macOS and Linux. Open this manually: ${target}`);
1576
+ }
1577
+ const child = Bun.spawn([command, target], {
1578
+ stderr: "pipe",
1579
+ stdout: "pipe"
1580
+ });
1581
+ const exitCode = await child.exited;
1582
+ if (exitCode !== 0) {
1583
+ const stderr = await new Response(child.stderr).text();
1584
+ throw new CliError2("open_failed", stderr.trim() || `Could not open ${target} with ${command}`);
1585
+ }
1586
+ }
1587
+ function platformOpenCommand() {
1588
+ if (process.platform === "darwin") {
1589
+ return "open";
1590
+ }
1591
+ if (process.platform === "linux") {
1592
+ return "xdg-open";
1593
+ }
1594
+ return;
1595
+ }
1596
+ var init_open_external = __esm(() => {
1597
+ init_output();
1598
+ });
1599
+
1508
1600
  // src/commands/recent.ts
1509
1601
  function mapTrack2(track) {
1510
1602
  if (track.type === "mixtape") {
@@ -1598,31 +1690,6 @@ function spotifyPlaylistUrlToAppUri(playlistUrl) {
1598
1690
  }
1599
1691
  return `spotify:playlist:${playlistId}`;
1600
1692
  }
1601
- async function openExternal(target) {
1602
- const command = platformOpenCommand();
1603
- if (!command) {
1604
- console.log(target);
1605
- throw new CliError2("unsupported_platform", `Automatic opening is only supported on macOS and Linux. Open this manually: ${target}`);
1606
- }
1607
- const child = Bun.spawn([command, target], {
1608
- stderr: "pipe",
1609
- stdout: "pipe"
1610
- });
1611
- const exitCode = await child.exited;
1612
- if (exitCode !== 0) {
1613
- const stderr = await new Response(child.stderr).text();
1614
- throw new CliError2("open_failed", stderr.trim() || `Could not open ${target} with ${command}`);
1615
- }
1616
- }
1617
- function platformOpenCommand() {
1618
- if (process.platform === "darwin") {
1619
- return "open";
1620
- }
1621
- if (process.platform === "linux") {
1622
- return "xdg-open";
1623
- }
1624
- return;
1625
- }
1626
1693
  async function selectRecentTrack(tracks) {
1627
1694
  return await selectWithKeyboard(tracks, {
1628
1695
  nonInteractiveMessage: SELECT_NON_INTERACTIVE_MESSAGE,
@@ -1649,6 +1716,7 @@ var TELEGRAM_APP_URI = "tg://resolve?domain=fluncle", SPOTIFY_PLAYLIST_OPEN_URL,
1649
1716
  var init_open = __esm(() => {
1650
1717
  init_format();
1651
1718
  init_interactive();
1719
+ init_open_external();
1652
1720
  init_output();
1653
1721
  init_recent2();
1654
1722
  SPOTIFY_PLAYLIST_OPEN_URL = `${spotifyPlaylistUrl2}?si=054d3c6cbcf14a36`;
@@ -1756,6 +1824,162 @@ var init_submit = __esm(() => {
1756
1824
  init_output();
1757
1825
  });
1758
1826
 
1827
+ // src/commands/login.ts
1828
+ var exports_login = {};
1829
+ __export(exports_login, {
1830
+ logoutCommand: () => logoutCommand,
1831
+ loginCommand: () => loginCommand
1832
+ });
1833
+ async function loginCommand() {
1834
+ const baseUrl = getApiBaseUrl();
1835
+ const existing = readUserToken();
1836
+ if (existing) {
1837
+ const who2 = existing.user?.username ?? existing.user?.name ?? "your account";
1838
+ console.log(`Already signed in as ${who2}. Run \`fluncle logout\` first to switch accounts.`);
1839
+ return;
1840
+ }
1841
+ const device = await requestDeviceCode(baseUrl);
1842
+ const verificationUrl = device.verification_uri_complete ?? device.verification_uri;
1843
+ console.log(`Linking this device to your Fluncle account.
1844
+ `);
1845
+ console.log(` Code: ${device.user_code}`);
1846
+ console.log(` Open: ${verificationUrl}
1847
+ `);
1848
+ console.log("Opening your browser to approve… (sign in if you aren't already)");
1849
+ try {
1850
+ await openExternal(verificationUrl);
1851
+ } catch {
1852
+ console.log("Couldn't open a browser automatically — visit the URL above to approve.");
1853
+ }
1854
+ const token = await pollForToken(baseUrl, device);
1855
+ const user = await fetchSessionUser(baseUrl, token.access_token);
1856
+ writeUserToken({
1857
+ baseUrl,
1858
+ token: token.access_token,
1859
+ user
1860
+ });
1861
+ const who = user?.username ?? user?.name ?? "cosmonaut";
1862
+ console.log(`
1863
+ Aboard, ${who}. This device is linked to your Galaxy.`);
1864
+ console.log("Try `fluncle me` to see your progress.");
1865
+ }
1866
+ async function logoutCommand() {
1867
+ const stored = readUserToken();
1868
+ if (stored) {
1869
+ await revokeSession(stored.baseUrl, stored.token).catch(() => {
1870
+ return;
1871
+ });
1872
+ }
1873
+ const cleared = clearUserToken();
1874
+ console.log(cleared ? "Signed out. This device is no longer linked to your account." : "Not signed in — nothing to do.");
1875
+ }
1876
+ async function requestDeviceCode(baseUrl) {
1877
+ const response = await fetch(`${baseUrl}/api/auth/device/code`, {
1878
+ body: JSON.stringify({ client_id: CLI_CLIENT_ID, scope: DEVICE_SCOPE }),
1879
+ headers: { "Content-Type": "application/json" },
1880
+ method: "POST"
1881
+ });
1882
+ const data = await response.json().catch(() => {
1883
+ return;
1884
+ });
1885
+ if (!response.ok || !data?.device_code || !data.user_code) {
1886
+ throw new CliError2("device_code_failed", `Couldn't start the login flow (${response.status}). Try again in a moment.`);
1887
+ }
1888
+ return data;
1889
+ }
1890
+ async function pollForToken(baseUrl, device) {
1891
+ let intervalMs = Math.max(device.interval, 1) * 1000;
1892
+ const deadline = Date.now() + Math.max(device.expires_in, 1) * 1000;
1893
+ await sleep(intervalMs);
1894
+ while (Date.now() < deadline) {
1895
+ const response = await fetch(`${baseUrl}/api/auth/device/token`, {
1896
+ body: JSON.stringify({
1897
+ client_id: CLI_CLIENT_ID,
1898
+ device_code: device.device_code,
1899
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
1900
+ }),
1901
+ headers: { "Content-Type": "application/json" },
1902
+ method: "POST"
1903
+ });
1904
+ const data = await response.json().catch(() => {
1905
+ return;
1906
+ });
1907
+ if (response.ok && data && "access_token" in data && data.access_token) {
1908
+ return data;
1909
+ }
1910
+ const pending = data && "error" in data ? data : undefined;
1911
+ switch (pending?.error) {
1912
+ case "authorization_pending":
1913
+ break;
1914
+ case "slow_down":
1915
+ intervalMs += 5000;
1916
+ break;
1917
+ case "access_denied":
1918
+ throw new CliError2("access_denied", "Sign-in was denied in the browser. Nothing linked.");
1919
+ case "expired_token":
1920
+ throw new CliError2("expired_token", "The login request timed out. Run `fluncle login` again.");
1921
+ default:
1922
+ throw new CliError2("device_token_failed", pending?.error_description ?? `Login failed (${response.status}).`);
1923
+ }
1924
+ await sleep(intervalMs);
1925
+ }
1926
+ throw new CliError2("expired_token", "The login request timed out. Run `fluncle login` again.");
1927
+ }
1928
+ async function fetchSessionUser(baseUrl, token) {
1929
+ const response = await fetch(`${baseUrl}/api/me`, {
1930
+ headers: { Authorization: `Bearer ${token}` }
1931
+ });
1932
+ if (!response.ok) {
1933
+ return;
1934
+ }
1935
+ const data = await response.json().catch(() => {
1936
+ return;
1937
+ });
1938
+ return data?.user ?? undefined;
1939
+ }
1940
+ async function revokeSession(baseUrl, token) {
1941
+ await fetch(`${baseUrl}/api/auth/sign-out`, {
1942
+ headers: { Authorization: `Bearer ${token}` },
1943
+ method: "POST"
1944
+ });
1945
+ }
1946
+ function sleep(ms) {
1947
+ return new Promise((resolve) => setTimeout(resolve, ms));
1948
+ }
1949
+ var CLI_CLIENT_ID = "fluncle-cli", DEVICE_SCOPE = "galaxy-sync";
1950
+ var init_login = __esm(() => {
1951
+ init_env();
1952
+ init_open_external();
1953
+ init_output();
1954
+ init_user_token();
1955
+ });
1956
+
1957
+ // src/commands/me.ts
1958
+ var exports_me = {};
1959
+ __export(exports_me, {
1960
+ meCommand: () => meCommand
1961
+ });
1962
+ async function meCommand() {
1963
+ const [me, progress] = await Promise.all([
1964
+ userApiGet("/api/me"),
1965
+ userApiGet("/api/me/galaxy-progress")
1966
+ ]);
1967
+ if (!me.user) {
1968
+ throw new Error("Your sign-in expired. Run `fluncle login` to link this device again.");
1969
+ }
1970
+ return {
1971
+ collectedCount: progress.collectedLogIds.length,
1972
+ deaths: progress.deaths,
1973
+ joinedAt: me.user.createdAt,
1974
+ name: me.user.displayUsername ?? me.user.username ?? "cosmonaut",
1975
+ userId: me.user.id,
1976
+ wins: progress.wins
1977
+ };
1978
+ }
1979
+ var init_me = __esm(() => {
1980
+ init_api();
1981
+ });
1982
+
1759
1983
  // src/brand.ts
1760
1984
  var fluncleAsciiLogo2 = `███████╗██╗ ██╗ ██╗███╗ ██╗ ██████╗██╗ ███████╗
1761
1985
  ██╔════╝██║ ██║ ██║████╗ ██║██╔════╝██║ ██╔════╝
@@ -1914,7 +2138,7 @@ function parseVersion2(version) {
1914
2138
  var currentVersion2, latestReleaseUrl = "https://api.github.com/repos/mauricekleine/fluncle/releases/latest";
1915
2139
  var init_version2 = __esm(() => {
1916
2140
  init_output();
1917
- currentVersion2 = "0.64.0".trim() ? "0.64.0".trim() : "0.1.0";
2141
+ currentVersion2 = "0.66.0".trim() ? "0.66.0".trim() : "0.1.0";
1918
2142
  });
1919
2143
 
1920
2144
  // ../../packages/registry/src/index.ts
@@ -2493,6 +2717,17 @@ var init_src = __esm(() => {
2493
2717
  probeConfig: { cadenceMs: 30 * MINUTE_MS, cronName: "fluncle-backfill", kind: "cron" },
2494
2718
  weights: { status: "hidden" }
2495
2719
  },
2720
+ {
2721
+ command: "fluncle admin tracks social --capture",
2722
+ exposedContent: [
2723
+ "capture the YouTube/TikTok post URLs Postiz withholds on create → write back (--no-agent, Worker HTTP)"
2724
+ ],
2725
+ kind: "cron",
2726
+ name: "cron.social-capture",
2727
+ operatorNotes: "every 10m. Pure HTTP trigger, zero LLM tokens. Agent tier (fills the public URL only — publishes nothing). The box's baked CLI predates the `--capture` verb, so the cron curls POST /api/admin/social/posts/capture directly; the Worker polls Postiz and writes back. Source: docs/agents/hermes/scripts/social-capture-sweep.sh. Probed on /status as cron.social-capture.",
2728
+ probeConfig: { cadenceMs: 10 * MINUTE_MS, cronName: "fluncle-social-capture", kind: "cron" },
2729
+ weights: { status: "hidden" }
2730
+ },
2496
2731
  {
2497
2732
  command: "fluncle admin tracks queue",
2498
2733
  exposedContent: [
@@ -3269,7 +3504,7 @@ __export(exports_newsletter, {
3269
3504
  newsletterListCommand: () => newsletterListCommand,
3270
3505
  newsletterDraftCommand: () => newsletterDraftCommand
3271
3506
  });
3272
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
3507
+ import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
3273
3508
  function buildBody2(options, { requireContent }) {
3274
3509
  const body = {};
3275
3510
  if (options.contentFile !== undefined) {
@@ -3292,7 +3527,7 @@ function readContentFile(filePath) {
3292
3527
  if (!existsSync2(filePath)) {
3293
3528
  throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
3294
3529
  }
3295
- const text = readFileSync2(filePath, "utf-8");
3530
+ const text = readFileSync3(filePath, "utf-8");
3296
3531
  try {
3297
3532
  return JSON.parse(text);
3298
3533
  } catch (error) {
@@ -3926,7 +4161,7 @@ var COORD_FALLBACK2 = "—";
3926
4161
  var init_format2 = () => {};
3927
4162
 
3928
4163
  // src/cli.ts
3929
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
4164
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
3930
4165
  import path2 from "path";
3931
4166
 
3932
4167
  // ../../node_modules/commander/lib/error.js
@@ -6046,6 +6281,7 @@ ${fluncleAsciiLogo}
6046
6281
  `).addHelpText("after", rootHelpSections);
6047
6282
  addListenCommands(program2);
6048
6283
  addShareCommands(program2);
6284
+ addAccountCommands(program2);
6049
6285
  addMetaCommands(program2);
6050
6286
  addTrackCommands(program2);
6051
6287
  addAdminCommands(program2);
@@ -6109,6 +6345,20 @@ function addShareCommands(program2) {
6109
6345
  await submitCommand2(input.join(" ") || undefined);
6110
6346
  });
6111
6347
  }
6348
+ function addAccountCommands(program2) {
6349
+ program2.command("login").description("Link this device to your Fluncle account (sync your Galaxy)").action(async () => {
6350
+ const { loginCommand: loginCommand2 } = await Promise.resolve().then(() => (init_login(), exports_login));
6351
+ await loginCommand2();
6352
+ });
6353
+ program2.command("logout").description("Unlink this device from your account").action(async () => {
6354
+ const { logoutCommand: logoutCommand2 } = await Promise.resolve().then(() => (init_login(), exports_login));
6355
+ await logoutCommand2();
6356
+ });
6357
+ program2.command("me").description("Your account and Galaxy progress (sign in with `fluncle login`)").option("--json", "Print JSON", false).action(async (options) => {
6358
+ const { meCommand: meCommand2 } = await Promise.resolve().then(() => (init_me(), exports_me));
6359
+ await runMe(options, meCommand2);
6360
+ });
6361
+ }
6112
6362
  function addMetaCommands(program2) {
6113
6363
  program2.command("about").description("Fluncle, and where to find him").action(async () => {
6114
6364
  const { aboutCommand: aboutCommand2 } = await Promise.resolve().then(() => (init_about(), exports_about));
@@ -6378,7 +6628,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
6378
6628
  console.log(` mime: ${result.mime}`);
6379
6629
  }
6380
6630
  async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
6381
- const script = options.scriptFile ? readFileSync3(options.scriptFile, "utf8") : options.script;
6631
+ const script = options.scriptFile ? readFileSync4(options.scriptFile, "utf8") : options.script;
6382
6632
  if (!idOrLogId || !script || !script.trim()) {
6383
6633
  throw new Error("Usage: fluncle admin tracks observe <track_id|log_id> (--script <text> | --script-file <file>) [--voice-id <id>] [--duration-ms <ms>] [--context-note <text>] [--json]");
6384
6634
  }
@@ -6434,7 +6684,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
6434
6684
  }
6435
6685
  }
6436
6686
  async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
6437
- const note = options.scriptFile ? readFileSync3(options.scriptFile, "utf8") : options.script;
6687
+ const note = options.scriptFile ? readFileSync4(options.scriptFile, "utf8") : options.script;
6438
6688
  if (!idOrLogId || !note || !note.trim()) {
6439
6689
  throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--json]");
6440
6690
  }
@@ -6477,6 +6727,7 @@ async function runBackfillLastfm(options, backfillLastfmCommand2) {
6477
6727
  const skipped = [];
6478
6728
  let cursor;
6479
6729
  let dryRun = options.dryRun;
6730
+ let throttled = false;
6480
6731
  while (loved.length + failed.length < limit) {
6481
6732
  const remaining = limit - (loved.length + failed.length);
6482
6733
  const result = await backfillLastfmCommand2(remaining, options.dryRun, cursor);
@@ -6488,6 +6739,10 @@ async function runBackfillLastfm(options, backfillLastfmCommand2) {
6488
6739
  const verb2 = result.dryRun ? "Would love" : "Loved";
6489
6740
  console.log(` \u2026${verb2.toLowerCase()} ${result.lovedCount}; ${result.failedCount} failed; ${result.skippedCount} skipped`);
6490
6741
  }
6742
+ if (result.rateLimited) {
6743
+ throttled = true;
6744
+ break;
6745
+ }
6491
6746
  if (result.nextCursor === null) {
6492
6747
  break;
6493
6748
  }
@@ -6501,6 +6756,7 @@ async function runBackfillLastfm(options, backfillLastfmCommand2) {
6501
6756
  loved,
6502
6757
  lovedCount: loved.length,
6503
6758
  ok: true,
6759
+ rateLimited: throttled,
6504
6760
  skipped,
6505
6761
  skippedCount: skipped.length
6506
6762
  });
@@ -6522,6 +6778,7 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
6522
6778
  const skipped = [];
6523
6779
  let cursor;
6524
6780
  let dryRun = options.dryRun;
6781
+ let throttled = false;
6525
6782
  while (resolved.length + unresolved.length < limit) {
6526
6783
  const remaining = limit - (resolved.length + unresolved.length);
6527
6784
  const result = await backfillDiscogsCommand2(remaining, options.dryRun, cursor);
@@ -6533,6 +6790,10 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
6533
6790
  const verb2 = result.dryRun ? "would resolve" : "resolved";
6534
6791
  console.log(` \u2026${verb2} ${result.resolvedCount}; ${result.unresolvedCount} unresolved; ${result.skippedCount} skipped`);
6535
6792
  }
6793
+ if (result.rateLimited) {
6794
+ throttled = true;
6795
+ break;
6796
+ }
6536
6797
  if (result.nextCursor === null) {
6537
6798
  break;
6538
6799
  }
@@ -6542,6 +6803,7 @@ async function runBackfillDiscogs(options, backfillDiscogsCommand2) {
6542
6803
  printJson({
6543
6804
  dryRun,
6544
6805
  ok: true,
6806
+ rateLimited: throttled,
6545
6807
  resolved,
6546
6808
  resolvedCount: resolved.length,
6547
6809
  skipped,
@@ -7177,6 +7439,20 @@ async function runStatus(options, statusCommand2) {
7177
7439
  console.log(statusLines2(snapshot).join(`
7178
7440
  `));
7179
7441
  }
7442
+ async function runMe(options, meCommand2) {
7443
+ const me = await meCommand2();
7444
+ if (options.json) {
7445
+ printJson({ ok: true, ...me });
7446
+ return;
7447
+ }
7448
+ console.log(me.name);
7449
+ console.log([
7450
+ `${me.collectedCount} ${me.collectedCount === 1 ? "finding" : "findings"} collected`,
7451
+ `${me.wins} ${me.wins === 1 ? "win" : "wins"}`,
7452
+ `${me.deaths} ${me.deaths === 1 ? "death" : "deaths"}`
7453
+ ].join(" \xB7 "));
7454
+ console.log(`Aboard since ${new Date(me.joinedAt).toLocaleDateString()}`);
7455
+ }
7180
7456
  function rejectUnexpectedPositionals(positionals) {
7181
7457
  if (positionals.length === 0) {
7182
7458
  return;
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.64.0"
34
+ "version": "0.66.0"
35
35
  }