@claudinho/cli 0.8.12 → 0.8.13

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 +107 -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,
@@ -5568,6 +5599,7 @@ async function cmdToday(date, ctx) {
5568
5599
  const src = dataSource(source, cfg.lang, c);
5569
5600
  if (src) out(src);
5570
5601
  out(disclaimer(t2, c));
5602
+ maybeStarNudge(ctx);
5571
5603
  }
5572
5604
  async function cmdLive(ctx) {
5573
5605
  const { cfg, t: t2 } = ctx;
@@ -5594,6 +5626,7 @@ async function cmdLive(ctx) {
5594
5626
  const src = dataSource(source, cfg.lang, c);
5595
5627
  if (src) out(src);
5596
5628
  out(disclaimer(t2, c));
5629
+ maybeStarNudge(ctx);
5597
5630
  }
5598
5631
  async function cmdNext(team, ctx) {
5599
5632
  const { cfg, t: t2, now } = ctx;
@@ -5630,6 +5663,7 @@ async function cmdNext(team, ctx) {
5630
5663
  const src = dataSource(source, cfg.lang, c);
5631
5664
  if (src) out(src);
5632
5665
  out(disclaimer(t2, c));
5666
+ maybeStarNudge(ctx);
5633
5667
  }
5634
5668
  async function cmdTable(group, ctx) {
5635
5669
  const { cfg, t: t2 } = ctx;
@@ -5692,6 +5726,7 @@ async function cmdTable(group, ctx) {
5692
5726
  const src = dataSource(source, cfg.lang, c);
5693
5727
  if (src) out(src);
5694
5728
  out(disclaimer(t2, c));
5729
+ maybeStarNudge(ctx);
5695
5730
  }
5696
5731
  var BRACKET_STAGES = /* @__PURE__ */ new Set(["R32", "R16", "QF", "SF", "3P", "F"]);
5697
5732
  async function cmdBracket(stage, opts, ctx) {
@@ -5743,6 +5778,7 @@ async function cmdBracket(stage, opts, ctx) {
5743
5778
  const src = dataSource(source, cfg.lang, c);
5744
5779
  if (src) out(src);
5745
5780
  out(disclaimer(t2, c));
5781
+ maybeStarNudge(ctx);
5746
5782
  }
5747
5783
  function cmdPrompt({ cfg }) {
5748
5784
  try {
@@ -5788,13 +5824,19 @@ function printInitResult(res, cfg) {
5788
5824
  out(`${mark} ${res.message}`);
5789
5825
  }
5790
5826
  function cmdInitStatusline(opts, { cfg }) {
5791
- printInitResult(initStatusline({ print: opts.print, command: opts.command }), cfg);
5827
+ const res = initStatusline({ print: opts.print, command: opts.command });
5828
+ printInitResult(res, cfg);
5829
+ if (!opts.print && res.action === "written") printInitStarCta(cfg);
5792
5830
  }
5793
5831
  function cmdInitHook(opts, { cfg }) {
5794
- printInitResult(initHook({ print: opts.print, command: opts.command }), cfg);
5832
+ const res = initHook({ print: opts.print, command: opts.command });
5833
+ printInitResult(res, cfg);
5834
+ if (!opts.print && res.action === "written") printInitStarCta(cfg);
5795
5835
  }
5796
5836
  function cmdInitCursorStatusline(opts, { cfg }) {
5797
- printInitResult(initCursorStatusline({ print: opts.print, command: opts.command }), cfg);
5837
+ const res = initCursorStatusline({ print: opts.print, command: opts.command });
5838
+ printInitResult(res, cfg);
5839
+ if (!opts.print && res.action === "written") printInitStarCta(cfg);
5798
5840
  }
5799
5841
  var CURSOR_MCP_SNIPPET = `{
5800
5842
  "mcpServers": {
@@ -5819,6 +5861,7 @@ function cmdInitCursor(opts, { cfg }) {
5819
5861
  out("Tip: export CLAUDINHO_CURSOR_META=auto for a model + context line below the score.");
5820
5862
  out("");
5821
5863
  out("\u2192 Restart your agent session to see it.");
5864
+ printInitStarCta(cfg);
5822
5865
  }
5823
5866
  function cmdInitClaude(opts, { cfg }) {
5824
5867
  if (opts.print) {
@@ -5839,6 +5882,7 @@ function cmdInitClaude(opts, { cfg }) {
5839
5882
  out(` ${CLAUDE_MCP_ONELINER}`);
5840
5883
  out("");
5841
5884
  out("\u2192 Restart Claude Code to see it.");
5885
+ printInitStarCta(cfg);
5842
5886
  }
5843
5887
  async function cmdMatch(id, ctx) {
5844
5888
  const { cfg, t: t2 } = ctx;
@@ -5887,6 +5931,7 @@ async function cmdMatch(id, ctx) {
5887
5931
  const src = dataSource(liveSource, cfg.lang, c);
5888
5932
  if (src) out(src);
5889
5933
  out(disclaimer(t2, c));
5934
+ maybeStarNudge(ctx);
5890
5935
  }
5891
5936
  var MARKET_INFO = "Prediction-market data is informational only.";
5892
5937
  function marketDisplayable(match, sig) {
@@ -6311,6 +6356,34 @@ function vibePool(todayLocal, fixtures = allFixtures()) {
6311
6356
  if (todayLocal === last) return [...VIBES, ...VIBES_FINAL];
6312
6357
  return VIBES;
6313
6358
  }
6359
+ function cmdStar(ctx) {
6360
+ const { cfg } = ctx;
6361
+ if (cfg.json) {
6362
+ emitJson({ repo: REPO_URL, hashtag: "#VibingLaVidaLoca" });
6363
+ return;
6364
+ }
6365
+ const c = painterFor(cfg);
6366
+ out();
6367
+ out(" " + c.bold("\u2B50 Star Claudinho on GitHub"));
6368
+ out(" " + c.cyan(REPO_URL));
6369
+ out();
6370
+ out(" " + c.dim("Built for devs & fans \xB7 #VibingLaVidaLoca \u26BD"));
6371
+ out(" " + c.dim("Live World Cup scores in your terminal, Claude Code & Cursor \u2014 no API keys."));
6372
+ out();
6373
+ }
6374
+ function maybeStarNudge(ctx) {
6375
+ if (ctx.cfg.json || !process.stdout.isTTY || process.env.CLAUDINHO_NO_STAR) return;
6376
+ const n = bumpRunCount();
6377
+ if (n === void 0 || !shouldNudge(n)) return;
6378
+ const c = painterFor(ctx.cfg);
6379
+ out();
6380
+ out(c.dim(` \u2B50 Enjoying Claudinho? Star it \u2192 ${REPO_URL} (claudinho star)`));
6381
+ }
6382
+ function printInitStarCta(cfg) {
6383
+ const c = painterFor(cfg);
6384
+ out("");
6385
+ out(c.dim(`\u2B50 If this keeps you in the flow during the match, star the repo \u2192 ${REPO_URL}`));
6386
+ }
6314
6387
  function cmdVibe(ctx) {
6315
6388
  const { cfg } = ctx;
6316
6389
  const pool = vibePool(localDate((ctx.now ?? /* @__PURE__ */ new Date()).toISOString(), cfg.tz));
@@ -6344,7 +6417,7 @@ function handlePipeError(stream) {
6344
6417
  }
6345
6418
  handlePipeError(process.stdout);
6346
6419
  handlePipeError(process.stderr);
6347
- var VERSION = "0.8.12";
6420
+ var VERSION = "0.8.13";
6348
6421
  var DISCLAIMER = "Claudinho is an independent fan project. Not affiliated with or endorsed by FIFA or Anthropic.";
6349
6422
  function ctxFrom(cmd) {
6350
6423
  let root = cmd;
@@ -6468,6 +6541,9 @@ program.command("init-hook").description("wire the live-score hook into Claude C
6468
6541
  program.command("vibe").description("print a matchday-coder one-liner (#VibingLaVidaLoca)").action((_opts, cmd) => {
6469
6542
  cmdVibe(ctxFrom(cmd));
6470
6543
  });
6544
+ program.command("star").description("how to support Claudinho \u2014 star the repo \u2B50").action((_opts, cmd) => {
6545
+ cmdStar(ctxFrom(cmd));
6546
+ });
6471
6547
  var refreshCmd = new Command("_refresh").description("(internal) refresh the statusline cache").action(async (_opts, cmd) => {
6472
6548
  try {
6473
6549
  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.13",
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.13"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "tsup",