fluncle 0.65.0 → 0.67.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 +314 -41
- 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.
|
|
426
|
+
currentVersion = "0.67.0".trim() ? "0.67.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 =
|
|
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,10 +2138,13 @@ 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.
|
|
2141
|
+
currentVersion2 = "0.67.0".trim() ? "0.67.0".trim() : "0.1.0";
|
|
1918
2142
|
});
|
|
1919
2143
|
|
|
1920
2144
|
// ../../packages/registry/src/index.ts
|
|
2145
|
+
function liveSurfaces() {
|
|
2146
|
+
return SURFACES.filter((surface) => surface.pending !== true);
|
|
2147
|
+
}
|
|
1921
2148
|
var SITE = "https://www.fluncle.com", PROBE_CADENCE_MS, PROBE_TIMEOUT_MS, MINUTE_MS, SURFACES;
|
|
1922
2149
|
var init_src = __esm(() => {
|
|
1923
2150
|
PROBE_CADENCE_MS = 10 * 60 * 1000;
|
|
@@ -2440,6 +2667,18 @@ var init_src = __esm(() => {
|
|
|
2440
2667
|
operatorNotes: "Authenticated admin/agent tier. The enrichment crons drive a subset of these.",
|
|
2441
2668
|
weights: { cli: "hidden" }
|
|
2442
2669
|
},
|
|
2670
|
+
{
|
|
2671
|
+
exposedContent: [
|
|
2672
|
+
"Fluncle Lens — the browser extension that finds fluncle:// coordinates on any page and links each to its /log/<coord> finding (with a hover card from the public API)"
|
|
2673
|
+
],
|
|
2674
|
+
kind: "extension",
|
|
2675
|
+
name: "extension.lens",
|
|
2676
|
+
operatorNotes: "Fluncle Lens (apps/extension), MV3. PENDING Chrome Web Store review — DARK until approval, then flip `pending` + set the assigned listing URL. The placeholder `url` is the store search for the listing; probeConfig is the post-approval /status check.",
|
|
2677
|
+
pending: true,
|
|
2678
|
+
probeConfig: { cadenceMs: PROBE_CADENCE_MS, kind: "http", timeoutMs: PROBE_TIMEOUT_MS },
|
|
2679
|
+
url: "https://chromewebstore.google.com/search/Fluncle%20Lens",
|
|
2680
|
+
weights: { web: "secondary" }
|
|
2681
|
+
},
|
|
2443
2682
|
{
|
|
2444
2683
|
command: "fluncle admin tracks enrich --queue",
|
|
2445
2684
|
exposedContent: [
|
|
@@ -2596,7 +2835,7 @@ var init_status = __esm(() => {
|
|
|
2596
2835
|
init_api();
|
|
2597
2836
|
serviceLabels = (() => {
|
|
2598
2837
|
const labels = new Map;
|
|
2599
|
-
for (const surface of
|
|
2838
|
+
for (const surface of liveSurfaces()) {
|
|
2600
2839
|
const match = surface.operatorNotes?.match(/service `([a-z0-9-]+)`/);
|
|
2601
2840
|
const id = match?.[1];
|
|
2602
2841
|
const label = surface.exposedContent[0];
|
|
@@ -2820,7 +3059,7 @@ __export(exports_admin_tracks, {
|
|
|
2820
3059
|
backfillDiscogsCommand: () => backfillDiscogsCommand
|
|
2821
3060
|
});
|
|
2822
3061
|
async function fetchAdminTracks(options) {
|
|
2823
|
-
const { hasContext, hasNote, hasObservation, hasVideo, max, order, status } = options;
|
|
3062
|
+
const { hasContext, hasNote, hasObservation, hasVideo, max, order, retryEmptyContext, status } = options;
|
|
2824
3063
|
const results = [];
|
|
2825
3064
|
let cursor;
|
|
2826
3065
|
do {
|
|
@@ -2831,6 +3070,9 @@ async function fetchAdminTracks(options) {
|
|
|
2831
3070
|
if (hasContext !== undefined) {
|
|
2832
3071
|
params.set("hasContext", String(hasContext));
|
|
2833
3072
|
}
|
|
3073
|
+
if (retryEmptyContext) {
|
|
3074
|
+
params.set("retryEmptyContext", "true");
|
|
3075
|
+
}
|
|
2834
3076
|
if (hasNote !== undefined) {
|
|
2835
3077
|
params.set("hasNote", String(hasNote));
|
|
2836
3078
|
}
|
|
@@ -2870,8 +3112,8 @@ async function queueCommand(limit, filters = {}) {
|
|
|
2870
3112
|
async function enrichQueueCommand(limit) {
|
|
2871
3113
|
return fetchAdminTracks({ max: limit, order: "asc", status: "queue" });
|
|
2872
3114
|
}
|
|
2873
|
-
async function contextQueueCommand(limit) {
|
|
2874
|
-
return fetchAdminTracks({ hasContext: false, max: limit, order: "asc" });
|
|
3115
|
+
async function contextQueueCommand(limit, retryEmptyContext = false) {
|
|
3116
|
+
return fetchAdminTracks({ hasContext: false, max: limit, order: "asc", retryEmptyContext });
|
|
2875
3117
|
}
|
|
2876
3118
|
async function observeQueueCommand(limit) {
|
|
2877
3119
|
return fetchAdminTracks({
|
|
@@ -3280,7 +3522,7 @@ __export(exports_newsletter, {
|
|
|
3280
3522
|
newsletterListCommand: () => newsletterListCommand,
|
|
3281
3523
|
newsletterDraftCommand: () => newsletterDraftCommand
|
|
3282
3524
|
});
|
|
3283
|
-
import { existsSync as existsSync2, readFileSync as
|
|
3525
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
3284
3526
|
function buildBody2(options, { requireContent }) {
|
|
3285
3527
|
const body = {};
|
|
3286
3528
|
if (options.contentFile !== undefined) {
|
|
@@ -3303,7 +3545,7 @@ function readContentFile(filePath) {
|
|
|
3303
3545
|
if (!existsSync2(filePath)) {
|
|
3304
3546
|
throw new CliError2("file_not_found", `Content file not found: ${filePath}`);
|
|
3305
3547
|
}
|
|
3306
|
-
const text =
|
|
3548
|
+
const text = readFileSync3(filePath, "utf-8");
|
|
3307
3549
|
try {
|
|
3308
3550
|
return JSON.parse(text);
|
|
3309
3551
|
} catch (error) {
|
|
@@ -3937,7 +4179,7 @@ var COORD_FALLBACK2 = "—";
|
|
|
3937
4179
|
var init_format2 = () => {};
|
|
3938
4180
|
|
|
3939
4181
|
// src/cli.ts
|
|
3940
|
-
import { existsSync as existsSync3, readFileSync as
|
|
4182
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
|
|
3941
4183
|
import path2 from "path";
|
|
3942
4184
|
|
|
3943
4185
|
// ../../node_modules/commander/lib/error.js
|
|
@@ -6057,6 +6299,7 @@ ${fluncleAsciiLogo}
|
|
|
6057
6299
|
`).addHelpText("after", rootHelpSections);
|
|
6058
6300
|
addListenCommands(program2);
|
|
6059
6301
|
addShareCommands(program2);
|
|
6302
|
+
addAccountCommands(program2);
|
|
6060
6303
|
addMetaCommands(program2);
|
|
6061
6304
|
addTrackCommands(program2);
|
|
6062
6305
|
addAdminCommands(program2);
|
|
@@ -6120,6 +6363,20 @@ function addShareCommands(program2) {
|
|
|
6120
6363
|
await submitCommand2(input.join(" ") || undefined);
|
|
6121
6364
|
});
|
|
6122
6365
|
}
|
|
6366
|
+
function addAccountCommands(program2) {
|
|
6367
|
+
program2.command("login").description("Link this device to your Fluncle account (sync your Galaxy)").action(async () => {
|
|
6368
|
+
const { loginCommand: loginCommand2 } = await Promise.resolve().then(() => (init_login(), exports_login));
|
|
6369
|
+
await loginCommand2();
|
|
6370
|
+
});
|
|
6371
|
+
program2.command("logout").description("Unlink this device from your account").action(async () => {
|
|
6372
|
+
const { logoutCommand: logoutCommand2 } = await Promise.resolve().then(() => (init_login(), exports_login));
|
|
6373
|
+
await logoutCommand2();
|
|
6374
|
+
});
|
|
6375
|
+
program2.command("me").description("Your account and Galaxy progress (sign in with `fluncle login`)").option("--json", "Print JSON", false).action(async (options) => {
|
|
6376
|
+
const { meCommand: meCommand2 } = await Promise.resolve().then(() => (init_me(), exports_me));
|
|
6377
|
+
await runMe(options, meCommand2);
|
|
6378
|
+
});
|
|
6379
|
+
}
|
|
6123
6380
|
function addMetaCommands(program2) {
|
|
6124
6381
|
program2.command("about").description("Fluncle, and where to find him").action(async () => {
|
|
6125
6382
|
const { aboutCommand: aboutCommand2 } = await Promise.resolve().then(() => (init_about(), exports_about));
|
|
@@ -6232,7 +6489,7 @@ function addAdminCommands(program2) {
|
|
|
6232
6489
|
const { trackObserveCommand: trackObserveCommand2 } = await Promise.resolve().then(() => (init_track(), exports_track));
|
|
6233
6490
|
await runTrackObserve(idOrLogId, options, trackObserveCommand2);
|
|
6234
6491
|
});
|
|
6235
|
-
adminTrack.command("context").description("Gather the field notes for a finding (facts only; observe speaks from them)").argument("[idOrLogId]").option("--queue", "Show the context worklist (findings missing field notes), oldest first", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--query <text>", "Override the fact-search query (else the Worker builds one)").option("--refresh", "Re-run the fetch even if a note exists (backfill/sharpen)", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
|
|
6492
|
+
adminTrack.command("context").description("Gather the field notes for a finding (facts only; observe speaks from them)").argument("[idOrLogId]").option("--queue", "Show the context worklist (findings missing field notes), oldest first", false).option("--retry-empty", "With --queue: also re-pick finds confirmed empty last pass (widen the net)", false).option("--limit <limit>", "Number of findings to show with --queue", "10").option("--query <text>", "Override the fact-search query (else the Worker builds one)").option("--refresh", "Re-run the fetch even if a note exists (backfill/sharpen)", false).option("--json", "Print JSON", false).allowExcessArguments().action(async (idOrLogId, options) => {
|
|
6236
6493
|
if (options.queue) {
|
|
6237
6494
|
const { contextQueueCommand: contextQueueCommand2 } = await Promise.resolve().then(() => (init_admin_tracks(), exports_admin_tracks));
|
|
6238
6495
|
await runAdminContextQueue(options, contextQueueCommand2);
|
|
@@ -6389,7 +6646,7 @@ async function runTrackPreviewArchive(idOrLogId, options, previewArchiveUploadCo
|
|
|
6389
6646
|
console.log(` mime: ${result.mime}`);
|
|
6390
6647
|
}
|
|
6391
6648
|
async function runTrackObserve(idOrLogId, options, trackObserveCommand2) {
|
|
6392
|
-
const script = options.scriptFile ?
|
|
6649
|
+
const script = options.scriptFile ? readFileSync4(options.scriptFile, "utf8") : options.script;
|
|
6393
6650
|
if (!idOrLogId || !script || !script.trim()) {
|
|
6394
6651
|
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]");
|
|
6395
6652
|
}
|
|
@@ -6445,7 +6702,7 @@ async function runTrackContext(idOrLogId, options, trackContextCommand2) {
|
|
|
6445
6702
|
}
|
|
6446
6703
|
}
|
|
6447
6704
|
async function runTrackNote(idOrLogId, options, trackNoteCommand2) {
|
|
6448
|
-
const note = options.scriptFile ?
|
|
6705
|
+
const note = options.scriptFile ? readFileSync4(options.scriptFile, "utf8") : options.script;
|
|
6449
6706
|
if (!idOrLogId || !note || !note.trim()) {
|
|
6450
6707
|
throw new Error("Usage: fluncle admin tracks note <track_id|log_id> (--script <text> | --script-file <file>) [--json]");
|
|
6451
6708
|
}
|
|
@@ -7042,7 +7299,8 @@ async function runAdminQueue(options, queueCommand2) {
|
|
|
7042
7299
|
}
|
|
7043
7300
|
async function runAdminContextQueue(options, contextQueueCommand2) {
|
|
7044
7301
|
const limit = parseListLimit(options.limit);
|
|
7045
|
-
const
|
|
7302
|
+
const retryEmpty = options.retryEmpty === true;
|
|
7303
|
+
const tracks = await contextQueueCommand2(limit, retryEmpty);
|
|
7046
7304
|
if (options.json) {
|
|
7047
7305
|
printJson({
|
|
7048
7306
|
ok: true,
|
|
@@ -7056,7 +7314,8 @@ async function runAdminContextQueue(options, contextQueueCommand2) {
|
|
|
7056
7314
|
}
|
|
7057
7315
|
const { trackRows: trackRows2 } = await Promise.resolve().then(() => (init_format2(), exports_format));
|
|
7058
7316
|
const noun = tracks.length === 1 ? "finding" : "findings";
|
|
7059
|
-
|
|
7317
|
+
const scope = retryEmpty ? " (incl. empty retries)" : "";
|
|
7318
|
+
console.log(`${tracks.length} ${noun} missing field notes${scope}, oldest first:`);
|
|
7060
7319
|
console.log(trackRows2(tracks).join(`
|
|
7061
7320
|
`));
|
|
7062
7321
|
}
|
|
@@ -7200,6 +7459,20 @@ async function runStatus(options, statusCommand2) {
|
|
|
7200
7459
|
console.log(statusLines2(snapshot).join(`
|
|
7201
7460
|
`));
|
|
7202
7461
|
}
|
|
7462
|
+
async function runMe(options, meCommand2) {
|
|
7463
|
+
const me = await meCommand2();
|
|
7464
|
+
if (options.json) {
|
|
7465
|
+
printJson({ ok: true, ...me });
|
|
7466
|
+
return;
|
|
7467
|
+
}
|
|
7468
|
+
console.log(me.name);
|
|
7469
|
+
console.log([
|
|
7470
|
+
`${me.collectedCount} ${me.collectedCount === 1 ? "finding" : "findings"} collected`,
|
|
7471
|
+
`${me.wins} ${me.wins === 1 ? "win" : "wins"}`,
|
|
7472
|
+
`${me.deaths} ${me.deaths === 1 ? "death" : "deaths"}`
|
|
7473
|
+
].join(" \xB7 "));
|
|
7474
|
+
console.log(`Aboard since ${new Date(me.joinedAt).toLocaleDateString()}`);
|
|
7475
|
+
}
|
|
7203
7476
|
function rejectUnexpectedPositionals(positionals) {
|
|
7204
7477
|
if (positionals.length === 0) {
|
|
7205
7478
|
return;
|
package/package.json
CHANGED