@claudinho/core 0.3.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 +2 -1
- package/dist/index.d.ts +55 -1
- package/dist/index.js +107 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,8 +22,9 @@ npm i @claudinho/core
|
|
|
22
22
|
- **Static schedule** — all 104 fixtures (groups, venues, host cities, kickoffs) bundled; query with `allFixtures`, `fixturesByDate` (groups by your timezone), `fixturesByTeam`, `fixturesByGroup`, `nextFixtureForTeam`, `groups`
|
|
23
23
|
- **Live overlay** — `makeAdapter`, `getMatchesForDate`, `getLiveMatches`, `mergeLive` (static base + live state, with graceful degradation)
|
|
24
24
|
- **Standings** — `computeStandings` (points / GD / GF tiebreak)
|
|
25
|
-
- **Helpers** — emoji flags (`nationToFlag`), TZ-aware time (`formatKickoff`, `countdown`, `localDate`), location strings (`matchLocation`), localized commentary flair (`matchFlavor` / `FlavorLevel`), validators (`isValidDate`, `isValidTimeZone`)
|
|
25
|
+
- **Helpers** — emoji flags (`nationToFlag`), TZ-aware time (`formatKickoff`, `formatDate`, `formatTime`, `countdown`, `localDate`), location strings (`matchLocation`), localized commentary flair (`matchFlavor` / `FlavorLevel`), validators (`isValidDate`, `isValidTimeZone`)
|
|
26
26
|
- **Prediction-market signals (sidecar)** — read-only market odds kept *separate* from `Match`: the `MarketSignal` / `MarketProvider` model, the `PolymarketProvider` (public Gamma data only — no auth/trading/links; event slugs auto-derived per fixture, validation fails closed), a `FakeMarketProvider`, `makeMarketProvider`, `getMarketSignal` / `getMarketSignals`, the `isReliableMarketSignal` gate, and approved-copy formatters (`marketFavoriteText`, `marketProbabilityText`, `marketBlock`). Informational only — never betting advice.
|
|
27
|
+
- **Shareable snippets** — `formatShareSnippet` builds pure, deterministic, plain-text match cards (composing `Match` + the market copy bank) for the CLI's `share` command and future MCP/site reuse. The non-affiliation disclaimer is non-optional; market lines come from the approved bank.
|
|
27
28
|
|
|
28
29
|
## Example
|
|
29
30
|
|
package/dist/index.d.ts
CHANGED
|
@@ -135,6 +135,10 @@ interface FormatOpts {
|
|
|
135
135
|
}
|
|
136
136
|
/** Format a kickoff like "Thu 19:00" in the target timezone/locale. */
|
|
137
137
|
declare function formatKickoff(iso: string, opts?: FormatOpts): string;
|
|
138
|
+
/** A short calendar date like "Jun 11" in the target timezone/locale. */
|
|
139
|
+
declare function formatDate(iso: string, opts?: FormatOpts): string;
|
|
140
|
+
/** Time of day like "19:00" (24-hour) in the target timezone/locale. */
|
|
141
|
+
declare function formatTime(iso: string, opts?: FormatOpts): string;
|
|
138
142
|
/** Compact human countdown until kickoff: "3d4h", "2h10m", "45m", or "now". */
|
|
139
143
|
declare function countdown(iso: string, from?: Date): string;
|
|
140
144
|
/** The calendar date (YYYY-MM-DD) of a kickoff in the target timezone. */
|
|
@@ -709,4 +713,54 @@ declare class PolymarketProvider implements MarketProvider {
|
|
|
709
713
|
private toSignal;
|
|
710
714
|
}
|
|
711
715
|
|
|
712
|
-
|
|
716
|
+
/** The two v1 snippet shapes. `social` is the rich card; `compact` is one terse line per match. */
|
|
717
|
+
type ShareStyle = 'compact' | 'social';
|
|
718
|
+
/** The project social tag — a distribution unit, default-on but removable. */
|
|
719
|
+
declare const SHARE_HASHTAG = "#VibingLaVidaLoca";
|
|
720
|
+
/**
|
|
721
|
+
* The non-affiliation line. Non-optional in every snippet: a shared artifact is
|
|
722
|
+
* decontextualized, so the legal disclaimer must travel with every paste.
|
|
723
|
+
*/
|
|
724
|
+
declare const SHARE_DISCLAIMER = "Independent fan project \u00B7 not affiliated with FIFA or Anthropic.";
|
|
725
|
+
interface ShareSnippetOptions {
|
|
726
|
+
/** Snippet shape; defaults to `social`. */
|
|
727
|
+
style?: ShareStyle;
|
|
728
|
+
/** Include the reliable market block/line when a signal is present (default true). */
|
|
729
|
+
includeMarkets?: boolean;
|
|
730
|
+
/** Include the #VibingLaVidaLoca tag (default true). */
|
|
731
|
+
includeHashtag?: boolean;
|
|
732
|
+
/** Include the "Try it: …" install/run cue (default true). */
|
|
733
|
+
includeInstallLine?: boolean;
|
|
734
|
+
}
|
|
735
|
+
interface ShareSnippetInput {
|
|
736
|
+
/** Pre-resolved, English title line, e.g. "Next up for Mexico". */
|
|
737
|
+
title: string;
|
|
738
|
+
/** Matches to render (0..n). An empty set still yields a valid titled card. */
|
|
739
|
+
matches: Match[];
|
|
740
|
+
/**
|
|
741
|
+
* Reliable, display-ready market signals keyed by match id (sidecar — never
|
|
742
|
+
* embedded in Match). Callers gate these; the formatter only renders.
|
|
743
|
+
*/
|
|
744
|
+
marketSignals?: Map<string, MarketSignal>;
|
|
745
|
+
/** Live-data provider name (e.g. "espn") for attribution; omit when static/degraded. */
|
|
746
|
+
source?: string;
|
|
747
|
+
/**
|
|
748
|
+
* Body line shown when `matches` is empty (e.g. "No upcoming fixture found for
|
|
749
|
+
* ZZZ."), so an unknown/empty target yields a clear card instead of a void.
|
|
750
|
+
*/
|
|
751
|
+
emptyNote?: string;
|
|
752
|
+
/** Exact run cue to advertise, e.g. "npx @claudinho/cli next MEX". */
|
|
753
|
+
installLine?: string;
|
|
754
|
+
/** Timezone for kickoff date/time (date/time only — copy stays English). */
|
|
755
|
+
tz?: string;
|
|
756
|
+
/** Locale for kickoff date/time. */
|
|
757
|
+
locale?: string;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Render a shareable snippet. Pure and deterministic: identical input yields
|
|
761
|
+
* identical output. Blocks (title, each match, footer) are separated by a blank
|
|
762
|
+
* line; lines within a block by a single newline.
|
|
763
|
+
*/
|
|
764
|
+
declare function formatShareSnippet(input: ShareSnippetInput, options?: ShareSnippetOptions): string;
|
|
765
|
+
|
|
766
|
+
export { type BuildSignalInput, DEFAULT_COMPETITION, DEFAULT_FLAVOR, DEFAULT_MAX_AGE_MS, EspnAdapter, type EspnAdapterOptions, FLAVOR_LEVELS, FakeMarketProvider, type FakeMarketProviderOptions, type FavoriteStrength, type FlavorLevel, type FormatOpts, type LedgerRow, type LiveResult, type MapContext, type MarketFavorite, type MarketMapping, type MarketMappingTable, type MarketOutcome, type MarketOutcomeKind, type MarketProvider, type MarketSignal, type MarketSignalOptions, type MarketSignalsResult, type Match, type MatchEvent, type Outcome, PolymarketProvider, type PolymarketProviderOptions, type ProviderAdapter, type ProviderCapabilities, type PunditPick, SHARE_DISCLAIMER, SHARE_HASHTAG, type ShareSnippetInput, type ShareSnippetOptions, type ShareStyle, type Stage, type StandingRow, type Status, type Team, allFixtures, asFlavorLevel, buildMarketSignal, byKickoff, competitionBase, computeStandings, countdown, deriveFavorite, favoriteStrength, fixturesByDate, fixturesByGroup, fixturesByTeam, flagEmoji, formatDate, formatKickoff, formatShareSnippet, formatTime, getLiveMatches, getMarketSignal, getMarketSignals, getMatchesForDate, groups, hasSaneDistribution, isFinished, isFlavorLevel, isLive, isReliableMarketSignal, isStaleSignal, isValidDate, isValidTimeZone, liveSourceLabel, localDate, makeAdapter, makeMarketProvider, mapEspnEvent, mapsCleanly, marketAttributionText, marketBlock, marketFavoriteText, marketLine, marketProbabilityText, marketSourceLabel, matchFlavor, matchLocation, mergeLive, nationToFlag, nationToRegion, nextFixtureForTeam, normalizeOutcomes, outcomeFromScore, resolveCompetition, resolveMarketSource, resolveTz, scoreline };
|
package/dist/index.js
CHANGED
|
@@ -310,6 +310,25 @@ function formatKickoff(iso, opts = {}) {
|
|
|
310
310
|
timeZone: tz
|
|
311
311
|
}).format(new Date(iso));
|
|
312
312
|
}
|
|
313
|
+
function formatDate(iso, opts = {}) {
|
|
314
|
+
const tz = resolveTz(opts.tz);
|
|
315
|
+
const locale = safeLocale(opts.locale);
|
|
316
|
+
return new Intl.DateTimeFormat(locale, {
|
|
317
|
+
month: "short",
|
|
318
|
+
day: "numeric",
|
|
319
|
+
timeZone: tz
|
|
320
|
+
}).format(new Date(iso));
|
|
321
|
+
}
|
|
322
|
+
function formatTime(iso, opts = {}) {
|
|
323
|
+
const tz = resolveTz(opts.tz);
|
|
324
|
+
const locale = safeLocale(opts.locale);
|
|
325
|
+
return new Intl.DateTimeFormat(locale, {
|
|
326
|
+
hour: "2-digit",
|
|
327
|
+
minute: "2-digit",
|
|
328
|
+
hour12: false,
|
|
329
|
+
timeZone: tz
|
|
330
|
+
}).format(new Date(iso));
|
|
331
|
+
}
|
|
313
332
|
function countdown(iso, from = /* @__PURE__ */ new Date()) {
|
|
314
333
|
const ms = new Date(iso).getTime() - from.getTime();
|
|
315
334
|
if (ms <= 0) return "now";
|
|
@@ -3306,6 +3325,89 @@ async function getMarketSignals(provider, matches, options) {
|
|
|
3306
3325
|
return { signals: /* @__PURE__ */ new Map(), checked: /* @__PURE__ */ new Set() };
|
|
3307
3326
|
}
|
|
3308
3327
|
}
|
|
3328
|
+
|
|
3329
|
+
// src/share/format.ts
|
|
3330
|
+
var SHARE_HASHTAG = "#VibingLaVidaLoca";
|
|
3331
|
+
var SHARE_DISCLAIMER = "Independent fan project \xB7 not affiliated with FIFA or Anthropic.";
|
|
3332
|
+
function mid(m) {
|
|
3333
|
+
return isLive(m.status) || m.status === "FT" ? scoreline(m) : "vs";
|
|
3334
|
+
}
|
|
3335
|
+
function statusTail(m) {
|
|
3336
|
+
switch (m.status) {
|
|
3337
|
+
case "LIVE":
|
|
3338
|
+
return m.minute ? `${m.minute}'` : "LIVE";
|
|
3339
|
+
case "HT":
|
|
3340
|
+
return "HT";
|
|
3341
|
+
case "FT":
|
|
3342
|
+
return "FT";
|
|
3343
|
+
case "POSTPONED":
|
|
3344
|
+
return "postponed";
|
|
3345
|
+
case "CANCELLED":
|
|
3346
|
+
return "cancelled";
|
|
3347
|
+
default:
|
|
3348
|
+
return "";
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
function compactLine(m, input, single) {
|
|
3352
|
+
const home = `${m.home.flag} ${m.home.code}`;
|
|
3353
|
+
const away = `${m.away.code} ${m.away.flag}`;
|
|
3354
|
+
const opts = { tz: input.tz, locale: input.locale };
|
|
3355
|
+
let tail;
|
|
3356
|
+
if (m.status === "SCHEDULED") {
|
|
3357
|
+
const time = formatTime(m.kickoff, opts);
|
|
3358
|
+
tail = single ? `${formatDate(m.kickoff, opts)} ${time}` : time;
|
|
3359
|
+
} else {
|
|
3360
|
+
tail = statusTail(m);
|
|
3361
|
+
}
|
|
3362
|
+
return `${home} ${mid(m)} ${away}${tail ? ` \xB7 ${tail}` : ""}`;
|
|
3363
|
+
}
|
|
3364
|
+
function socialCard(m, input) {
|
|
3365
|
+
const lines = [];
|
|
3366
|
+
const head = `${m.home.flag} ${m.home.name} ${mid(m)} ${m.away.name} ${m.away.flag}`;
|
|
3367
|
+
if (m.status === "SCHEDULED") {
|
|
3368
|
+
lines.push(head);
|
|
3369
|
+
const date = formatDate(m.kickoff, { tz: input.tz, locale: input.locale });
|
|
3370
|
+
const time = formatTime(m.kickoff, { tz: input.tz, locale: input.locale });
|
|
3371
|
+
const zone = input.tz ? ` ${input.tz}` : "";
|
|
3372
|
+
lines.push(`${date} \xB7 ${time}${zone}`);
|
|
3373
|
+
} else {
|
|
3374
|
+
const tail = statusTail(m);
|
|
3375
|
+
lines.push(tail ? `${head} \xB7 ${tail}` : head);
|
|
3376
|
+
}
|
|
3377
|
+
const loc = matchLocation(m);
|
|
3378
|
+
if (loc) lines.push(loc);
|
|
3379
|
+
return lines;
|
|
3380
|
+
}
|
|
3381
|
+
function formatShareSnippet(input, options = {}) {
|
|
3382
|
+
const style = options.style ?? "social";
|
|
3383
|
+
const includeMarkets = options.includeMarkets !== false;
|
|
3384
|
+
const includeHashtag = options.includeHashtag !== false;
|
|
3385
|
+
const includeInstall = options.includeInstallLine !== false;
|
|
3386
|
+
const signals = input.marketSignals ?? /* @__PURE__ */ new Map();
|
|
3387
|
+
const single = input.matches.length === 1;
|
|
3388
|
+
const blocks = [input.title];
|
|
3389
|
+
if (input.matches.length === 0) {
|
|
3390
|
+
if (input.emptyNote) blocks.push(input.emptyNote);
|
|
3391
|
+
} else if (style === "compact") {
|
|
3392
|
+
blocks.push(input.matches.map((m) => compactLine(m, input, single)).join("\n"));
|
|
3393
|
+
} else {
|
|
3394
|
+
for (const m of input.matches) {
|
|
3395
|
+
const card = socialCard(m, input);
|
|
3396
|
+
const sig = includeMarkets ? signals.get(m.id) : void 0;
|
|
3397
|
+
if (sig) {
|
|
3398
|
+
if (single) card.push("", ...marketBlock(sig, m));
|
|
3399
|
+
else card.push(marketLine(sig, m));
|
|
3400
|
+
}
|
|
3401
|
+
blocks.push(card.join("\n"));
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
const footer = [];
|
|
3405
|
+
if (input.source) footer.push(`Live data: ${liveSourceLabel(input.source)}`);
|
|
3406
|
+
footer.push([includeHashtag ? SHARE_HASHTAG : "", SHARE_DISCLAIMER].filter(Boolean).join(" \xB7 "));
|
|
3407
|
+
if (includeInstall && input.installLine) footer.push(`Try it: ${input.installLine}`);
|
|
3408
|
+
blocks.push(footer.join("\n"));
|
|
3409
|
+
return blocks.join("\n\n");
|
|
3410
|
+
}
|
|
3309
3411
|
export {
|
|
3310
3412
|
DEFAULT_COMPETITION,
|
|
3311
3413
|
DEFAULT_FLAVOR,
|
|
@@ -3314,6 +3416,8 @@ export {
|
|
|
3314
3416
|
FLAVOR_LEVELS,
|
|
3315
3417
|
FakeMarketProvider,
|
|
3316
3418
|
PolymarketProvider,
|
|
3419
|
+
SHARE_DISCLAIMER,
|
|
3420
|
+
SHARE_HASHTAG,
|
|
3317
3421
|
allFixtures,
|
|
3318
3422
|
asFlavorLevel,
|
|
3319
3423
|
buildMarketSignal,
|
|
@@ -3327,7 +3431,10 @@ export {
|
|
|
3327
3431
|
fixturesByGroup,
|
|
3328
3432
|
fixturesByTeam,
|
|
3329
3433
|
flagEmoji,
|
|
3434
|
+
formatDate,
|
|
3330
3435
|
formatKickoff,
|
|
3436
|
+
formatShareSnippet,
|
|
3437
|
+
formatTime,
|
|
3331
3438
|
getLiveMatches,
|
|
3332
3439
|
getMarketSignal,
|
|
3333
3440
|
getMarketSignals,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@claudinho/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Domain model, provider adapters, standings, and a read-only market-signal sidecar for Claudinho — the 2026 men's football tournament. Not affiliated with FIFA or Anthropic.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|