@claudinho/cli 0.2.0 → 0.4.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/README.md +102 -3
- package/dist/index.js +991 -44
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -311,6 +311,25 @@ function formatKickoff(iso, opts = {}) {
|
|
|
311
311
|
timeZone: tz
|
|
312
312
|
}).format(new Date(iso));
|
|
313
313
|
}
|
|
314
|
+
function formatDate(iso, opts = {}) {
|
|
315
|
+
const tz = resolveTz(opts.tz);
|
|
316
|
+
const locale = safeLocale(opts.locale);
|
|
317
|
+
return new Intl.DateTimeFormat(locale, {
|
|
318
|
+
month: "short",
|
|
319
|
+
day: "numeric",
|
|
320
|
+
timeZone: tz
|
|
321
|
+
}).format(new Date(iso));
|
|
322
|
+
}
|
|
323
|
+
function formatTime(iso, opts = {}) {
|
|
324
|
+
const tz = resolveTz(opts.tz);
|
|
325
|
+
const locale = safeLocale(opts.locale);
|
|
326
|
+
return new Intl.DateTimeFormat(locale, {
|
|
327
|
+
hour: "2-digit",
|
|
328
|
+
minute: "2-digit",
|
|
329
|
+
hour12: false,
|
|
330
|
+
timeZone: tz
|
|
331
|
+
}).format(new Date(iso));
|
|
332
|
+
}
|
|
314
333
|
function countdown(iso, from = /* @__PURE__ */ new Date()) {
|
|
315
334
|
const ms = new Date(iso).getTime() - from.getTime();
|
|
316
335
|
if (ms <= 0) return "now";
|
|
@@ -2830,12 +2849,16 @@ function mergeLive(base, live) {
|
|
|
2830
2849
|
for (const m of live) byId.set(m.id, m);
|
|
2831
2850
|
return [...byId.values()];
|
|
2832
2851
|
}
|
|
2852
|
+
function liveSourceLabel(source) {
|
|
2853
|
+
const known = { espn: "ESPN" };
|
|
2854
|
+
return known[source] ?? source.charAt(0).toUpperCase() + source.slice(1);
|
|
2855
|
+
}
|
|
2833
2856
|
async function getMatchesForDate(adapter, dateISO) {
|
|
2834
2857
|
const base = allFixtures();
|
|
2835
2858
|
const day = dateISO.slice(0, 10);
|
|
2836
2859
|
try {
|
|
2837
2860
|
const live = adapter.fetchWindow ? await adapter.fetchWindow(shiftUtcDate(day, -1), shiftUtcDate(day, 1)) : await adapter.fetchByDate(day);
|
|
2838
|
-
return { matches: mergeLive(base, live), degraded: false };
|
|
2861
|
+
return { matches: mergeLive(base, live), degraded: false, source: adapter.name };
|
|
2839
2862
|
} catch {
|
|
2840
2863
|
return { matches: base, degraded: true };
|
|
2841
2864
|
}
|
|
@@ -2846,11 +2869,506 @@ function shiftUtcDate(dateISO, days) {
|
|
|
2846
2869
|
}
|
|
2847
2870
|
async function getLiveMatches(adapter) {
|
|
2848
2871
|
try {
|
|
2849
|
-
return { matches: await adapter.fetchLive(), degraded: false };
|
|
2872
|
+
return { matches: await adapter.fetchLive(), degraded: false, source: adapter.name };
|
|
2850
2873
|
} catch {
|
|
2851
2874
|
return { matches: [], degraded: true };
|
|
2852
2875
|
}
|
|
2853
2876
|
}
|
|
2877
|
+
var DEFAULT_MAX_AGE_MS = 15 * 6e4;
|
|
2878
|
+
function normalizeOutcomes(outcomes) {
|
|
2879
|
+
const sum = outcomes.reduce(
|
|
2880
|
+
(s, o) => s + (Number.isFinite(o.probability) && o.probability > 0 ? o.probability : 0),
|
|
2881
|
+
0
|
|
2882
|
+
);
|
|
2883
|
+
if (sum <= 0) return outcomes.map((o) => ({ ...o, probability: 0 }));
|
|
2884
|
+
return outcomes.map((o) => ({
|
|
2885
|
+
...o,
|
|
2886
|
+
probability: Number.isFinite(o.probability) && o.probability > 0 ? o.probability / sum : 0
|
|
2887
|
+
}));
|
|
2888
|
+
}
|
|
2889
|
+
function favoriteStrength(probability) {
|
|
2890
|
+
if (probability >= 0.65) return "clear";
|
|
2891
|
+
if (probability >= 0.52) return "slight";
|
|
2892
|
+
return "close";
|
|
2893
|
+
}
|
|
2894
|
+
function deriveFavorite(outcomes) {
|
|
2895
|
+
let top;
|
|
2896
|
+
for (const o of outcomes) {
|
|
2897
|
+
if (o.kind === "other") continue;
|
|
2898
|
+
if (!top || o.probability > top.probability) top = o;
|
|
2899
|
+
}
|
|
2900
|
+
if (!top || top.probability <= 0 || top.kind === "other") return void 0;
|
|
2901
|
+
return {
|
|
2902
|
+
kind: top.kind,
|
|
2903
|
+
teamCode: top.teamCode,
|
|
2904
|
+
probability: top.probability,
|
|
2905
|
+
strength: favoriteStrength(top.probability)
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
function mapsCleanly(match, outcomes) {
|
|
2909
|
+
if (outcomes.some((o) => o.kind === "other")) return false;
|
|
2910
|
+
const home = outcomes.find((o) => o.kind === "home");
|
|
2911
|
+
const away = outcomes.find((o) => o.kind === "away");
|
|
2912
|
+
const draw = outcomes.find((o) => o.kind === "draw");
|
|
2913
|
+
if (!home || !away) return false;
|
|
2914
|
+
if (home.teamCode && home.teamCode.toUpperCase() !== match.home.code.toUpperCase()) {
|
|
2915
|
+
return false;
|
|
2916
|
+
}
|
|
2917
|
+
if (away.teamCode && away.teamCode.toUpperCase() !== match.away.code.toUpperCase()) {
|
|
2918
|
+
return false;
|
|
2919
|
+
}
|
|
2920
|
+
if (match.stage === "GROUP" && !draw) return false;
|
|
2921
|
+
return true;
|
|
2922
|
+
}
|
|
2923
|
+
function hasSaneDistribution(outcomes) {
|
|
2924
|
+
const priced = outcomes.filter((o) => Number.isFinite(o.probability) && o.probability > 0);
|
|
2925
|
+
if (priced.length < 2) return false;
|
|
2926
|
+
const sum = priced.reduce((s, o) => s + o.probability, 0);
|
|
2927
|
+
return sum > 0.97 && sum < 1.03;
|
|
2928
|
+
}
|
|
2929
|
+
function isStaleSignal(signal, options = {}) {
|
|
2930
|
+
const maxAge = options.maxAgeMs ?? DEFAULT_MAX_AGE_MS;
|
|
2931
|
+
const asOf = Date.parse(signal.asOf);
|
|
2932
|
+
if (!Number.isFinite(asOf)) return true;
|
|
2933
|
+
const now = (options.now ?? /* @__PURE__ */ new Date()).getTime();
|
|
2934
|
+
return now - asOf > maxAge;
|
|
2935
|
+
}
|
|
2936
|
+
function isReliableMarketSignal(signal, options = {}) {
|
|
2937
|
+
if (options.includeUnreliable) return true;
|
|
2938
|
+
if (signal.ambiguous) return false;
|
|
2939
|
+
if (!signal.favorite) return false;
|
|
2940
|
+
if (!hasSaneDistribution(signal.outcomes)) return false;
|
|
2941
|
+
if (signal.stale || isStaleSignal(signal, options)) return false;
|
|
2942
|
+
if (options.minLiquidity != null) {
|
|
2943
|
+
if (signal.liquidity == null || signal.liquidity < options.minLiquidity) return false;
|
|
2944
|
+
}
|
|
2945
|
+
return true;
|
|
2946
|
+
}
|
|
2947
|
+
function buildMarketSignal(input) {
|
|
2948
|
+
const outcomes = normalizeOutcomes(input.outcomes);
|
|
2949
|
+
const ambiguous = input.ambiguous === true || !mapsCleanly(input.match, outcomes);
|
|
2950
|
+
const favorite = ambiguous ? void 0 : deriveFavorite(outcomes);
|
|
2951
|
+
const signal = {
|
|
2952
|
+
matchId: input.match.id,
|
|
2953
|
+
source: input.source,
|
|
2954
|
+
sourceMarketId: input.sourceMarketId,
|
|
2955
|
+
asOf: input.asOf,
|
|
2956
|
+
fetchedAt: input.fetchedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2957
|
+
outcomes,
|
|
2958
|
+
favorite,
|
|
2959
|
+
liquidity: input.liquidity,
|
|
2960
|
+
volume24h: input.volume24h,
|
|
2961
|
+
stale: false,
|
|
2962
|
+
ambiguous
|
|
2963
|
+
};
|
|
2964
|
+
signal.stale = isStaleSignal(signal, { now: input.now, maxAgeMs: input.maxAgeMs });
|
|
2965
|
+
return signal;
|
|
2966
|
+
}
|
|
2967
|
+
function pct(p) {
|
|
2968
|
+
return Math.round(p * 100);
|
|
2969
|
+
}
|
|
2970
|
+
function marketSourceLabel(source) {
|
|
2971
|
+
if (source === "polymarket") return "Polymarket";
|
|
2972
|
+
if (source === "fake") return "demo data";
|
|
2973
|
+
return source.charAt(0).toUpperCase() + source.slice(1);
|
|
2974
|
+
}
|
|
2975
|
+
function outcomeLabel(o, match) {
|
|
2976
|
+
if (o.kind === "home") return match.home.name;
|
|
2977
|
+
if (o.kind === "away") return match.away.name;
|
|
2978
|
+
if (o.kind === "draw") return "Draw";
|
|
2979
|
+
return o.label;
|
|
2980
|
+
}
|
|
2981
|
+
function utcHhmm(iso) {
|
|
2982
|
+
const t = Date.parse(iso);
|
|
2983
|
+
if (!Number.isFinite(t)) return "";
|
|
2984
|
+
return `${new Date(t).toISOString().slice(11, 16)} UTC`;
|
|
2985
|
+
}
|
|
2986
|
+
function marketFavoriteText(signal, match) {
|
|
2987
|
+
const fav = signal.favorite;
|
|
2988
|
+
if (!fav || fav.strength === "close") return "Prediction markets see this match as close.";
|
|
2989
|
+
if (fav.kind === "draw") return "Prediction markets see a draw as the top outcome.";
|
|
2990
|
+
const name = fav.kind === "home" ? match.home.name : match.away.name;
|
|
2991
|
+
return fav.strength === "clear" ? `Prediction markets favor ${name}.` : `Prediction markets slightly favor ${name}.`;
|
|
2992
|
+
}
|
|
2993
|
+
function marketProbabilityText(signal, match) {
|
|
2994
|
+
const order = ["home", "draw", "away"];
|
|
2995
|
+
const parts = [];
|
|
2996
|
+
for (const kind of order) {
|
|
2997
|
+
const o = signal.outcomes.find((x) => x.kind === kind);
|
|
2998
|
+
if (o) parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
|
|
2999
|
+
}
|
|
3000
|
+
for (const o of signal.outcomes) {
|
|
3001
|
+
if (o.kind === "other") parts.push(`${outcomeLabel(o, match)} ${pct(o.probability)}%`);
|
|
3002
|
+
}
|
|
3003
|
+
return parts.join(" \xB7 ");
|
|
3004
|
+
}
|
|
3005
|
+
function marketAttributionText(signal) {
|
|
3006
|
+
const time = utcHhmm(signal.asOf);
|
|
3007
|
+
const src = `Source: ${marketSourceLabel(signal.source)}`;
|
|
3008
|
+
return time ? `${src} \xB7 updated ${time}` : src;
|
|
3009
|
+
}
|
|
3010
|
+
function marketLine(signal, match) {
|
|
3011
|
+
return `Market: ${marketProbabilityText(signal, match)} \xB7 ${marketSourceLabel(
|
|
3012
|
+
signal.source
|
|
3013
|
+
)} \xB7 informational only`;
|
|
3014
|
+
}
|
|
3015
|
+
function marketBlock(signal, match) {
|
|
3016
|
+
const lines = [];
|
|
3017
|
+
if (signal.stale) lines.push("Market signal is stale; the reading may be out of date.");
|
|
3018
|
+
lines.push(marketFavoriteText(signal, match));
|
|
3019
|
+
lines.push(marketProbabilityText(signal, match));
|
|
3020
|
+
lines.push(`${marketAttributionText(signal)} \xB7 informational only`);
|
|
3021
|
+
return lines;
|
|
3022
|
+
}
|
|
3023
|
+
var FakeMarketProvider = class {
|
|
3024
|
+
constructor(opts = {}) {
|
|
3025
|
+
this.opts = opts;
|
|
3026
|
+
}
|
|
3027
|
+
opts;
|
|
3028
|
+
name = "fake";
|
|
3029
|
+
async findSignal(match, options) {
|
|
3030
|
+
const preset = this.opts.signals?.[match.id];
|
|
3031
|
+
if (preset) return preset;
|
|
3032
|
+
if (this.opts.synthesize) return this.synthesize(match, options);
|
|
3033
|
+
return void 0;
|
|
3034
|
+
}
|
|
3035
|
+
async findSignals(matches, options) {
|
|
3036
|
+
const signals = /* @__PURE__ */ new Map();
|
|
3037
|
+
const checked = /* @__PURE__ */ new Set();
|
|
3038
|
+
for (const m of matches) {
|
|
3039
|
+
checked.add(m.id);
|
|
3040
|
+
const s = await this.findSignal(m, options);
|
|
3041
|
+
if (s) signals.set(m.id, s);
|
|
3042
|
+
}
|
|
3043
|
+
return { signals, checked };
|
|
3044
|
+
}
|
|
3045
|
+
synthesize(match, options) {
|
|
3046
|
+
const seed = hash(`${match.home.code}-${match.away.code}`);
|
|
3047
|
+
const home = 0.3 + seed % 33 / 100;
|
|
3048
|
+
const away = 0.18 + (seed >> 3) % 23 / 100;
|
|
3049
|
+
const draw = Math.max(0.05, 1 - home - away);
|
|
3050
|
+
const outcomes = [
|
|
3051
|
+
{ kind: "home", teamCode: match.home.code, label: match.home.name, probability: home },
|
|
3052
|
+
{ kind: "draw", label: "Draw", probability: draw },
|
|
3053
|
+
{ kind: "away", teamCode: match.away.code, label: match.away.name, probability: away }
|
|
3054
|
+
];
|
|
3055
|
+
const now = this.opts.now ?? options?.now ?? /* @__PURE__ */ new Date();
|
|
3056
|
+
const asOf = new Date(now.getTime() - 6e4).toISOString();
|
|
3057
|
+
return buildMarketSignal({
|
|
3058
|
+
match,
|
|
3059
|
+
source: "fake",
|
|
3060
|
+
sourceMarketId: `fake-${match.id}`,
|
|
3061
|
+
asOf,
|
|
3062
|
+
fetchedAt: now.toISOString(),
|
|
3063
|
+
outcomes,
|
|
3064
|
+
liquidity: 5e4,
|
|
3065
|
+
now,
|
|
3066
|
+
maxAgeMs: options?.maxAgeMs
|
|
3067
|
+
});
|
|
3068
|
+
}
|
|
3069
|
+
};
|
|
3070
|
+
function hash(s) {
|
|
3071
|
+
let h = 0;
|
|
3072
|
+
for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) >>> 0;
|
|
3073
|
+
return h % 1e5;
|
|
3074
|
+
}
|
|
3075
|
+
var mapping_2026_default = {
|
|
3076
|
+
version: 1,
|
|
3077
|
+
note: "Optional matchId -> Polymarket EVENT-slug OVERRIDES. By default the slug is DERIVED from each fixture (fifwc-{home}-{away}-{UTC-date}, e.g. fifwc-mex-rsa-2026-06-11), so most matches need NO entry here. Add an entry only for a fixture whose real Polymarket slug differs (e.g. a team abbreviation that isn't the FIFA code, or a tz-shifted date). The event payload carries the three moneyline binary markets; each outcome's probability is its 'Yes' price; validation fails closed. Entry: { eventSlug, eventId? }.",
|
|
3078
|
+
markets: {}
|
|
3079
|
+
};
|
|
3080
|
+
var DEFAULT_BASE2 = "https://gamma-api.polymarket.com";
|
|
3081
|
+
var ALLOWED_HOSTS = /* @__PURE__ */ new Set(["gamma-api.polymarket.com"]);
|
|
3082
|
+
var USER_AGENT2 = "claudinho/0.0 (+https://github.com/arturogarrido/claudinho)";
|
|
3083
|
+
var DEFAULT_TIMEOUT_MS = 8e3;
|
|
3084
|
+
var WC_SERIES_SLUG = "soccer-fifwc";
|
|
3085
|
+
var WC_SPORT = "fifwc";
|
|
3086
|
+
var KICKOFF_TOLERANCE_MS = 6 * 60 * 6e4;
|
|
3087
|
+
var NON_REGULAR_TIME = /extra time|penalt|to advance|to qualif|win the (group|tournament|cup|title)/i;
|
|
3088
|
+
var BUNDLED_MAPPING = mapping_2026_default.markets;
|
|
3089
|
+
var PolymarketProvider = class {
|
|
3090
|
+
constructor(opts = {}) {
|
|
3091
|
+
this.opts = opts;
|
|
3092
|
+
}
|
|
3093
|
+
opts;
|
|
3094
|
+
name = "polymarket";
|
|
3095
|
+
async findSignal(match, options) {
|
|
3096
|
+
return (await this.resolveOne(match, options)).signal;
|
|
3097
|
+
}
|
|
3098
|
+
async findSignals(matches, options) {
|
|
3099
|
+
const signals = /* @__PURE__ */ new Map();
|
|
3100
|
+
const checked = /* @__PURE__ */ new Set();
|
|
3101
|
+
const deadline = options?.deadlineMs != null ? Date.now() + options.deadlineMs : Number.POSITIVE_INFINITY;
|
|
3102
|
+
for (const m of matches) {
|
|
3103
|
+
if (Date.now() >= deadline) break;
|
|
3104
|
+
const r = await this.resolveOne(m, options);
|
|
3105
|
+
if (r.checked) checked.add(m.id);
|
|
3106
|
+
if (r.signal) signals.set(m.id, r.signal);
|
|
3107
|
+
}
|
|
3108
|
+
return { signals, checked };
|
|
3109
|
+
}
|
|
3110
|
+
/**
|
|
3111
|
+
* Resolve one match. `checked` distinguishes a DEFINITIVE result (reached the
|
|
3112
|
+
* source and found no usable market, or the fixture is unmappable) from a
|
|
3113
|
+
* provider/network error — so transient failures are retried, not
|
|
3114
|
+
* negative-cached.
|
|
3115
|
+
*/
|
|
3116
|
+
async resolveOne(match, options) {
|
|
3117
|
+
const entry = (this.opts.mapping ?? BUNDLED_MAPPING)[match.id];
|
|
3118
|
+
const eventSlug = entry?.eventSlug ?? deriveEventSlug(match);
|
|
3119
|
+
if (!eventSlug) return { checked: true };
|
|
3120
|
+
try {
|
|
3121
|
+
const event = await this.fetchEvent(eventSlug, options?.timeoutMs);
|
|
3122
|
+
const signal = event ? this.toSignal(match, eventSlug, event, options) : void 0;
|
|
3123
|
+
return { signal, checked: true };
|
|
3124
|
+
} catch {
|
|
3125
|
+
return { checked: false };
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
async fetchEvent(slug, timeoutMs) {
|
|
3129
|
+
const base = this.opts.baseUrl ?? DEFAULT_BASE2;
|
|
3130
|
+
assertAllowedHost(base);
|
|
3131
|
+
const url = `${base}/events?slug=${encodeURIComponent(slug)}`;
|
|
3132
|
+
const doFetch = this.opts.fetchImpl ?? fetch;
|
|
3133
|
+
const res = await doFetch(url, {
|
|
3134
|
+
signal: AbortSignal.timeout(timeoutMs ?? this.opts.timeoutMs ?? DEFAULT_TIMEOUT_MS),
|
|
3135
|
+
headers: { Accept: "application/json", "User-Agent": USER_AGENT2 }
|
|
3136
|
+
});
|
|
3137
|
+
if (res.status === 404) return void 0;
|
|
3138
|
+
if (!res.ok) {
|
|
3139
|
+
throw new Error(`Polymarket request failed: ${res.status} ${res.statusText}`);
|
|
3140
|
+
}
|
|
3141
|
+
const data = await res.json();
|
|
3142
|
+
const event = Array.isArray(data) ? data[0] : data;
|
|
3143
|
+
return event && typeof event === "object" ? event : void 0;
|
|
3144
|
+
}
|
|
3145
|
+
toSignal(match, eventSlug, event, options) {
|
|
3146
|
+
if (event.active === false || event.closed === true) return void 0;
|
|
3147
|
+
if (event.seriesSlug != null && event.seriesSlug !== WC_SERIES_SLUG && event.sport?.sport !== WC_SPORT) {
|
|
3148
|
+
return void 0;
|
|
3149
|
+
}
|
|
3150
|
+
if (event.slug != null && event.slug !== eventSlug) return void 0;
|
|
3151
|
+
const start = event.startTime ? Date.parse(event.startTime) : Number.NaN;
|
|
3152
|
+
const kick = Date.parse(match.kickoff);
|
|
3153
|
+
if (Number.isFinite(start) && Number.isFinite(kick) && Math.abs(start - kick) > KICKOFF_TOLERANCE_MS) {
|
|
3154
|
+
return void 0;
|
|
3155
|
+
}
|
|
3156
|
+
const moneyline = (event.markets ?? []).filter(
|
|
3157
|
+
(m) => (m.sportsMarketType ?? "moneyline") === "moneyline"
|
|
3158
|
+
);
|
|
3159
|
+
const homeMarket = pickMarket(moneyline, match.home.code, match.home.name);
|
|
3160
|
+
const awayMarket = pickMarket(moneyline, match.away.code, match.away.name);
|
|
3161
|
+
const drawMarket = pickDraw(moneyline);
|
|
3162
|
+
if (!homeMarket || !awayMarket) return void 0;
|
|
3163
|
+
const legIds = [homeMarket, awayMarket, drawMarket].filter((m) => m != null).map((m) => m.id ?? m.slug ?? "");
|
|
3164
|
+
if (new Set(legIds).size !== legIds.length) return void 0;
|
|
3165
|
+
const legs = [
|
|
3166
|
+
["home", homeMarket, match.home.code, match.home.name],
|
|
3167
|
+
["draw", drawMarket, void 0, "Draw"],
|
|
3168
|
+
["away", awayMarket, match.away.code, match.away.name]
|
|
3169
|
+
];
|
|
3170
|
+
const outcomes = [];
|
|
3171
|
+
let asOf = event.updatedAt;
|
|
3172
|
+
let liquidity;
|
|
3173
|
+
for (const [kind, market, teamCode, label] of legs) {
|
|
3174
|
+
if (!market) continue;
|
|
3175
|
+
if (market.closed === true || market.active === false) return void 0;
|
|
3176
|
+
if (market.description && NON_REGULAR_TIME.test(market.description)) return void 0;
|
|
3177
|
+
const yes = yesPrice(market);
|
|
3178
|
+
if (yes == null) return void 0;
|
|
3179
|
+
outcomes.push({ kind, teamCode, label, probability: yes });
|
|
3180
|
+
if (market.updatedAt && (!asOf || market.updatedAt < asOf)) asOf = market.updatedAt;
|
|
3181
|
+
const liq = numberish(market.liquidityNum ?? market.liquidity);
|
|
3182
|
+
if (liq != null) liquidity = liquidity == null ? liq : Math.min(liquidity, liq);
|
|
3183
|
+
}
|
|
3184
|
+
const rawSum = outcomes.reduce((s, o) => s + o.probability, 0);
|
|
3185
|
+
if (rawSum < 0.9 || rawSum > 1.15) return void 0;
|
|
3186
|
+
const signal = buildMarketSignal({
|
|
3187
|
+
match,
|
|
3188
|
+
source: "polymarket",
|
|
3189
|
+
sourceMarketId: event.id ?? eventSlug,
|
|
3190
|
+
asOf: asOf ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3191
|
+
outcomes,
|
|
3192
|
+
liquidity,
|
|
3193
|
+
now: options?.now ?? this.opts.now,
|
|
3194
|
+
maxAgeMs: options?.maxAgeMs ?? this.opts.maxAgeMs
|
|
3195
|
+
});
|
|
3196
|
+
return signal.ambiguous ? void 0 : signal;
|
|
3197
|
+
}
|
|
3198
|
+
};
|
|
3199
|
+
function deriveEventSlug(match) {
|
|
3200
|
+
const home = match.home.code.toLowerCase();
|
|
3201
|
+
const away = match.away.code.toLowerCase();
|
|
3202
|
+
if (home === away || home === "tbd" || away === "tbd") return void 0;
|
|
3203
|
+
if (!/^[a-z]{3}$/.test(home) || !/^[a-z]{3}$/.test(away)) return void 0;
|
|
3204
|
+
const date = match.kickoff.slice(0, 10);
|
|
3205
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
|
|
3206
|
+
return `fifwc-${home}-${away}-${date}`;
|
|
3207
|
+
}
|
|
3208
|
+
function slugToken(m) {
|
|
3209
|
+
return (m.slug ?? "").toLowerCase().split("-").pop() ?? "";
|
|
3210
|
+
}
|
|
3211
|
+
function isDrawMarket(m) {
|
|
3212
|
+
return slugToken(m) === "draw" || (m.groupItemTitle ?? "").trim().toLowerCase().startsWith("draw");
|
|
3213
|
+
}
|
|
3214
|
+
function pickMarket(markets, teamCode, teamName) {
|
|
3215
|
+
const code = teamCode.toLowerCase();
|
|
3216
|
+
const name = teamName.trim().toLowerCase();
|
|
3217
|
+
const teamMarkets = markets.filter((m) => !isDrawMarket(m));
|
|
3218
|
+
const bySlug = teamMarkets.find((m) => slugToken(m) === code);
|
|
3219
|
+
if (bySlug) return bySlug;
|
|
3220
|
+
return teamMarkets.find((m) => (m.groupItemTitle ?? "").trim().toLowerCase() === name);
|
|
3221
|
+
}
|
|
3222
|
+
function pickDraw(markets) {
|
|
3223
|
+
return markets.find(isDrawMarket);
|
|
3224
|
+
}
|
|
3225
|
+
function assertAllowedHost(base) {
|
|
3226
|
+
let host;
|
|
3227
|
+
try {
|
|
3228
|
+
host = new URL(base).host;
|
|
3229
|
+
} catch {
|
|
3230
|
+
throw new Error(`Invalid Polymarket base URL: ${base}`);
|
|
3231
|
+
}
|
|
3232
|
+
if (!ALLOWED_HOSTS.has(host)) {
|
|
3233
|
+
throw new Error(`Polymarket host not allow-listed: ${host}`);
|
|
3234
|
+
}
|
|
3235
|
+
}
|
|
3236
|
+
function yesPrice(market) {
|
|
3237
|
+
const labels = parseJsonArray(market.outcomes);
|
|
3238
|
+
const prices = parseJsonArray(market.outcomePrices).map((p2) => Number(p2));
|
|
3239
|
+
if (labels.length === 0 || labels.length !== prices.length) return void 0;
|
|
3240
|
+
const i = labels.findIndex((l) => l.trim().toLowerCase() === "yes");
|
|
3241
|
+
if (i < 0) return void 0;
|
|
3242
|
+
const p = prices[i];
|
|
3243
|
+
return typeof p === "number" && Number.isFinite(p) && p > 0 && p <= 1 ? p : void 0;
|
|
3244
|
+
}
|
|
3245
|
+
function parseJsonArray(v) {
|
|
3246
|
+
if (Array.isArray(v)) return v.map((x) => String(x));
|
|
3247
|
+
if (typeof v === "string") {
|
|
3248
|
+
try {
|
|
3249
|
+
const parsed = JSON.parse(v);
|
|
3250
|
+
return Array.isArray(parsed) ? parsed.map((x) => String(x)) : [];
|
|
3251
|
+
} catch {
|
|
3252
|
+
return [];
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
return [];
|
|
3256
|
+
}
|
|
3257
|
+
function numberish(v) {
|
|
3258
|
+
if (typeof v === "number") return Number.isFinite(v) ? v : void 0;
|
|
3259
|
+
if (typeof v === "string") {
|
|
3260
|
+
const n = Number(v);
|
|
3261
|
+
return Number.isFinite(n) ? n : void 0;
|
|
3262
|
+
}
|
|
3263
|
+
return void 0;
|
|
3264
|
+
}
|
|
3265
|
+
function resolveMarketSource(explicit) {
|
|
3266
|
+
if (explicit) return explicit;
|
|
3267
|
+
if (typeof process !== "undefined" && process.env?.CLAUDINHO_MARKETS_SOURCE) {
|
|
3268
|
+
return process.env.CLAUDINHO_MARKETS_SOURCE;
|
|
3269
|
+
}
|
|
3270
|
+
return "polymarket";
|
|
3271
|
+
}
|
|
3272
|
+
function makeMarketProvider(source) {
|
|
3273
|
+
switch (resolveMarketSource(source)) {
|
|
3274
|
+
case "fake":
|
|
3275
|
+
return new FakeMarketProvider({ synthesize: true });
|
|
3276
|
+
case "none":
|
|
3277
|
+
case "off":
|
|
3278
|
+
return new FakeMarketProvider();
|
|
3279
|
+
// no synth → yields no signals, no network
|
|
3280
|
+
default:
|
|
3281
|
+
return new PolymarketProvider();
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3284
|
+
async function getMarketSignals(provider, matches, options) {
|
|
3285
|
+
try {
|
|
3286
|
+
return await provider.findSignals(matches, options);
|
|
3287
|
+
} catch {
|
|
3288
|
+
return { signals: /* @__PURE__ */ new Map(), checked: /* @__PURE__ */ new Set() };
|
|
3289
|
+
}
|
|
3290
|
+
}
|
|
3291
|
+
var SHARE_HASHTAG = "#VibingLaVidaLoca";
|
|
3292
|
+
var SHARE_DISCLAIMER = "Independent fan project \xB7 not affiliated with FIFA or Anthropic.";
|
|
3293
|
+
function mid(m) {
|
|
3294
|
+
return isLive(m.status) || m.status === "FT" ? scoreline(m) : "vs";
|
|
3295
|
+
}
|
|
3296
|
+
function statusTail(m) {
|
|
3297
|
+
switch (m.status) {
|
|
3298
|
+
case "LIVE":
|
|
3299
|
+
return m.minute ? `${m.minute}'` : "LIVE";
|
|
3300
|
+
case "HT":
|
|
3301
|
+
return "HT";
|
|
3302
|
+
case "FT":
|
|
3303
|
+
return "FT";
|
|
3304
|
+
case "POSTPONED":
|
|
3305
|
+
return "postponed";
|
|
3306
|
+
case "CANCELLED":
|
|
3307
|
+
return "cancelled";
|
|
3308
|
+
default:
|
|
3309
|
+
return "";
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
function compactLine(m, input, single) {
|
|
3313
|
+
const home = `${m.home.flag} ${m.home.code}`;
|
|
3314
|
+
const away = `${m.away.code} ${m.away.flag}`;
|
|
3315
|
+
const opts = { tz: input.tz, locale: input.locale };
|
|
3316
|
+
let tail;
|
|
3317
|
+
if (m.status === "SCHEDULED") {
|
|
3318
|
+
const time = formatTime(m.kickoff, opts);
|
|
3319
|
+
tail = single ? `${formatDate(m.kickoff, opts)} ${time}` : time;
|
|
3320
|
+
} else {
|
|
3321
|
+
tail = statusTail(m);
|
|
3322
|
+
}
|
|
3323
|
+
return `${home} ${mid(m)} ${away}${tail ? ` \xB7 ${tail}` : ""}`;
|
|
3324
|
+
}
|
|
3325
|
+
function socialCard(m, input) {
|
|
3326
|
+
const lines = [];
|
|
3327
|
+
const head = `${m.home.flag} ${m.home.name} ${mid(m)} ${m.away.name} ${m.away.flag}`;
|
|
3328
|
+
if (m.status === "SCHEDULED") {
|
|
3329
|
+
lines.push(head);
|
|
3330
|
+
const date = formatDate(m.kickoff, { tz: input.tz, locale: input.locale });
|
|
3331
|
+
const time = formatTime(m.kickoff, { tz: input.tz, locale: input.locale });
|
|
3332
|
+
const zone = input.tz ? ` ${input.tz}` : "";
|
|
3333
|
+
lines.push(`${date} \xB7 ${time}${zone}`);
|
|
3334
|
+
} else {
|
|
3335
|
+
const tail = statusTail(m);
|
|
3336
|
+
lines.push(tail ? `${head} \xB7 ${tail}` : head);
|
|
3337
|
+
}
|
|
3338
|
+
const loc = matchLocation(m);
|
|
3339
|
+
if (loc) lines.push(loc);
|
|
3340
|
+
return lines;
|
|
3341
|
+
}
|
|
3342
|
+
function formatShareSnippet(input, options = {}) {
|
|
3343
|
+
const style = options.style ?? "social";
|
|
3344
|
+
const includeMarkets = options.includeMarkets !== false;
|
|
3345
|
+
const includeHashtag = options.includeHashtag !== false;
|
|
3346
|
+
const includeInstall = options.includeInstallLine !== false;
|
|
3347
|
+
const signals = input.marketSignals ?? /* @__PURE__ */ new Map();
|
|
3348
|
+
const single = input.matches.length === 1;
|
|
3349
|
+
const blocks = [input.title];
|
|
3350
|
+
if (input.matches.length === 0) {
|
|
3351
|
+
if (input.emptyNote) blocks.push(input.emptyNote);
|
|
3352
|
+
} else if (style === "compact") {
|
|
3353
|
+
blocks.push(input.matches.map((m) => compactLine(m, input, single)).join("\n"));
|
|
3354
|
+
} else {
|
|
3355
|
+
for (const m of input.matches) {
|
|
3356
|
+
const card = socialCard(m, input);
|
|
3357
|
+
const sig = includeMarkets ? signals.get(m.id) : void 0;
|
|
3358
|
+
if (sig) {
|
|
3359
|
+
if (single) card.push("", ...marketBlock(sig, m));
|
|
3360
|
+
else card.push(marketLine(sig, m));
|
|
3361
|
+
}
|
|
3362
|
+
blocks.push(card.join("\n"));
|
|
3363
|
+
}
|
|
3364
|
+
}
|
|
3365
|
+
const footer = [];
|
|
3366
|
+
if (input.source) footer.push(`Live data: ${liveSourceLabel(input.source)}`);
|
|
3367
|
+
footer.push([includeHashtag ? SHARE_HASHTAG : "", SHARE_DISCLAIMER].filter(Boolean).join(" \xB7 "));
|
|
3368
|
+
if (includeInstall && input.installLine) footer.push(`Try it: ${input.installLine}`);
|
|
3369
|
+
blocks.push(footer.join("\n"));
|
|
3370
|
+
return blocks.join("\n\n");
|
|
3371
|
+
}
|
|
2854
3372
|
|
|
2855
3373
|
// src/config.ts
|
|
2856
3374
|
var SUPPORTED_LANGS = ["en", "es", "pt", "fr"];
|
|
@@ -2876,6 +3394,11 @@ function pickColor(explicit) {
|
|
|
2876
3394
|
function isSupportedLang(s) {
|
|
2877
3395
|
return SUPPORTED_LANGS.includes(s);
|
|
2878
3396
|
}
|
|
3397
|
+
function pickMarkets(explicit) {
|
|
3398
|
+
if (explicit === false) return false;
|
|
3399
|
+
if ((process.env.CLAUDINHO_MARKETS ?? "").toLowerCase() === "off") return false;
|
|
3400
|
+
return true;
|
|
3401
|
+
}
|
|
2879
3402
|
function resolveConfig(opts) {
|
|
2880
3403
|
const langRequestedUnsupported = opts.lang && !isSupportedLang(opts.lang) ? opts.lang : void 0;
|
|
2881
3404
|
return {
|
|
@@ -2885,6 +3408,7 @@ function resolveConfig(opts) {
|
|
|
2885
3408
|
color: pickColor(opts.color),
|
|
2886
3409
|
source: opts.source ?? process.env.CLAUDINHO_SOURCE ?? "espn",
|
|
2887
3410
|
flavor: asFlavorLevel(opts.flavor ?? process.env.CLAUDINHO_FLAVOR),
|
|
3411
|
+
markets: pickMarkets(opts.markets),
|
|
2888
3412
|
langRequestedUnsupported
|
|
2889
3413
|
};
|
|
2890
3414
|
}
|
|
@@ -3070,8 +3594,8 @@ function statusToken(m, t, c) {
|
|
|
3070
3594
|
function matchLine(m, cfg, t, c) {
|
|
3071
3595
|
const home = `${m.home.flag} ${m.home.name}`;
|
|
3072
3596
|
const away = `${m.away.name} ${m.away.flag}`;
|
|
3073
|
-
const
|
|
3074
|
-
const left = `${home.padEnd(22)} ${
|
|
3597
|
+
const mid2 = isLive(m.status) || m.status === "FT" ? c.bold(scoreline(m)) : c.dim("vs");
|
|
3598
|
+
const left = `${home.padEnd(22)} ${mid2.padStart(3)} ${away}`;
|
|
3075
3599
|
let right = "";
|
|
3076
3600
|
if (m.status === "SCHEDULED") {
|
|
3077
3601
|
right = c.dim(
|
|
@@ -3090,34 +3614,117 @@ function header(text, c) {
|
|
|
3090
3614
|
function disclaimer(t, c) {
|
|
3091
3615
|
return c.dim(t("disclaimer"));
|
|
3092
3616
|
}
|
|
3617
|
+
function dataSource(source, c) {
|
|
3618
|
+
return source ? c.dim(`Live data: ${liveSourceLabel(source)}`) : "";
|
|
3619
|
+
}
|
|
3620
|
+
|
|
3621
|
+
// src/marketCache.ts
|
|
3622
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "fs";
|
|
3623
|
+
import { homedir } from "os";
|
|
3624
|
+
import { join } from "path";
|
|
3625
|
+
var POSITIVE_TTL_MS = 10 * 6e4;
|
|
3626
|
+
var NEGATIVE_TTL_MS = 3 * 6e4;
|
|
3627
|
+
function cacheDir() {
|
|
3628
|
+
const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
|
|
3629
|
+
return join(base, "claudinho");
|
|
3630
|
+
}
|
|
3631
|
+
function cachePath() {
|
|
3632
|
+
return join(cacheDir(), "market-signals.json");
|
|
3633
|
+
}
|
|
3634
|
+
function readFile() {
|
|
3635
|
+
try {
|
|
3636
|
+
return JSON.parse(readFileSync(cachePath(), "utf8"));
|
|
3637
|
+
} catch {
|
|
3638
|
+
return void 0;
|
|
3639
|
+
}
|
|
3640
|
+
}
|
|
3641
|
+
function readMarketCache(source, competition, now = Date.now()) {
|
|
3642
|
+
const signals = /* @__PURE__ */ new Map();
|
|
3643
|
+
const checked = /* @__PURE__ */ new Set();
|
|
3644
|
+
const file = readFile();
|
|
3645
|
+
if (!file || file.source !== source || file.competition !== competition) {
|
|
3646
|
+
return { signals, checked };
|
|
3647
|
+
}
|
|
3648
|
+
for (const [id, entry] of Object.entries(file.entries ?? {})) {
|
|
3649
|
+
const t = Date.parse(entry.fetchedAt);
|
|
3650
|
+
if (!Number.isFinite(t)) continue;
|
|
3651
|
+
const ttl = entry.signal ? POSITIVE_TTL_MS : NEGATIVE_TTL_MS;
|
|
3652
|
+
if (now - t > ttl) continue;
|
|
3653
|
+
checked.add(id);
|
|
3654
|
+
if (entry.signal) signals.set(id, entry.signal);
|
|
3655
|
+
}
|
|
3656
|
+
return { signals, checked };
|
|
3657
|
+
}
|
|
3658
|
+
function writeMarketCache(source, competition, attempted, fetched, now = Date.now()) {
|
|
3659
|
+
if (attempted.length === 0) return;
|
|
3660
|
+
try {
|
|
3661
|
+
const existing = readFile();
|
|
3662
|
+
const base = existing && existing.source === source && existing.competition === competition ? existing : { source, competition, entries: {} };
|
|
3663
|
+
const fetchedAt = new Date(now).toISOString();
|
|
3664
|
+
for (const id of attempted) {
|
|
3665
|
+
base.entries[id] = { fetchedAt, signal: fetched.get(id) ?? null };
|
|
3666
|
+
}
|
|
3667
|
+
mkdirSync(cacheDir(), { recursive: true });
|
|
3668
|
+
const tmp = join(cacheDir(), `market-signals.${process.pid}.tmp`);
|
|
3669
|
+
writeFileSync(tmp, JSON.stringify(base));
|
|
3670
|
+
renameSync(tmp, cachePath());
|
|
3671
|
+
} catch {
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
|
|
3675
|
+
// src/clipboard.ts
|
|
3676
|
+
import { spawnSync } from "child_process";
|
|
3677
|
+
function clipboardTools(platform) {
|
|
3678
|
+
if (platform === "darwin") return [{ cmd: "pbcopy", args: [] }];
|
|
3679
|
+
if (platform === "win32") return [{ cmd: "clip", args: [] }];
|
|
3680
|
+
return [
|
|
3681
|
+
{ cmd: "wl-copy", args: [] },
|
|
3682
|
+
{ cmd: "xclip", args: ["-selection", "clipboard"] },
|
|
3683
|
+
{ cmd: "xsel", args: ["--clipboard", "--input"] }
|
|
3684
|
+
];
|
|
3685
|
+
}
|
|
3686
|
+
function copyToClipboard(text, platform = process.platform) {
|
|
3687
|
+
for (const { cmd, args } of clipboardTools(platform)) {
|
|
3688
|
+
try {
|
|
3689
|
+
const res = spawnSync(cmd, args, {
|
|
3690
|
+
input: text,
|
|
3691
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
3692
|
+
timeout: 1e3
|
|
3693
|
+
});
|
|
3694
|
+
if (!res.error && res.status === 0) return true;
|
|
3695
|
+
} catch {
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
return false;
|
|
3699
|
+
}
|
|
3093
3700
|
|
|
3094
3701
|
// src/cache.ts
|
|
3095
3702
|
import {
|
|
3096
3703
|
closeSync,
|
|
3097
|
-
mkdirSync,
|
|
3704
|
+
mkdirSync as mkdirSync2,
|
|
3098
3705
|
openSync,
|
|
3099
|
-
readFileSync,
|
|
3100
|
-
renameSync,
|
|
3706
|
+
readFileSync as readFileSync2,
|
|
3707
|
+
renameSync as renameSync2,
|
|
3101
3708
|
rmSync,
|
|
3102
3709
|
statSync,
|
|
3103
3710
|
writeSync
|
|
3104
3711
|
} from "fs";
|
|
3105
|
-
import { homedir } from "os";
|
|
3106
|
-
import { join } from "path";
|
|
3712
|
+
import { homedir as homedir2 } from "os";
|
|
3713
|
+
import { join as join2 } from "path";
|
|
3107
3714
|
var LOCK_STALE_MS = 6e4;
|
|
3108
|
-
function
|
|
3109
|
-
const base = process.env.XDG_CACHE_HOME ||
|
|
3110
|
-
return
|
|
3715
|
+
function cacheDir2() {
|
|
3716
|
+
const base = process.env.XDG_CACHE_HOME || join2(homedir2(), ".cache");
|
|
3717
|
+
return join2(base, "claudinho");
|
|
3111
3718
|
}
|
|
3112
|
-
function
|
|
3113
|
-
return
|
|
3719
|
+
function cachePath2() {
|
|
3720
|
+
return join2(cacheDir2(), "state.json");
|
|
3114
3721
|
}
|
|
3115
3722
|
function lockPath() {
|
|
3116
|
-
return
|
|
3723
|
+
return join2(cacheDir2(), "refresh.lock");
|
|
3117
3724
|
}
|
|
3118
3725
|
function readState() {
|
|
3119
3726
|
try {
|
|
3120
|
-
return JSON.parse(
|
|
3727
|
+
return JSON.parse(readFileSync2(cachePath2(), "utf8"));
|
|
3121
3728
|
} catch {
|
|
3122
3729
|
return void 0;
|
|
3123
3730
|
}
|
|
@@ -3127,11 +3734,11 @@ function readCurrentState(source, competition) {
|
|
|
3127
3734
|
return s && s.source === source && s.competition === competition ? s : void 0;
|
|
3128
3735
|
}
|
|
3129
3736
|
function writeState(state) {
|
|
3130
|
-
const dir =
|
|
3131
|
-
|
|
3132
|
-
const tmp =
|
|
3737
|
+
const dir = cacheDir2();
|
|
3738
|
+
mkdirSync2(dir, { recursive: true });
|
|
3739
|
+
const tmp = join2(dir, `state.${process.pid}.tmp`);
|
|
3133
3740
|
writeFileSync_(tmp, JSON.stringify(state));
|
|
3134
|
-
|
|
3741
|
+
renameSync2(tmp, cachePath2());
|
|
3135
3742
|
}
|
|
3136
3743
|
function writeFileSync_(path, data) {
|
|
3137
3744
|
const fd = openSync(path, "w");
|
|
@@ -3149,7 +3756,7 @@ function ageMs(state, now = Date.now()) {
|
|
|
3149
3756
|
function lockAgeMs(now = Date.now()) {
|
|
3150
3757
|
const lp = lockPath();
|
|
3151
3758
|
try {
|
|
3152
|
-
const contents =
|
|
3759
|
+
const contents = readFileSync2(lp, "utf8");
|
|
3153
3760
|
const written = Number.parseInt(contents.split(/\s+/)[1] ?? "", 10);
|
|
3154
3761
|
if (Number.isFinite(written)) return now - written;
|
|
3155
3762
|
} catch {
|
|
@@ -3165,7 +3772,7 @@ function isLockFresh(now = Date.now()) {
|
|
|
3165
3772
|
return lockAgeMs(now) < LOCK_STALE_MS;
|
|
3166
3773
|
}
|
|
3167
3774
|
function acquireLock(now = Date.now()) {
|
|
3168
|
-
|
|
3775
|
+
mkdirSync2(cacheDir2(), { recursive: true });
|
|
3169
3776
|
const lp = lockPath();
|
|
3170
3777
|
try {
|
|
3171
3778
|
const fd = openSync(lp, "wx");
|
|
@@ -3337,14 +3944,14 @@ function spawnRefresh(source) {
|
|
|
3337
3944
|
import {
|
|
3338
3945
|
copyFileSync,
|
|
3339
3946
|
existsSync,
|
|
3340
|
-
mkdirSync as
|
|
3341
|
-
readFileSync as
|
|
3342
|
-
writeFileSync
|
|
3947
|
+
mkdirSync as mkdirSync3,
|
|
3948
|
+
readFileSync as readFileSync3,
|
|
3949
|
+
writeFileSync as writeFileSync2
|
|
3343
3950
|
} from "fs";
|
|
3344
|
-
import { homedir as
|
|
3345
|
-
import { dirname, join as
|
|
3951
|
+
import { homedir as homedir3 } from "os";
|
|
3952
|
+
import { dirname, join as join3 } from "path";
|
|
3346
3953
|
function claudeSettingsPath() {
|
|
3347
|
-
return
|
|
3954
|
+
return join3(homedir3(), ".claude", "settings.json");
|
|
3348
3955
|
}
|
|
3349
3956
|
function backupOnce(path) {
|
|
3350
3957
|
const bak = `${path}.claudinho.bak`;
|
|
@@ -3360,7 +3967,7 @@ function initStatusline(opts = {}) {
|
|
|
3360
3967
|
let settings = {};
|
|
3361
3968
|
if (existsSync(path)) {
|
|
3362
3969
|
try {
|
|
3363
|
-
settings = JSON.parse(
|
|
3970
|
+
settings = JSON.parse(readFileSync3(path, "utf8"));
|
|
3364
3971
|
} catch {
|
|
3365
3972
|
return {
|
|
3366
3973
|
action: "manual",
|
|
@@ -3376,8 +3983,8 @@ ${snippet}`
|
|
|
3376
3983
|
}
|
|
3377
3984
|
backupOnce(path);
|
|
3378
3985
|
settings.statusLine = sl;
|
|
3379
|
-
|
|
3380
|
-
|
|
3986
|
+
mkdirSync3(dirname(path), { recursive: true });
|
|
3987
|
+
writeFileSync2(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
3381
3988
|
return {
|
|
3382
3989
|
action: "written",
|
|
3383
3990
|
path,
|
|
@@ -3398,7 +4005,7 @@ function initHook(opts = {}) {
|
|
|
3398
4005
|
let settings = {};
|
|
3399
4006
|
if (existsSync(path)) {
|
|
3400
4007
|
try {
|
|
3401
|
-
settings = JSON.parse(
|
|
4008
|
+
settings = JSON.parse(readFileSync3(path, "utf8"));
|
|
3402
4009
|
} catch {
|
|
3403
4010
|
return {
|
|
3404
4011
|
action: "manual",
|
|
@@ -3420,8 +4027,8 @@ ${snippet}`
|
|
|
3420
4027
|
}
|
|
3421
4028
|
backupOnce(path);
|
|
3422
4029
|
matchers.push({ hooks: [{ type: "command", command }] });
|
|
3423
|
-
|
|
3424
|
-
|
|
4030
|
+
mkdirSync3(dirname(path), { recursive: true });
|
|
4031
|
+
writeFileSync2(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
3425
4032
|
return {
|
|
3426
4033
|
action: "written",
|
|
3427
4034
|
path,
|
|
@@ -3433,6 +4040,45 @@ ${snippet}`
|
|
|
3433
4040
|
function adapterFor({ cfg, adapter }) {
|
|
3434
4041
|
return adapter ?? makeAdapter(cfg.source);
|
|
3435
4042
|
}
|
|
4043
|
+
var DEFAULT_ON_MARKET_OPTS = { deadlineMs: 2e3, timeoutMs: 2500 };
|
|
4044
|
+
var MARKETS_CMD_OPTS = { deadlineMs: 12e3, timeoutMs: 6e3 };
|
|
4045
|
+
async function marketSignalsFor(ctx, matches, opts = {}) {
|
|
4046
|
+
if (ctx.marketProvider) return (await getMarketSignals(ctx.marketProvider, matches, opts)).signals;
|
|
4047
|
+
const source = resolveMarketSource();
|
|
4048
|
+
if (source !== "polymarket") {
|
|
4049
|
+
return (await getMarketSignals(makeMarketProvider(source), matches, opts)).signals;
|
|
4050
|
+
}
|
|
4051
|
+
const competition = resolveCompetition();
|
|
4052
|
+
const { signals: cached, checked: cachedIds } = readMarketCache("polymarket", competition);
|
|
4053
|
+
const result = /* @__PURE__ */ new Map();
|
|
4054
|
+
const miss = [];
|
|
4055
|
+
for (const m of matches) {
|
|
4056
|
+
const hit = cached.get(m.id);
|
|
4057
|
+
if (hit) result.set(m.id, hit);
|
|
4058
|
+
else if (!cachedIds.has(m.id)) miss.push(m);
|
|
4059
|
+
}
|
|
4060
|
+
if (miss.length > 0) {
|
|
4061
|
+
const { signals: fetched, checked } = await getMarketSignals(
|
|
4062
|
+
makeMarketProvider("polymarket"),
|
|
4063
|
+
miss,
|
|
4064
|
+
opts
|
|
4065
|
+
);
|
|
4066
|
+
writeMarketCache("polymarket", competition, [...checked], fetched);
|
|
4067
|
+
for (const [id, s] of fetched) result.set(id, s);
|
|
4068
|
+
}
|
|
4069
|
+
return result;
|
|
4070
|
+
}
|
|
4071
|
+
async function reliableMarketSignals(ctx, matches) {
|
|
4072
|
+
if (ctx.cfg.markets === false) return /* @__PURE__ */ new Map();
|
|
4073
|
+
const raw = await marketSignalsFor(ctx, matches, DEFAULT_ON_MARKET_OPTS);
|
|
4074
|
+
const now = /* @__PURE__ */ new Date();
|
|
4075
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
4076
|
+
for (const [id, s] of raw) if (isReliableMarketSignal(s, { now })) out2.set(id, s);
|
|
4077
|
+
return out2;
|
|
4078
|
+
}
|
|
4079
|
+
async function reliableMarketSignalFor(ctx, match) {
|
|
4080
|
+
return (await reliableMarketSignals(ctx, [match])).get(match.id);
|
|
4081
|
+
}
|
|
3436
4082
|
function out(line2 = "") {
|
|
3437
4083
|
process.stdout.write(line2 + "\n");
|
|
3438
4084
|
}
|
|
@@ -3457,10 +4103,17 @@ async function cmdToday(date, ctx) {
|
|
|
3457
4103
|
precheck(cfg, t, date);
|
|
3458
4104
|
const adapter = adapterFor(ctx);
|
|
3459
4105
|
const targetDate = date ?? localDate((/* @__PURE__ */ new Date()).toISOString(), cfg.tz);
|
|
3460
|
-
const { matches, degraded } = await getMatchesForDate(adapter, targetDate);
|
|
4106
|
+
const { matches, degraded, source } = await getMatchesForDate(adapter, targetDate);
|
|
3461
4107
|
const todays = fixturesByDate(targetDate, matches, cfg.tz);
|
|
4108
|
+
const signals = await reliableMarketSignals(ctx, todays);
|
|
3462
4109
|
if (cfg.json) {
|
|
3463
|
-
emitJson({
|
|
4110
|
+
emitJson({
|
|
4111
|
+
date: targetDate,
|
|
4112
|
+
degraded,
|
|
4113
|
+
source: source ?? null,
|
|
4114
|
+
matches: todays,
|
|
4115
|
+
marketSignals: Object.fromEntries(signals)
|
|
4116
|
+
});
|
|
3464
4117
|
return;
|
|
3465
4118
|
}
|
|
3466
4119
|
const c = painterFor(cfg);
|
|
@@ -3471,18 +4124,24 @@ async function cmdToday(date, ctx) {
|
|
|
3471
4124
|
if (todays.length === 0) {
|
|
3472
4125
|
out(c.dim(" " + t("today.none")));
|
|
3473
4126
|
} else {
|
|
3474
|
-
for (const m of todays)
|
|
4127
|
+
for (const m of todays) {
|
|
4128
|
+
out(matchLine(m, cfg, t, c));
|
|
4129
|
+
const s = signals.get(m.id);
|
|
4130
|
+
if (s) out(" " + c.dim(marketLine(s, m)));
|
|
4131
|
+
}
|
|
3475
4132
|
}
|
|
3476
4133
|
out();
|
|
4134
|
+
const src = dataSource(source, c);
|
|
4135
|
+
if (src) out(src);
|
|
3477
4136
|
out(disclaimer(t, c));
|
|
3478
4137
|
}
|
|
3479
4138
|
async function cmdLive(ctx) {
|
|
3480
4139
|
const { cfg, t } = ctx;
|
|
3481
4140
|
precheck(cfg, t);
|
|
3482
4141
|
const adapter = adapterFor(ctx);
|
|
3483
|
-
const { matches, degraded } = await getLiveMatches(adapter);
|
|
4142
|
+
const { matches, degraded, source } = await getLiveMatches(adapter);
|
|
3484
4143
|
if (cfg.json) {
|
|
3485
|
-
emitJson({ degraded, matches });
|
|
4144
|
+
emitJson({ degraded, source: source ?? null, matches });
|
|
3486
4145
|
return;
|
|
3487
4146
|
}
|
|
3488
4147
|
const c = painterFor(cfg);
|
|
@@ -3495,6 +4154,8 @@ async function cmdLive(ctx) {
|
|
|
3495
4154
|
for (const m of matches) out(matchLine(m, cfg, t, c));
|
|
3496
4155
|
}
|
|
3497
4156
|
out();
|
|
4157
|
+
const src = dataSource(source, c);
|
|
4158
|
+
if (src) out(src);
|
|
3498
4159
|
out(disclaimer(t, c));
|
|
3499
4160
|
}
|
|
3500
4161
|
async function cmdNext(team, { cfg, t }) {
|
|
@@ -3528,7 +4189,7 @@ async function cmdTable(group, ctx) {
|
|
|
3528
4189
|
const { cfg, t } = ctx;
|
|
3529
4190
|
precheck(cfg, t);
|
|
3530
4191
|
const adapter = adapterFor(ctx);
|
|
3531
|
-
const { matches } = await getMatchesForDate(
|
|
4192
|
+
const { matches, degraded, source } = await getMatchesForDate(
|
|
3532
4193
|
adapter,
|
|
3533
4194
|
localDate((/* @__PURE__ */ new Date()).toISOString(), cfg.tz)
|
|
3534
4195
|
);
|
|
@@ -3538,7 +4199,11 @@ async function cmdTable(group, ctx) {
|
|
|
3538
4199
|
group: g,
|
|
3539
4200
|
standings: computeStandings(fixturesByGroup(g, matches))
|
|
3540
4201
|
}));
|
|
3541
|
-
emitJson(
|
|
4202
|
+
emitJson({
|
|
4203
|
+
degraded,
|
|
4204
|
+
source: source ?? null,
|
|
4205
|
+
tables: group ? tables[0] ?? null : tables
|
|
4206
|
+
});
|
|
3542
4207
|
return;
|
|
3543
4208
|
}
|
|
3544
4209
|
const c = painterFor(cfg);
|
|
@@ -3578,6 +4243,8 @@ async function cmdTable(group, ctx) {
|
|
|
3578
4243
|
out(table.toString());
|
|
3579
4244
|
}
|
|
3580
4245
|
out();
|
|
4246
|
+
const src = dataSource(source, c);
|
|
4247
|
+
if (src) out(src);
|
|
3581
4248
|
out(disclaimer(t, c));
|
|
3582
4249
|
}
|
|
3583
4250
|
function cmdPrompt({ cfg }) {
|
|
@@ -3633,15 +4300,18 @@ async function cmdMatch(id, ctx) {
|
|
|
3633
4300
|
precheck(cfg, t);
|
|
3634
4301
|
const adapter = adapterFor(ctx);
|
|
3635
4302
|
let match = allFixtures().find((m) => m.id === id);
|
|
4303
|
+
let liveSource;
|
|
3636
4304
|
try {
|
|
3637
4305
|
if (match) {
|
|
3638
4306
|
const live = await adapter.fetchByDate(match.kickoff.slice(0, 10));
|
|
3639
4307
|
match = live.find((m) => m.id === id) ?? match;
|
|
4308
|
+
liveSource = adapter.name;
|
|
3640
4309
|
}
|
|
3641
4310
|
} catch {
|
|
3642
4311
|
}
|
|
4312
|
+
const marketSignal = match ? await reliableMarketSignalFor(ctx, match) : void 0;
|
|
3643
4313
|
if (cfg.json) {
|
|
3644
|
-
emitJson({ match: match ?? null });
|
|
4314
|
+
emitJson({ match: match ?? null, source: liveSource ?? null, marketSignal: marketSignal ?? null });
|
|
3645
4315
|
return;
|
|
3646
4316
|
}
|
|
3647
4317
|
const c = painterFor(cfg);
|
|
@@ -3668,8 +4338,271 @@ async function cmdMatch(id, ctx) {
|
|
|
3668
4338
|
out(` ${e.minute}' ${e.type} ${e.teamCode}${e.player ? ` \u2014 ${e.player}` : ""}`);
|
|
3669
4339
|
}
|
|
3670
4340
|
}
|
|
4341
|
+
if (marketSignal) {
|
|
4342
|
+
out();
|
|
4343
|
+
for (const mline of marketBlock(marketSignal, match)) out(" " + c.dim(mline));
|
|
4344
|
+
}
|
|
4345
|
+
out();
|
|
4346
|
+
const src = dataSource(liveSource, c);
|
|
4347
|
+
if (src) out(src);
|
|
4348
|
+
out(disclaimer(t, c));
|
|
4349
|
+
}
|
|
4350
|
+
var MARKET_INFO = "Prediction-market data is informational only.";
|
|
4351
|
+
function marketDisplayable(sig) {
|
|
4352
|
+
return !sig.ambiguous && sig.favorite != null && hasSaneDistribution(sig.outcomes);
|
|
4353
|
+
}
|
|
4354
|
+
function marketHeaderLine(m) {
|
|
4355
|
+
return `${m.home.flag} ${m.home.name} vs ${m.away.name} ${m.away.flag}`;
|
|
4356
|
+
}
|
|
4357
|
+
function printMarketBlock(m, sig, c) {
|
|
4358
|
+
for (const line2 of marketBlock(sig, m)) out(" " + c.dim(line2));
|
|
4359
|
+
}
|
|
4360
|
+
async function cmdMarkets(target, team, ctx) {
|
|
4361
|
+
const { cfg, t } = ctx;
|
|
4362
|
+
if (target === "next") {
|
|
4363
|
+
precheck(cfg, t);
|
|
4364
|
+
if (!team) throw new InputError("Usage: claudinho markets next <team>");
|
|
4365
|
+
const code = team.toUpperCase();
|
|
4366
|
+
const fixture = nextFixtureForTeam(code);
|
|
4367
|
+
const sig = fixture ? (await marketSignalsFor(ctx, [fixture], MARKETS_CMD_OPTS)).get(fixture.id) : void 0;
|
|
4368
|
+
const shown = sig && marketDisplayable(sig) ? sig : void 0;
|
|
4369
|
+
if (cfg.json) {
|
|
4370
|
+
emitJson({
|
|
4371
|
+
team: code,
|
|
4372
|
+
matchId: fixture?.id ?? null,
|
|
4373
|
+
informationalOnly: true,
|
|
4374
|
+
signal: shown ?? null
|
|
4375
|
+
});
|
|
4376
|
+
return;
|
|
4377
|
+
}
|
|
4378
|
+
const c2 = painterFor(cfg);
|
|
4379
|
+
out();
|
|
4380
|
+
if (!fixture) {
|
|
4381
|
+
out(c2.dim(" " + t("next.none", { team: code })));
|
|
4382
|
+
} else {
|
|
4383
|
+
out(header(marketHeaderLine(fixture), c2));
|
|
4384
|
+
out();
|
|
4385
|
+
if (shown) printMarketBlock(fixture, shown, c2);
|
|
4386
|
+
else out(c2.dim(" No market signal for this match."));
|
|
4387
|
+
}
|
|
4388
|
+
out();
|
|
4389
|
+
out(disclaimer(t, c2));
|
|
4390
|
+
out(c2.dim(MARKET_INFO));
|
|
4391
|
+
return;
|
|
4392
|
+
}
|
|
4393
|
+
if (target && target !== "today" && !isValidDate(target)) {
|
|
4394
|
+
precheck(cfg, t);
|
|
4395
|
+
const match = allFixtures().find((m) => m.id === target);
|
|
4396
|
+
const sig = match ? (await marketSignalsFor(ctx, [match], MARKETS_CMD_OPTS)).get(match.id) : void 0;
|
|
4397
|
+
const shown = sig && marketDisplayable(sig) ? sig : void 0;
|
|
4398
|
+
if (cfg.json) {
|
|
4399
|
+
emitJson({ matchId: target, informationalOnly: true, signal: shown ?? null });
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4402
|
+
const c2 = painterFor(cfg);
|
|
4403
|
+
out();
|
|
4404
|
+
if (!match) {
|
|
4405
|
+
out(c2.dim(" " + t("match.none", { id: target })));
|
|
4406
|
+
} else {
|
|
4407
|
+
out(header(marketHeaderLine(match), c2));
|
|
4408
|
+
out();
|
|
4409
|
+
if (shown) printMarketBlock(match, shown, c2);
|
|
4410
|
+
else out(c2.dim(" No market signal for this match."));
|
|
4411
|
+
}
|
|
4412
|
+
out();
|
|
4413
|
+
out(disclaimer(t, c2));
|
|
4414
|
+
out(c2.dim(MARKET_INFO));
|
|
4415
|
+
return;
|
|
4416
|
+
}
|
|
4417
|
+
const explicitDate = target && target !== "today" ? target : void 0;
|
|
4418
|
+
precheck(cfg, t, explicitDate);
|
|
4419
|
+
const date = explicitDate ?? localDate((/* @__PURE__ */ new Date()).toISOString(), cfg.tz);
|
|
4420
|
+
const { matches } = await getMatchesForDate(adapterFor(ctx), date);
|
|
4421
|
+
const todays = fixturesByDate(date, matches, cfg.tz);
|
|
4422
|
+
const signals = await marketSignalsFor(ctx, todays, MARKETS_CMD_OPTS);
|
|
4423
|
+
const rows = todays.map((m) => ({ match: m, signal: signals.get(m.id) })).filter(
|
|
4424
|
+
(r) => !!r.signal && marketDisplayable(r.signal)
|
|
4425
|
+
);
|
|
4426
|
+
if (cfg.json) {
|
|
4427
|
+
const marketSignals = {};
|
|
4428
|
+
for (const r of rows) marketSignals[r.match.id] = r.signal;
|
|
4429
|
+
emitJson({ date, informationalOnly: true, marketSignals });
|
|
4430
|
+
return;
|
|
4431
|
+
}
|
|
4432
|
+
const c = painterFor(cfg);
|
|
3671
4433
|
out();
|
|
4434
|
+
out(header(`Market signals \xB7 ${date}`, c));
|
|
4435
|
+
out();
|
|
4436
|
+
if (rows.length === 0) {
|
|
4437
|
+
out(c.dim(` No market signals available for ${date}.`));
|
|
4438
|
+
} else {
|
|
4439
|
+
for (const { match, signal } of rows) {
|
|
4440
|
+
out(" " + c.bold(marketHeaderLine(match)));
|
|
4441
|
+
printMarketBlock(match, signal, c);
|
|
4442
|
+
out();
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
3672
4445
|
out(disclaimer(t, c));
|
|
4446
|
+
out(c.dim(MARKET_INFO));
|
|
4447
|
+
}
|
|
4448
|
+
function pickShareStyle(v) {
|
|
4449
|
+
return v === "compact" ? "compact" : "social";
|
|
4450
|
+
}
|
|
4451
|
+
async function reliableShareSignals(ctx, matches) {
|
|
4452
|
+
const raw = await reliableMarketSignals(ctx, matches);
|
|
4453
|
+
const out2 = /* @__PURE__ */ new Map();
|
|
4454
|
+
for (const [id, s] of raw) if (marketDisplayable(s)) out2.set(id, s);
|
|
4455
|
+
return out2;
|
|
4456
|
+
}
|
|
4457
|
+
function emitShare(ctx, e, copy) {
|
|
4458
|
+
const snippet = formatShareSnippet(e.input, e.options);
|
|
4459
|
+
if (ctx.cfg.json) {
|
|
4460
|
+
emitJson({
|
|
4461
|
+
kind: e.kind,
|
|
4462
|
+
target: e.target,
|
|
4463
|
+
...e.team ? { team: e.team } : {},
|
|
4464
|
+
source: e.input.source ?? null,
|
|
4465
|
+
informationalOnly: true,
|
|
4466
|
+
style: e.options.style ?? "social",
|
|
4467
|
+
snippet,
|
|
4468
|
+
matches: e.input.matches,
|
|
4469
|
+
marketSignals: Object.fromEntries(e.input.marketSignals ?? /* @__PURE__ */ new Map())
|
|
4470
|
+
});
|
|
4471
|
+
} else {
|
|
4472
|
+
out(snippet);
|
|
4473
|
+
}
|
|
4474
|
+
if (copy) {
|
|
4475
|
+
const ok = (ctx.copy ?? copyToClipboard)(snippet);
|
|
4476
|
+
process.stderr.write(
|
|
4477
|
+
(ok ? "Copied share snippet to clipboard." : "Clipboard unavailable; printed snippet instead.") + "\n"
|
|
4478
|
+
);
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
async function cmdShare(target, team, opts, ctx) {
|
|
4482
|
+
const { cfg, t } = ctx;
|
|
4483
|
+
const baseOptions = {
|
|
4484
|
+
style: pickShareStyle(opts.style),
|
|
4485
|
+
includeMarkets: cfg.markets !== false,
|
|
4486
|
+
includeHashtag: opts.hashtag !== false,
|
|
4487
|
+
includeInstallLine: opts.installLine !== false
|
|
4488
|
+
};
|
|
4489
|
+
const copy = opts.copy === true;
|
|
4490
|
+
if (target === "live") {
|
|
4491
|
+
precheck(cfg, t);
|
|
4492
|
+
const { matches, source: source2 } = await getLiveMatches(adapterFor(ctx));
|
|
4493
|
+
emitShare(
|
|
4494
|
+
ctx,
|
|
4495
|
+
{
|
|
4496
|
+
kind: "live",
|
|
4497
|
+
target: "live",
|
|
4498
|
+
input: {
|
|
4499
|
+
title: "Live match pulse",
|
|
4500
|
+
matches,
|
|
4501
|
+
source: source2,
|
|
4502
|
+
emptyNote: "No matches in play right now.",
|
|
4503
|
+
installLine: "npx @claudinho/cli live",
|
|
4504
|
+
tz: cfg.tz,
|
|
4505
|
+
locale: cfg.lang
|
|
4506
|
+
},
|
|
4507
|
+
// Live snippets stay lean: no market enrichment (and no extra fetch).
|
|
4508
|
+
options: { ...baseOptions, includeMarkets: false }
|
|
4509
|
+
},
|
|
4510
|
+
copy
|
|
4511
|
+
);
|
|
4512
|
+
return;
|
|
4513
|
+
}
|
|
4514
|
+
if (target === "next") {
|
|
4515
|
+
precheck(cfg, t);
|
|
4516
|
+
if (!team) throw new InputError("Usage: claudinho share next <team>");
|
|
4517
|
+
const code = team.toUpperCase();
|
|
4518
|
+
const fixture = nextFixtureForTeam(code);
|
|
4519
|
+
const matches = fixture ? [fixture] : [];
|
|
4520
|
+
const signals2 = await reliableShareSignals(ctx, matches);
|
|
4521
|
+
const teamName = fixture ? fixture.home.code === code ? fixture.home.name : fixture.away.name : code;
|
|
4522
|
+
emitShare(
|
|
4523
|
+
ctx,
|
|
4524
|
+
{
|
|
4525
|
+
kind: "next",
|
|
4526
|
+
target: "next",
|
|
4527
|
+
team: code,
|
|
4528
|
+
input: {
|
|
4529
|
+
title: `Next up for ${teamName}`,
|
|
4530
|
+
matches,
|
|
4531
|
+
marketSignals: signals2,
|
|
4532
|
+
emptyNote: `No upcoming fixture found for ${code}.`,
|
|
4533
|
+
installLine: `npx @claudinho/cli next ${code}`,
|
|
4534
|
+
tz: cfg.tz,
|
|
4535
|
+
locale: cfg.lang
|
|
4536
|
+
},
|
|
4537
|
+
options: baseOptions
|
|
4538
|
+
},
|
|
4539
|
+
copy
|
|
4540
|
+
);
|
|
4541
|
+
return;
|
|
4542
|
+
}
|
|
4543
|
+
if (target && target !== "today" && !isValidDate(target)) {
|
|
4544
|
+
precheck(cfg, t);
|
|
4545
|
+
const adapter = adapterFor(ctx);
|
|
4546
|
+
let match = allFixtures().find((m) => m.id === target);
|
|
4547
|
+
let source2;
|
|
4548
|
+
try {
|
|
4549
|
+
if (match) {
|
|
4550
|
+
const live = await adapter.fetchByDate(match.kickoff.slice(0, 10));
|
|
4551
|
+
match = live.find((m) => m.id === target) ?? match;
|
|
4552
|
+
source2 = adapter.name;
|
|
4553
|
+
}
|
|
4554
|
+
} catch {
|
|
4555
|
+
}
|
|
4556
|
+
const matches = match ? [match] : [];
|
|
4557
|
+
const signals2 = await reliableShareSignals(ctx, matches);
|
|
4558
|
+
emitShare(
|
|
4559
|
+
ctx,
|
|
4560
|
+
{
|
|
4561
|
+
kind: "match",
|
|
4562
|
+
target,
|
|
4563
|
+
input: {
|
|
4564
|
+
title: "Match pulse",
|
|
4565
|
+
matches,
|
|
4566
|
+
marketSignals: signals2,
|
|
4567
|
+
source: source2,
|
|
4568
|
+
emptyNote: `No match found with id ${target}.`,
|
|
4569
|
+
installLine: `npx @claudinho/cli match ${target}`,
|
|
4570
|
+
tz: cfg.tz,
|
|
4571
|
+
locale: cfg.lang
|
|
4572
|
+
},
|
|
4573
|
+
options: baseOptions
|
|
4574
|
+
},
|
|
4575
|
+
copy
|
|
4576
|
+
);
|
|
4577
|
+
return;
|
|
4578
|
+
}
|
|
4579
|
+
const explicitDate = target && target !== "today" ? target : void 0;
|
|
4580
|
+
precheck(cfg, t, explicitDate);
|
|
4581
|
+
const date = explicitDate ?? localDate((/* @__PURE__ */ new Date()).toISOString(), cfg.tz);
|
|
4582
|
+
const { matches: all, source } = await getMatchesForDate(adapterFor(ctx), date);
|
|
4583
|
+
const todays = fixturesByDate(date, all, cfg.tz);
|
|
4584
|
+
const signals = await reliableShareSignals(ctx, todays);
|
|
4585
|
+
const human = formatDate(`${date}T12:00:00.000Z`, { tz: cfg.tz, locale: cfg.lang });
|
|
4586
|
+
const title = explicitDate ? `Matches \xB7 ${human}` : `Today's matches \xB7 ${human}`;
|
|
4587
|
+
emitShare(
|
|
4588
|
+
ctx,
|
|
4589
|
+
{
|
|
4590
|
+
kind: "today",
|
|
4591
|
+
target: date,
|
|
4592
|
+
input: {
|
|
4593
|
+
title,
|
|
4594
|
+
matches: todays,
|
|
4595
|
+
marketSignals: signals,
|
|
4596
|
+
source,
|
|
4597
|
+
emptyNote: `No matches scheduled for ${human}.`,
|
|
4598
|
+
installLine: "npx @claudinho/cli today",
|
|
4599
|
+
tz: cfg.tz,
|
|
4600
|
+
locale: cfg.lang
|
|
4601
|
+
},
|
|
4602
|
+
options: baseOptions
|
|
4603
|
+
},
|
|
4604
|
+
copy
|
|
4605
|
+
);
|
|
3673
4606
|
}
|
|
3674
4607
|
var VIBES = [
|
|
3675
4608
|
"Shipping code, watching goals.",
|
|
@@ -3704,7 +4637,7 @@ function handlePipeError(stream) {
|
|
|
3704
4637
|
}
|
|
3705
4638
|
handlePipeError(process.stdout);
|
|
3706
4639
|
handlePipeError(process.stderr);
|
|
3707
|
-
var VERSION = "0.
|
|
4640
|
+
var VERSION = "0.4.0";
|
|
3708
4641
|
var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
|
|
3709
4642
|
function ctxFrom(cmd) {
|
|
3710
4643
|
const root = cmd.parent ?? cmd;
|
|
@@ -3719,7 +4652,7 @@ function fail(err) {
|
|
|
3719
4652
|
process.exit(1);
|
|
3720
4653
|
}
|
|
3721
4654
|
var program = new Command();
|
|
3722
|
-
program.name("claudinho").description("The 2026 football tournament in your terminal.\n" + DISCLAIMER).version(VERSION, "-v, --version").option("--lang <code>", "language: en, es, pt, fr").option("--tz <zone>", "IANA timezone, e.g. America/Mexico_City").option("--json", "output JSON (for scripting)").option("--no-color", "disable ANSI colors").option("--source <name>", "live data provider (advanced)").option("--flavor <level>", "commentary flair: off, subtle, full (default: full)");
|
|
4655
|
+
program.name("claudinho").description("The 2026 football tournament in your terminal.\n" + DISCLAIMER).version(VERSION, "-v, --version").option("--lang <code>", "language: en, es, pt, fr").option("--tz <zone>", "IANA timezone, e.g. America/Mexico_City").option("--json", "output JSON (for scripting)").option("--no-color", "disable ANSI colors").option("--source <name>", "live data provider (advanced)").option("--flavor <level>", "commentary flair: off, subtle, full (default: full)").option("--no-markets", "hide prediction-market signals (informational odds)");
|
|
3723
4656
|
program.addHelpText("after", "\n#VibingLaVidaLoca \u26BD");
|
|
3724
4657
|
program.command("today").description("show a day's fixtures (default: today)").argument("[date]", "date as YYYY-MM-DD").action(async (date, _opts, cmd) => {
|
|
3725
4658
|
try {
|
|
@@ -3756,6 +4689,20 @@ program.command("match").description("show a single match by id").argument("<id>
|
|
|
3756
4689
|
fail(e);
|
|
3757
4690
|
}
|
|
3758
4691
|
});
|
|
4692
|
+
program.command("markets").description("show prediction-market signals (read-only, informational only)").argument("[target]", 'date (YYYY-MM-DD), match id, "today", or "next"').argument("[team]", 'team code when target is "next" (e.g. MEX)').action(async (target, team, _opts, cmd) => {
|
|
4693
|
+
try {
|
|
4694
|
+
await cmdMarkets(target, team, ctxFrom(cmd));
|
|
4695
|
+
} catch (e) {
|
|
4696
|
+
fail(e);
|
|
4697
|
+
}
|
|
4698
|
+
});
|
|
4699
|
+
program.command("share").description("print a shareable, copy-pasteable match snippet (#VibingLaVidaLoca)").argument("[target]", '"today" (default), "live", a date, a match id, or "next"').argument("[team]", 'team code when target is "next" (e.g. MEX)').option("--style <style>", "snippet style: social (default) or compact").option("--copy", "also copy the snippet to the clipboard (best-effort)").option("--no-hashtag", "omit the #VibingLaVidaLoca tag").option("--no-install-line", "omit the install/run cue").action(async (target, team, opts, cmd) => {
|
|
4700
|
+
try {
|
|
4701
|
+
await cmdShare(target, team, opts, ctxFrom(cmd));
|
|
4702
|
+
} catch (e) {
|
|
4703
|
+
fail(e);
|
|
4704
|
+
}
|
|
4705
|
+
});
|
|
3759
4706
|
program.command("prompt").description("print a one-line status (Claude Code statusline, tmux, Starship, \u2026)").action((_opts, cmd) => {
|
|
3760
4707
|
cmdPrompt(ctxFrom(cmd));
|
|
3761
4708
|
});
|