@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.
Files changed (3) hide show
  1. package/README.md +102 -3
  2. package/dist/index.js +991 -44
  3. 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 mid = isLive(m.status) || m.status === "FT" ? c.bold(scoreline(m)) : c.dim("vs");
3074
- const left = `${home.padEnd(22)} ${mid.padStart(3)} ${away}`;
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 cacheDir() {
3109
- const base = process.env.XDG_CACHE_HOME || join(homedir(), ".cache");
3110
- return join(base, "claudinho");
3715
+ function cacheDir2() {
3716
+ const base = process.env.XDG_CACHE_HOME || join2(homedir2(), ".cache");
3717
+ return join2(base, "claudinho");
3111
3718
  }
3112
- function cachePath() {
3113
- return join(cacheDir(), "state.json");
3719
+ function cachePath2() {
3720
+ return join2(cacheDir2(), "state.json");
3114
3721
  }
3115
3722
  function lockPath() {
3116
- return join(cacheDir(), "refresh.lock");
3723
+ return join2(cacheDir2(), "refresh.lock");
3117
3724
  }
3118
3725
  function readState() {
3119
3726
  try {
3120
- return JSON.parse(readFileSync(cachePath(), "utf8"));
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 = cacheDir();
3131
- mkdirSync(dir, { recursive: true });
3132
- const tmp = join(dir, `state.${process.pid}.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
- renameSync(tmp, cachePath());
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 = readFileSync(lp, "utf8");
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
- mkdirSync(cacheDir(), { recursive: true });
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 mkdirSync2,
3341
- readFileSync as readFileSync2,
3342
- writeFileSync
3947
+ mkdirSync as mkdirSync3,
3948
+ readFileSync as readFileSync3,
3949
+ writeFileSync as writeFileSync2
3343
3950
  } from "fs";
3344
- import { homedir as homedir2 } from "os";
3345
- import { dirname, join as join2 } from "path";
3951
+ import { homedir as homedir3 } from "os";
3952
+ import { dirname, join as join3 } from "path";
3346
3953
  function claudeSettingsPath() {
3347
- return join2(homedir2(), ".claude", "settings.json");
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(readFileSync2(path, "utf8"));
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
- mkdirSync2(dirname(path), { recursive: true });
3380
- writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
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(readFileSync2(path, "utf8"));
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
- mkdirSync2(dirname(path), { recursive: true });
3424
- writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
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({ date: targetDate, degraded, matches: todays });
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) out(matchLine(m, cfg, t, c));
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(group ? tables[0] ?? null : tables);
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.2.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
  });