Skip to content
Use this GitHub action with your project
Add this Action to an existing workflow or create a new one
View on Marketplace

Repository files navigation

tokenchit

License: MIT npm CI Node GitHub last commit Visitors

Turn your local AI coding agent logs into a stat card you commit to your README.

tokenchit — @iyashjayesh AI coding agent usage

That card is this repository's own, generated by the command below and committed as a file. It is not a screenshot and not a hosted image — which is the whole point.

npx -y @tokenchit/cli@latest generate --no-publish

One command: it finds your agents, shows your stats and writes the card. --no-publish stops it there, and that path has no networking code in it at all — no account, no sign-in, nothing sent. Running it often? npm i -g @tokenchit/cli, then tokenchit generate --no-publish.

  ┃ [1/2]  DETECT AGENTS

  ┃   ● Claude Code   ~/.claude*/projects/**/*.jsonl
  ┃   ○ Codex         not installed
  ┃   ○ OpenCode      not installed
  ┃   ○ Gemini CLI    not installed

✓ wrote .tokenchit.json (1 agents)
  ────────────────────────────────────────────────────────────

  ┃ [2/2]  YOUR STATS, AND THE CARD

  ┃   ~16.7B   $7,392        27d      51
  ┃   TOKENS   EQUIV. COST   STREAK   ACTIVE DAYS
  ┃
  ┃   30d  ▁··▄▆▆▆▅▁▃▅█▃▂▄▂▁▅█▆▂█▇█▅▄█▇▆▃  3.07B in 7d

✓ wrote tokenchit.svg (10144 bytes)

  embed     ![tokenchit — @you AI coding agent usage](./tokenchit.svg)
  commit    git add tokenchit.svg && git commit -m "chore: update tokenchit"
  ────────────────────────────────────────────────────────────

  stopped before publishing — drop --no-publish to join the board

Then, if you want to

npx -y @tokenchit/cli@latest publish

Proves your handle over GitHub's device flow and puts you on the public board, with a hosted card that refreshes without a commit. It sends daily totals by agent and model and your handle — never prompts, replies or file paths — and tokenchit unpublish takes all of it back.

Dropping --no-publish runs the same thing at the end of generate. At a terminal it names the board and waits for a y first; a piped or scripted run has nobody to ask, so it publishes — which is why --no-publish is what this page leads with.

Put it in your README

The card is a file, so the path is relative to the Markdown file you paste this into: ./ beside the card, ../ from a README one directory down. A profile README is its own repository, so the SVG has to be committed to that repo.

![tokenchit — @you AI coding agent usage](./tokenchit.svg)

Why this one

  • The card is a file, not a URL. Comparable tools serve cards from a hosted endpoint, so your README depends on someone else's uptime and rate limits. A committed SVG has none of that — GitHub serves it directly, and it keeps working if this site goes away.
  • Nothing leaves your machine unless you ask. init, sync, recap, ledger and doctor do not import the networking module at all — a test enforces that, rather than a promise. publish holds the only code that uploads; generate ends by calling it, asking first at a terminal but not in a script, which is what --no-publish is for. --dry-run prints the exact bytes.
  • Honest about what it cannot see. Logs get rotated, prices change, and some models have no public price. Where a number is incomplete, the tool says so.
npx -y @tokenchit/cli@latest recap    # the year in review, a second committable SVG

tokenchit recap

What it reads

agent source
Claude Code ~/.claude*/projects/**/*.jsonl — every profile directory, not just the default
Codex ~/.codex/sessions/**/rollout-*.jsonl
OpenCode ~/.local/share/opencode/opencode.db
Gemini CLI ~/.gemini/tmp/*/chats/*.jsonl

Gemini records token counts only in recent versions, so an installation whose recordings all predate that is reported as detected-but-uncountable rather than as a confident zero.

Copilot CLI is detected and reported as unsupported: it records only a live context gauge, never a cumulative total.

Your numbers will not match Claude Code's Stats panel. It counts an API call once per streaming rewrite, so it reads roughly twice as high. sync prints both figures and the gap.

Equivalent cost is not what you paid — it is what these tokens would cost at list API rates. Most agent usage runs under a subscription. See docs/internals.md.

Commands

tokenchit generate       detect agents, write the card, join the board
  --no-publish           stop after the card

tokenchit init           detect agents, write .tokenchit.json
tokenchit sync           read your logs, show your stats, write the card
tokenchit publish        put your row on the public board
tokenchit recap          year in review, as a second committable SVG
tokenchit ledger         show the local history bank, export it, or merge one in
tokenchit schedule       print a cron or launchd entry; installs nothing
tokenchit login          prove your GitHub handle (device flow, no password)
tokenchit logout         forget this machine
tokenchit whoami         who this machine is signed in as

Moving your history between machines

tokenchit ledger --export <file> writes a portable copy — usage only, no credentials, no transcripts, no paths. --import <file> previews merging one in and writes nothing until you add --apply.

The merge is by session, not by day, which is the only way to tell two machines' work apart from one machine's work copied twice: the same session seen twice counts once at its fuller reading, and different sessions add. History banked before this version carries no session identity, so it is kept and reported but never added — nothing in an aggregate distinguishes independent work from a duplicate.

Upgrading migrates the ledger, and going back a version is not safe. An older tokenchit reads the new format as unrecognised, falls back to an empty bank and rewrites it from whatever logs are still on disk. Upgrading itself is lossless and the CLI says so once when it happens. If you might roll back, run tokenchit ledger --export first — that file is one no version of this tool will overwrite.

tokenchit help <command> explains one command. NO_COLOR=1 drops colour and animation. Common flags: --out, --theme auto|light|dark, --layout default|compact, --json, --dry-run.

Keeping the card fresh

The card is a file, which is the point — and a file does not update itself. Re-running generate is the honest answer, but nobody remembers to.

There is an action in this repository for the half a runner can actually do:

# .github/workflows/card.yml
name: card
on:
  schedule: [{ cron: "0 6 * * *" }]
  workflow_dispatch:

jobs:
  refresh:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v7
      - uses: iyashjayesh/tokenchit@v1
        with:
          handle: your-handle

It does not read your logs, and it cannot. An Actions runner has no access to ~/.claude or ~/.codex, so nothing on a runner can regenerate a card from source — you still run publish from the machine that has the logs. What the action does is re-fetch the card you already published and commit it, so the SVG in your README stops drifting away from your real numbers while readers keep loading a committed file rather than an endpoint.

It refuses to overwrite a good card with a bad response: a non-SVG body, a non-200, or the placeholder card the endpoint returns for a handle with nothing on the board. That last one matters — without it a typo in handle commits an empty card on a schedule, silently, forever.

Inputs are handle (required), output, layout, theme, agents, hide, commit and commit-message; layout, theme, agents and hide are the same options the embed endpoint takes. It outputs changed so you can gate later steps on a real update.

One thing to know: GitHub disables scheduled workflows in a repository after 60 days with no activity, and a run that finds an unchanged card makes no commit. On a quiet repository the schedule can switch itself off. workflow_dispatch is there so you can start it again, and the run summary says which happened.

From an agent, not a terminal

There is an MCP server in packages/mcp, so a model can answer questions about your usage instead of you reading a table:

{
  "mcpServers": {
    "tokenchit": { "command": "npx", "args": ["-y", "@tokenchit/mcp@latest"] }
  }
}

Four tools — get_usage, get_daily_usage, get_recap and detect_agents. All of them read the same logs the CLI reads, and none of them can make a network request: the net.isolated test covers packages/mcp/src alongside the CLI, and unlike the CLI this package has no allowlisted module, so every file under it must be clean. Adding a fetch anywhere in it fails the suite.

Two things it does that a plain data dump would not. Every figure ships with its caveat as a sibling field, because a model handed equivCostUsd on its own will report it to you as money you spent — and it is not. And a tool that fails answers with isError rather than a transport error, so "no logs on this machine" reaches the model as something it can relay instead of something it has to guess at.

It reads the ledger and never writes it. sync banks what it saw because you asked it to; a tool call is a question, and a question that mutates state on disk is a surprise you cannot see or undo.

Privacy

sync and recap make no network request at all.

publish holds the only code that uploads. It sends daily token totals per agent, model names, and your handle — never prompts, replies, file paths, branch names, or repository names. --dry-run prints the exact bytes so you can check rather than take our word.

generate ends by calling publish, so it is the one other command that can upload. At a terminal it names the board and waits for a y; a piped or scripted run has nobody to ask and publishes as it always has. generate --no-publish returns before any of that.

Five tests in packages/cli/test/privacy.test.js enforce this on every push, including one that fails if any file outside net.ts can open a socket — across the CLI, the core engine and the MCP server.

The board

Opt-in, and only publish puts you there. Verified rows rank above unverified ones: signing in is the only thing that ties a row to a GitHub account. An unverified row still appears.

Submissions are self-reported, so two bands guard them — rejected for the arithmetically impossible, and held for review for the possible but far outside anything seen. A held row is stored and returned to you, and kept off the board until a person looks. Both thresholds have been raised after real users were refused; the reasoning is in docs/internals.md.

Development

npm install
npm run build     # core, then cli, then site
npm test          # core, cli and site suites
npm run dev       # the site at http://localhost:3000

Node 22 or newer — OpenCode support uses the built-in node:sqlite. The site and the CLI render through the same buildCardSvg(), so they cannot drift.

The CLI, the card and npm test need no database at all — a contributor who only touches those never sets one up. npm run dev does: the site reads DATABASE_URL from apps/site/.env.local and any Postgres will do.

The one worth knowing about is a Neon branch. It is a copy-on-write clone of the board — the same rows, created in about a second, where anything you write stays yours:

neon branches create --name dev          # then point .env.local at its pooled URL

That matters more than it sounds. Without it the obvious thing to do is point local development at the live database, and then every npm run dev, every test publish and every stray query is running against real people's rows. docs/internals.md has the rest.

Supported by

Neon

Neon supports tokenchit through their Open Source programme, and is the Postgres this project documents as its default — a choice docs/research.md §4 reached on its own merits, before the sponsorship existed. Any Postgres works: the site reads one DATABASE_URL and nothing below it knows or cares who serves it.

Licence

MIT.

About

Read your local AI coding agent logs and render a stat card into your repo.

Topics

Resources

Stars

15 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages