Shows AI water usage in your status bar, converted to almonds.

Shows AI water usage in your Claude Code status bar, converted to almonds: D 1🥜 | W 150🥜. A --long flag spells it out: 🥜 Day: 3.5 tsp = 1 almonds · Week: 2.4 gal = 150 almonds.
"Day" and "Week" cover every Claude Code session on this machine, across every project and every account, not just the one the status bar happens to be running in.
Every constant behind the number is cited or flagged as an assumption in SOURCES.md. This is a playful estimate, not a measurement Anthropic or AWS publishes; it exists to make a point about where AI's water use is worst and best, not to be exact.
At a Claude Code prompt:
/plugin install nutprint --marketplace jamesvillarrubia/nutprint
Answer y to add the marketplace, then pick a scope (user is the default). From a shell, the same install is two commands:
claude plugin marketplace add jamesvillarrubia/nutprint
claude plugin install nutprint@nutprint
On a Claude Code build that loads function-hook mods, NutPrint pins its count under the prompt (D 11.3🥜 | W 11.3🥜) by itself, refreshed after every turn. Nothing in settings.json changes, and you can skip the next section. Day is today's total; Week is today plus the six days before it. If your statusLine.command already runs NutPrint from ~/.claude/almonds/, the mod stays quiet so the count does not show twice.
claude plugin update nutprint picks up a new release later; no reinstall, no rebuild. This repo also ships its built dist/ in git for exactly that reason.
Cloning the repo and building it yourself still works (pnpm install && pnpm build, then point Claude Code's plugin flow at the local checkout); the marketplace install above is just the shorter path.
For a Claude Code build without function-hook mods, or if you want the count in the status bar instead, run this inside Claude Code:
/nutprint:setup-statusline
That sets statusLine.command in ~/.claude/settings.json for you. If you already have a statusLine.command, it asks before touching anything: chain NutPrint onto your existing one (wrapping both in a small generated script), or leave it alone and hand you the one line to add yourself. It backs up settings.json before writing to it either way.
Prefer to do it by hand? Add this to ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "node ~/.claude/almonds/statusline.js"
}
}
That fixed path (~/.claude/almonds/statusline.js), not the plugin's own install directory, is deliberate: Claude Code caches an installed plugin under a version-numbered path (~/.claude/plugins/cache/nutprint/nutprint/0.1.0/...), which would move on every claude plugin update. NutPrint's own Stop hook re-copies its statusline script to the fixed path on every session, so statusLine.command never has to change. That also means the fixed path won't exist until you've finished at least one Claude Code session after installing.
Already have a statusLine.command and doing this by hand? Chain them in a small wrapper script instead of replacing it:
#!/usr/bin/env bash
your-existing-statusline-command
printf ' · '
node ~/.claude/almonds/statusline.js
Point statusLine.command at that wrapper script instead.
The default form is compact: D 11.3🥜 | W 11.3🥜. Add --long for the spelled-out form: 🥜 Day: 3.5 tsp = 1 almonds · Week: 2.4 gal = 150 almonds.
Short version: a data center's water draw is blue water (physically withdrawn), so this project compares it to the blue-water share of a California almond's footprint (6.2 L), not the popular blended green+blue+grey figure (~12 L) that most "gallons per almond" claims quote. Full reasoning and every citation: SOURCES.md.
NutPrint measures water in almonds instead of liters because a raw liter count reads as a large, hard-to-place number by itself. A per-almond figure gives a reader a reference point they can check.
Project Rainier, the AWS facility that trains and serves Claude, runs at 0.15 L/kWh on-site. The industry-average facility this project uses as a fallback for other models runs at 1.9 L/kWh. Data centers in hot, evaporative-cooling climates like Arizona have recorded peaks above 9 L/kWh. SOURCES.md cites all three figures.
New AI data centers exist because compute demand already exists. A construction block does not remove that demand. The workload moves to an existing facility, typically one with a worse water footprint than the blocked facility would have had. New construction, sited with real resource planning, can beat the legacy infrastructure it displaces demand onto, on the water axis specifically.
NutPrint takes no position on where a specific new data center should be sited; that decision raises other questions this project does not address. NutPrint also does not claim AI water use is zero-impact. This project has two aims. First, to make the comparison fun: an intuitive number beats an abstract one. Second, to push the wider conversation on AI water usage toward a full accounting, one that weighs every large consumer equally, including the almond farming this project uses as its own comparison point.
MIT, see LICENSE.
dist/mod/register.js 80 lines1// src/config/constants.ts
2var LITERS_PER_ALMOND = 6.2;
3var TSP_PER_LITER = 202.9;
4var TSP_PER_CUP = 48;
5var CUP_THRESHOLD_LITERS = TSP_PER_CUP / TSP_PER_LITER;
6var GAL_THRESHOLD_LITERS = 16 * TSP_PER_CUP / TSP_PER_LITER;
7
8// src/services/format.ts
9function formatAlmonds(n) {
10 if (n < 1) return n.toFixed(1);
11 return n < 10 ? String(Math.round(n)) : n.toFixed(1);
12}
13function formatStatusLineShort(dayLiters, weekLiters) {
14 const dayAlmonds = formatAlmonds(dayLiters / LITERS_PER_ALMOND);
15 const weekAlmonds = formatAlmonds(weekLiters / LITERS_PER_ALMOND);
16 return `D ${dayAlmonds}\u{1F95C} | W ${weekAlmonds}\u{1F95C}`;
17}
18
19// src/services/rollup-totals.ts
20function localDateKey(date) {
21 const yyyy = date.getFullYear();
22 const mm = String(date.getMonth() + 1).padStart(2, "0");
23 const dd = String(date.getDate()).padStart(2, "0");
24 return `${yyyy}-${mm}-${dd}`;
25}
26function parseRollup(content) {
27 const rollup = /* @__PURE__ */ new Map();
28 for (const line of content.split("\n")) {
29 if (line.trim().length === 0) {
30 continue;
31 }
32 try {
33 const parsed = JSON.parse(line);
34 rollup.set(parsed.date, parsed.liters);
35 } catch {
36 continue;
37 }
38 }
39 return rollup;
40}
41function rollupTotals(rollup, now) {
42 const dayLiters = rollup.get(localDateKey(now)) ?? 0;
43 let weekLiters = 0;
44 for (let daysBack = 0; daysBack < 7; daysBack++) {
45 const day = new Date(now.getFullYear(), now.getMonth(), now.getDate() - daysBack);
46 weekLiters += rollup.get(localDateKey(day)) ?? 0;
47 }
48 return { dayLiters, weekLiters };
49}
50
51// src/mod/register.ts
52var ALMONDS_DIR = "/.claude/almonds/";
53async function showStatus($) {
54 const settings = await $.settings.read();
55 if (settings.statusLine?.command?.includes(ALMONDS_DIR)) {
56 return;
57 }
58 const home = await $.env.get("HOME");
59 if (home === void 0) {
60 return;
61 }
62 const text = await $.fs.read(`${home}/.claude/almonds/rollup.jsonl`).catch(() => "");
63 const { dayLiters, weekLiters } = rollupTotals(parseRollup(text), /* @__PURE__ */ new Date());
64 $.ui.status(formatStatusLineShort(dayLiters, weekLiters));
65}
66function register(on) {
67 on("session.start", async ($, e, next) => {
68 await showStatus($);
69 return next(e);
70 });
71 on("classic.Stop", async ($, e, next) => {
72 const result = await next(e);
73 await showStatus($);
74 return result;
75 });
76}
77export {
78 register
79};
80