Rough CO2 / water / energy estimate of the session from token usage, with everyday comparisons.

Claude Code mod showing a rough CO₂ / water / energy estimate of the current session, from token usage, with comparisons (car metres, phone charges, glasses of water).


/plugin install eco-status --marketplace sevas/eco-status
Answer y to add the marketplace, then pick a scope.
Same per-token factors as claude-carbon (not a fork; this adds water and runs as a mod): Jegham et al. 2025, gCO₂e per million tokens, input/output: Sonnet 39/826, Opus 78/1652, Haiku 20/413, Fable 156/3304; cache reads at 8% of an input token; 0.287 g CO₂/Wh, PUE 1.14. Water is derived from the WUE in the same methodology (0.18 + 5.11 L/kWh). Comparisons use claude-carbon's "world" set (200 g/km car, 8.7 g per phone charge) plus a 250 mL glass.
Run /eco for a table of the last 30 days and monthly totals (history is kept ~13 months in the mod's own store).
Order-of-magnitude only: see claude-carbon's METHODOLOGY.md for caveats (the source is a preprint and may read high). The counter resets when the session restarts.
hooks/register.ts 75 lines1import type { Register } from 'claude-code'
2
3// Factors from claude-carbon (data/factors.json, METHODOLOGY.md): Jegham et al. 2025,
4// gCO2e per million tokens, location-based (AWS CIF 0.287 g/Wh, PUE 1.14 included).
5const G_PER_MTOK: Record<string, [number, number]> = {
6 fable: [156, 3304],
7 opus: [78, 1652],
8 sonnet: [39, 826],
9 haiku: [20, 413],
10}
11const CACHE_READ = 0.08 // cache-read token = 8% of an uncached input token
12const G_CO2_PER_WH = 0.287
13const ML_PER_WH = 0.18 + 5.11 // WUE on-site + off-site, L/kWh == mL/Wh
14// "world" equivalences from claude-carbon, plus a glass of water
15const G_PER_KM = 200, G_PER_CHARGE = 8.7, ML_GLASS = 250
16
17const family = (m: string) => /fable|mythos/.test(m) ? 'fable' : /opus/.test(m) ? 'opus' : /haiku/.test(m) ? 'haiku' : 'sonnet'
18const f = (n: number) => (n < 10 ? n.toFixed(2) : n.toFixed(0))
19const day = (ms: number) => { const d = new Date(ms); return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` }
20
21const metres = (m: number) => (m < 1000 ? `${f(m)} m` : `${(m / 1000).toFixed(1)} km`)
22// everyday comparisons for g CO2 (water derived from it)
23const eq = (g: number) => {
24 const ml = (g / G_CO2_PER_WH) * ML_PER_WH
25 return `🚗 ${metres(g / G_PER_KM * 1000)} · 📱 ${(g / G_PER_CHARGE).toFixed(1)} charges · 🥛 ${(ml / ML_GLASS).toFixed(1)} glasses`
26}
27const row = (label: string, g: number) => {
28 const wh = g / G_CO2_PER_WH
29 return `${label.padEnd(10)} ${f(g).padStart(7)} g ${f(wh * ML_PER_WH).padStart(7)} mL ${f(wh).padStart(6)} Wh ≈ ${eq(g)}`
30}
31
32export const register: Register = on => {
33 on('session.start', async ($, e, next) => {
34 await $.command.register({ name: 'eco', description: 'Daily and monthly CO₂ / water / energy history' })
35 return next(e)
36 })
37
38 on('command.run', { command: 'eco' }, async $ => {
39 const days = ((await $.store.get('days')) ?? {}) as Record<string, number>
40 const keys = Object.keys(days).sort()
41 if (!keys.length) return { text: 'No history yet: it fills in after your first completed turn.' }
42 const months: Record<string, number> = {}
43 for (const k of keys) months[k.slice(0, 7)] = (months[k.slice(0, 7)] ?? 0) + days[k]
44 const lines = [
45 'Last 30 days (estimates, see README):',
46 ...keys.slice(-30).map(k => row(k, days[k])),
47 '',
48 'By month:',
49 ...Object.keys(months).sort().slice(-12).map(k => row(k, months[k])),
50 ]
51 return { text: lines.join('\n') }
52 })
53
54 let co2 = 0 // session total; resets on reload/restart
55 on('turn.complete', async ($, e, next) => {
56 const r = await next(e)
57 const u = r.usage
58 if (u && u.model.includes('claude')) {
59 const [i, o] = G_PER_MTOK[family(u.model)]
60 const g = ((u.input_tokens + u.cache_creation_input_tokens + u.cache_read_input_tokens * CACHE_READ) * i + u.output_tokens * o) / 1e6
61 co2 += g
62 // daily history in $.store (key "days": { "YYYY-MM-DD": gCO2 }), kept ~13 months
63 const today = day(await $.clock.now())
64 const days = ((await $.store.get('days')) ?? {}) as Record<string, number>
65 days[today] = (days[today] ?? 0) + g
66 for (const k of Object.keys(days).sort().slice(0, -400)) delete days[k]
67 await $.store.set('days', days)
68
69 const wh = co2 / G_CO2_PER_WH, ml = wh * ML_PER_WH
70 $.ui.status(`🌍 ~${f(co2)} g CO₂ · 💧 ~${f(ml)} mL · ⚡ ${f(wh)} Wh (≈ ${eq(co2)})`)
71 }
72 return r
73 })
74}
75