@claudinho/cli 0.8.12 → 0.8.14

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 +3 -0
  2. package/dist/index.js +117 -31
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  **The 2026 men's football tournament, right in your terminal.** Live scores, fixtures, group tables, and market signals — TZ-aware, localized, scriptable. No API key, no signup.
4
4
 
5
+ > ⭐ Installing via `npx` or globally? **[Star the repo](https://github.com/arturogarrido/claudinho)** — a fan project runs on stars. (`claudinho star` shows you how anytime.)
6
+
5
7
  ## Install
6
8
 
7
9
  ```bash
@@ -48,6 +50,7 @@ claudinho init-cursor-statusline # (granular) wire just the Cursor CLI statusli
48
50
  claudinho hook # live-score context for a Claude Code hook (silent off-match)
49
51
  claudinho init-hook # (granular) make Claude itself score-aware (UserPromptSubmit)
50
52
  claudinho vibe # a matchday-coder one-liner (#VibingLaVidaLoca)
53
+ claudinho star # how to support the project (star the repo ⭐)
51
54
  ```
52
55
 
53
56
  ### Examples
package/dist/index.js CHANGED
@@ -4918,6 +4918,37 @@ function writeMarketCache(source, competition, attempted, fetched, now = Date.no
4918
4918
  }
4919
4919
  }
4920
4920
 
4921
+ // src/starNudge.ts
4922
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
4923
+ import { homedir as homedir2 } from "os";
4924
+ import { dirname, join as join2 } from "path";
4925
+ var REPO_URL = "https://github.com/arturogarrido/claudinho";
4926
+ var NUDGE_EVERY = 5;
4927
+ function counterPath() {
4928
+ const base = process.env.XDG_CACHE_HOME || join2(homedir2(), ".cache");
4929
+ return join2(base, "claudinho", "runs.json");
4930
+ }
4931
+ function shouldNudge(runCount, every = NUDGE_EVERY) {
4932
+ return runCount > 0 && runCount % every === 0;
4933
+ }
4934
+ function bumpRunCount(path = counterPath()) {
4935
+ try {
4936
+ let count = 0;
4937
+ try {
4938
+ const raw = JSON.parse(readFileSync2(path, "utf8"));
4939
+ if (typeof raw.count === "number" && Number.isFinite(raw.count)) count = raw.count;
4940
+ } catch {
4941
+ count = 0;
4942
+ }
4943
+ count += 1;
4944
+ mkdirSync2(dirname(path), { recursive: true });
4945
+ writeFileSync2(path, JSON.stringify({ count }), "utf8");
4946
+ return count;
4947
+ } catch {
4948
+ return void 0;
4949
+ }
4950
+ }
4951
+
4921
4952
  // src/clipboard.ts
4922
4953
  import { spawnSync } from "child_process";
4923
4954
  function clipboardTools(platform) {
@@ -4947,30 +4978,30 @@ function copyToClipboard(text, platform = process.platform) {
4947
4978
  // src/cache.ts
4948
4979
  import {
4949
4980
  closeSync,
4950
- mkdirSync as mkdirSync2,
4981
+ mkdirSync as mkdirSync3,
4951
4982
  openSync,
4952
- readFileSync as readFileSync2,
4983
+ readFileSync as readFileSync3,
4953
4984
  renameSync as renameSync2,
4954
4985
  rmSync,
4955
4986
  statSync,
4956
4987
  writeSync
4957
4988
  } from "fs";
4958
- import { homedir as homedir2 } from "os";
4959
- import { join as join2 } from "path";
4989
+ import { homedir as homedir3 } from "os";
4990
+ import { join as join3 } from "path";
4960
4991
  var LOCK_STALE_MS = 6e4;
4961
4992
  function cacheDir2() {
4962
- const base = process.env.XDG_CACHE_HOME || join2(homedir2(), ".cache");
4963
- return join2(base, "claudinho");
4993
+ const base = process.env.XDG_CACHE_HOME || join3(homedir3(), ".cache");
4994
+ return join3(base, "claudinho");
4964
4995
  }
4965
4996
  function cachePath2() {
4966
- return join2(cacheDir2(), "state.json");
4997
+ return join3(cacheDir2(), "state.json");
4967
4998
  }
4968
4999
  function lockPath() {
4969
- return join2(cacheDir2(), "refresh.lock");
5000
+ return join3(cacheDir2(), "refresh.lock");
4970
5001
  }
4971
5002
  function readState() {
4972
5003
  try {
4973
- return JSON.parse(readFileSync2(cachePath2(), "utf8"));
5004
+ return JSON.parse(readFileSync3(cachePath2(), "utf8"));
4974
5005
  } catch {
4975
5006
  return void 0;
4976
5007
  }
@@ -4981,8 +5012,8 @@ function readCurrentState(source, competition) {
4981
5012
  }
4982
5013
  function writeState(state) {
4983
5014
  const dir = cacheDir2();
4984
- mkdirSync2(dir, { recursive: true });
4985
- const tmp = join2(dir, `state.${process.pid}.tmp`);
5015
+ mkdirSync3(dir, { recursive: true });
5016
+ const tmp = join3(dir, `state.${process.pid}.tmp`);
4986
5017
  writeFileSync_(tmp, JSON.stringify(state));
4987
5018
  renameSync2(tmp, cachePath2());
4988
5019
  }
@@ -5007,7 +5038,7 @@ function fixturesAgeMs(state, now = Date.now()) {
5007
5038
  function lockAgeMs(now = Date.now()) {
5008
5039
  const lp = lockPath();
5009
5040
  try {
5010
- const contents = readFileSync2(lp, "utf8");
5041
+ const contents = readFileSync3(lp, "utf8");
5011
5042
  const written = Number.parseInt(contents.split(/\s+/)[1] ?? "", 10);
5012
5043
  if (Number.isFinite(written)) return now - written;
5013
5044
  } catch {
@@ -5023,7 +5054,7 @@ function isLockFresh(now = Date.now()) {
5023
5054
  return lockAgeMs(now) < LOCK_STALE_MS;
5024
5055
  }
5025
5056
  function acquireLock(now = Date.now()) {
5026
- mkdirSync2(cacheDir2(), { recursive: true });
5057
+ mkdirSync3(cacheDir2(), { recursive: true });
5027
5058
  const lp = lockPath();
5028
5059
  try {
5029
5060
  const fd = openSync(lp, "wx");
@@ -5267,7 +5298,7 @@ function spawnRefresh(source) {
5267
5298
  }
5268
5299
 
5269
5300
  // src/cursorPayload.ts
5270
- import { readFileSync as readFileSync3 } from "fs";
5301
+ import { readFileSync as readFileSync4 } from "fs";
5271
5302
  function parseCursorPayload(raw) {
5272
5303
  try {
5273
5304
  const trimmed = raw.trim();
@@ -5280,7 +5311,7 @@ function parseCursorPayload(raw) {
5280
5311
  function readCursorPayload() {
5281
5312
  try {
5282
5313
  if (process.stdin.isTTY) return void 0;
5283
- return parseCursorPayload(readFileSync3(0, "utf8"));
5314
+ return parseCursorPayload(readFileSync4(0, "utf8"));
5284
5315
  } catch {
5285
5316
  return void 0;
5286
5317
  }
@@ -5323,17 +5354,17 @@ ${meta}`;
5323
5354
  import {
5324
5355
  copyFileSync,
5325
5356
  existsSync,
5326
- mkdirSync as mkdirSync3,
5327
- readFileSync as readFileSync4,
5328
- writeFileSync as writeFileSync2
5357
+ mkdirSync as mkdirSync4,
5358
+ readFileSync as readFileSync5,
5359
+ writeFileSync as writeFileSync3
5329
5360
  } from "fs";
5330
- import { homedir as homedir3 } from "os";
5331
- import { dirname, join as join3 } from "path";
5361
+ import { homedir as homedir4 } from "os";
5362
+ import { dirname as dirname2, join as join4 } from "path";
5332
5363
  function claudeSettingsPath() {
5333
- return join3(homedir3(), ".claude", "settings.json");
5364
+ return join4(homedir4(), ".claude", "settings.json");
5334
5365
  }
5335
5366
  function cursorCliConfigPath() {
5336
- return join3(homedir3(), ".cursor", "cli-config.json");
5367
+ return join4(homedir4(), ".cursor", "cli-config.json");
5337
5368
  }
5338
5369
  function isSameCommand(configured, requested) {
5339
5370
  return configured === requested;
@@ -5364,7 +5395,7 @@ function restartMessage(target) {
5364
5395
  function readSettings(path, snippet) {
5365
5396
  if (!existsSync(path)) return {};
5366
5397
  try {
5367
- return JSON.parse(readFileSync4(path, "utf8"));
5398
+ return JSON.parse(readFileSync5(path, "utf8"));
5368
5399
  } catch {
5369
5400
  return {
5370
5401
  action: "manual",
@@ -5395,8 +5426,8 @@ function initStatuslineFor(target, opts = {}) {
5395
5426
  }
5396
5427
  backupOnce(path);
5397
5428
  settings.statusLine = sl;
5398
- mkdirSync3(dirname(path), { recursive: true });
5399
- writeFileSync2(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
5429
+ mkdirSync4(dirname2(path), { recursive: true });
5430
+ writeFileSync3(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
5400
5431
  const surface = target === "cursor" ? "Cursor CLI statusline" : "Statusline";
5401
5432
  return {
5402
5433
  action: "written",
@@ -5442,8 +5473,8 @@ function initHook(opts = {}) {
5442
5473
  const hooks = settings.hooks;
5443
5474
  hooks[CLAUDE_HOOK_EVENT] ??= [];
5444
5475
  hooks[CLAUDE_HOOK_EVENT].push({ hooks: [{ type: "command", command }] });
5445
- mkdirSync3(dirname(path), { recursive: true });
5446
- writeFileSync2(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
5476
+ mkdirSync4(dirname2(path), { recursive: true });
5477
+ writeFileSync3(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
5447
5478
  return {
5448
5479
  action: "written",
5449
5480
  path,
@@ -5530,6 +5561,12 @@ function resolveTeamArg(team, usage) {
5530
5561
  if (!code) throw new InputError(usage);
5531
5562
  return code.toUpperCase();
5532
5563
  }
5564
+ var MEX_RALLY = "\xBFY si s\xED?";
5565
+ function mexRally(m, cfg, c) {
5566
+ if (cfg.flavor === "off") return void 0;
5567
+ if (m.home.code !== "MEX" && m.away.code !== "MEX") return void 0;
5568
+ return c.green(MEX_RALLY);
5569
+ }
5533
5570
  async function cmdToday(date, ctx) {
5534
5571
  const { cfg, t: t2 } = ctx;
5535
5572
  precheck(cfg, t2, date);
@@ -5561,6 +5598,8 @@ async function cmdToday(date, ctx) {
5561
5598
  out(matchLine(m, cfg, t2, c, flags));
5562
5599
  const s = signals.get(m.id);
5563
5600
  if (s) out(" " + c.dim(marketLine(s, m)));
5601
+ const rally = mexRally(m, cfg, c);
5602
+ if (rally) out(" " + rally);
5564
5603
  }
5565
5604
  }
5566
5605
  out();
@@ -5568,6 +5607,7 @@ async function cmdToday(date, ctx) {
5568
5607
  const src = dataSource(source, cfg.lang, c);
5569
5608
  if (src) out(src);
5570
5609
  out(disclaimer(t2, c));
5610
+ maybeStarNudge(ctx);
5571
5611
  }
5572
5612
  async function cmdLive(ctx) {
5573
5613
  const { cfg, t: t2 } = ctx;
@@ -5594,6 +5634,7 @@ async function cmdLive(ctx) {
5594
5634
  const src = dataSource(source, cfg.lang, c);
5595
5635
  if (src) out(src);
5596
5636
  out(disclaimer(t2, c));
5637
+ maybeStarNudge(ctx);
5597
5638
  }
5598
5639
  async function cmdNext(team, ctx) {
5599
5640
  const { cfg, t: t2, now } = ctx;
@@ -5626,10 +5667,13 @@ async function cmdNext(team, ctx) {
5626
5667
  `${stage}${formatKickoff(fixture.kickoff, { tz: cfg.tz, locale: cfg.lang })} \xB7 ` + t2("next.in", { countdown: countdown(fixture.kickoff) })
5627
5668
  )
5628
5669
  );
5670
+ const rally = mexRally(fixture, cfg, c);
5671
+ if (rally) out(" " + rally);
5629
5672
  out();
5630
5673
  const src = dataSource(source, cfg.lang, c);
5631
5674
  if (src) out(src);
5632
5675
  out(disclaimer(t2, c));
5676
+ maybeStarNudge(ctx);
5633
5677
  }
5634
5678
  async function cmdTable(group, ctx) {
5635
5679
  const { cfg, t: t2 } = ctx;
@@ -5692,6 +5736,7 @@ async function cmdTable(group, ctx) {
5692
5736
  const src = dataSource(source, cfg.lang, c);
5693
5737
  if (src) out(src);
5694
5738
  out(disclaimer(t2, c));
5739
+ maybeStarNudge(ctx);
5695
5740
  }
5696
5741
  var BRACKET_STAGES = /* @__PURE__ */ new Set(["R32", "R16", "QF", "SF", "3P", "F"]);
5697
5742
  async function cmdBracket(stage, opts, ctx) {
@@ -5743,6 +5788,7 @@ async function cmdBracket(stage, opts, ctx) {
5743
5788
  const src = dataSource(source, cfg.lang, c);
5744
5789
  if (src) out(src);
5745
5790
  out(disclaimer(t2, c));
5791
+ maybeStarNudge(ctx);
5746
5792
  }
5747
5793
  function cmdPrompt({ cfg }) {
5748
5794
  try {
@@ -5788,13 +5834,19 @@ function printInitResult(res, cfg) {
5788
5834
  out(`${mark} ${res.message}`);
5789
5835
  }
5790
5836
  function cmdInitStatusline(opts, { cfg }) {
5791
- printInitResult(initStatusline({ print: opts.print, command: opts.command }), cfg);
5837
+ const res = initStatusline({ print: opts.print, command: opts.command });
5838
+ printInitResult(res, cfg);
5839
+ if (!opts.print && res.action === "written") printInitStarCta(cfg);
5792
5840
  }
5793
5841
  function cmdInitHook(opts, { cfg }) {
5794
- printInitResult(initHook({ print: opts.print, command: opts.command }), cfg);
5842
+ const res = initHook({ print: opts.print, command: opts.command });
5843
+ printInitResult(res, cfg);
5844
+ if (!opts.print && res.action === "written") printInitStarCta(cfg);
5795
5845
  }
5796
5846
  function cmdInitCursorStatusline(opts, { cfg }) {
5797
- printInitResult(initCursorStatusline({ print: opts.print, command: opts.command }), cfg);
5847
+ const res = initCursorStatusline({ print: opts.print, command: opts.command });
5848
+ printInitResult(res, cfg);
5849
+ if (!opts.print && res.action === "written") printInitStarCta(cfg);
5798
5850
  }
5799
5851
  var CURSOR_MCP_SNIPPET = `{
5800
5852
  "mcpServers": {
@@ -5819,6 +5871,7 @@ function cmdInitCursor(opts, { cfg }) {
5819
5871
  out("Tip: export CLAUDINHO_CURSOR_META=auto for a model + context line below the score.");
5820
5872
  out("");
5821
5873
  out("\u2192 Restart your agent session to see it.");
5874
+ printInitStarCta(cfg);
5822
5875
  }
5823
5876
  function cmdInitClaude(opts, { cfg }) {
5824
5877
  if (opts.print) {
@@ -5839,6 +5892,7 @@ function cmdInitClaude(opts, { cfg }) {
5839
5892
  out(` ${CLAUDE_MCP_ONELINER}`);
5840
5893
  out("");
5841
5894
  out("\u2192 Restart Claude Code to see it.");
5895
+ printInitStarCta(cfg);
5842
5896
  }
5843
5897
  async function cmdMatch(id, ctx) {
5844
5898
  const { cfg, t: t2 } = ctx;
@@ -5887,6 +5941,7 @@ async function cmdMatch(id, ctx) {
5887
5941
  const src = dataSource(liveSource, cfg.lang, c);
5888
5942
  if (src) out(src);
5889
5943
  out(disclaimer(t2, c));
5944
+ maybeStarNudge(ctx);
5890
5945
  }
5891
5946
  var MARKET_INFO = "Prediction-market data is informational only.";
5892
5947
  function marketDisplayable(match, sig) {
@@ -6311,6 +6366,34 @@ function vibePool(todayLocal, fixtures = allFixtures()) {
6311
6366
  if (todayLocal === last) return [...VIBES, ...VIBES_FINAL];
6312
6367
  return VIBES;
6313
6368
  }
6369
+ function cmdStar(ctx) {
6370
+ const { cfg } = ctx;
6371
+ if (cfg.json) {
6372
+ emitJson({ repo: REPO_URL, hashtag: "#VibingLaVidaLoca" });
6373
+ return;
6374
+ }
6375
+ const c = painterFor(cfg);
6376
+ out();
6377
+ out(" " + c.bold("\u2B50 Star Claudinho on GitHub"));
6378
+ out(" " + c.cyan(REPO_URL));
6379
+ out();
6380
+ out(" " + c.dim("Built for devs & fans \xB7 #VibingLaVidaLoca \u26BD"));
6381
+ out(" " + c.dim("Live World Cup scores in your terminal, Claude Code & Cursor \u2014 no API keys."));
6382
+ out();
6383
+ }
6384
+ function maybeStarNudge(ctx) {
6385
+ if (ctx.cfg.json || !process.stdout.isTTY || process.env.CLAUDINHO_NO_STAR) return;
6386
+ const n = bumpRunCount();
6387
+ if (n === void 0 || !shouldNudge(n)) return;
6388
+ const c = painterFor(ctx.cfg);
6389
+ out();
6390
+ out(c.dim(` \u2B50 Enjoying Claudinho? Star it \u2192 ${REPO_URL} (claudinho star)`));
6391
+ }
6392
+ function printInitStarCta(cfg) {
6393
+ const c = painterFor(cfg);
6394
+ out("");
6395
+ out(c.dim(`\u2B50 If this keeps you in the flow during the match, star the repo \u2192 ${REPO_URL}`));
6396
+ }
6314
6397
  function cmdVibe(ctx) {
6315
6398
  const { cfg } = ctx;
6316
6399
  const pool = vibePool(localDate((ctx.now ?? /* @__PURE__ */ new Date()).toISOString(), cfg.tz));
@@ -6344,7 +6427,7 @@ function handlePipeError(stream) {
6344
6427
  }
6345
6428
  handlePipeError(process.stdout);
6346
6429
  handlePipeError(process.stderr);
6347
- var VERSION = "0.8.12";
6430
+ var VERSION = "0.8.14";
6348
6431
  var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
6349
6432
  function ctxFrom(cmd) {
6350
6433
  let root = cmd;
@@ -6468,6 +6551,9 @@ program.command("init-hook").description("wire the live-score hook into Claude C
6468
6551
  program.command("vibe").description("print a matchday-coder one-liner (#VibingLaVidaLoca)").action((_opts, cmd) => {
6469
6552
  cmdVibe(ctxFrom(cmd));
6470
6553
  });
6554
+ program.command("star").description("how to support Claudinho \u2014 star the repo \u2B50").action((_opts, cmd) => {
6555
+ cmdStar(ctxFrom(cmd));
6556
+ });
6471
6557
  var refreshCmd = new Command("_refresh").description("(internal) refresh the statusline cache").action(async (_opts, cmd) => {
6472
6558
  try {
6473
6559
  await cmdRefresh(ctxFrom(cmd));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@claudinho/cli",
3
- "version": "0.8.12",
3
+ "version": "0.8.14",
4
4
  "description": "Live scores, fixtures, group tables, and read-only prediction-market signals for the 2026 men's football tournament — in your terminal, Claude Code, and Cursor CLI statusline. No API keys. Not affiliated with FIFA or Anthropic.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -61,7 +61,7 @@
61
61
  "tsup": "^8.0.0",
62
62
  "typescript": "^5.7.0",
63
63
  "vitest": "^4.1.9",
64
- "@claudinho/core": "0.8.12"
64
+ "@claudinho/core": "0.8.14"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",