statuslin.es

Meanwhile

NodeNetwork Access

An occasional, clearly-labeled "(sponsored)" line shows up during the idle wait -- real ads, always disclosed, capped so it's not obnoxious. Half of whatever it earns gets paid to you over PayPal (npx trymeanwhile claim to check). Node, no deps, fails quiet if the server's unreachable.

Updated 2026-09-09

Shows:NodeNetwork Access

0 copies

Preview

Clean repo
πŸ‘‹ meanwhile is here, in case you wanted to know.
New session
Nobody but you and this terminal ever saw this line get made.
Dirty branch
-----------------------🎈--
Near-full
πŸ‘‹ meanwhile is here, in case you wanted to know.
1M context
This one didn't come from our servers. It came from your machine.
Post-compact
πŸ‘‹ meanwhile is here, in case you wanted to know.
Worktree
No round trip for this one -- just you, right now.
Non-git
You've spent $0.41 on this session so far. That number never left your machine.

Source

#!/usr/bin/env node
// Claude Code status line -- Node port of statusline.py. Node is already
// guaranteed present (this is installed via `npx trymeanwhile`, which can't
// run without it), so this removes the separate hard Python dependency that
// used to stop the whole install cold for anyone without python3/python/py
// on PATH. Same server contract, same install ID file, same fail-quiet
// behavior -- existing installs still wired to statusline.py keep working
// unchanged; this is only what new installs get wired to.

const fs = require("fs");
const os = require("os");
const path = require("path");
const crypto = require("crypto");

const SERVER_URL = "https://trymeanwhile.online";
const INSTALL_ID_FILE = path.join(os.homedir(), ".deadtime", "install_id");
const FALLBACK_LINE = "deadtime: agent working...";

function getInstallId() {
  fs.mkdirSync(path.dirname(INSTALL_ID_FILE), { recursive: true });
  if (fs.existsSync(INSTALL_ID_FILE)) {
    return fs.readFileSync(INSTALL_ID_FILE, "utf8").trim();
  }
  const id = crypto.randomUUID();
  // This ID is a real credential -- it's what /register-payout trusts to
  // change where money goes. mode 0o600 so no other local account on a
  // shared machine can read it (no-op on Windows, which has no such model).
  fs.writeFileSync(INSTALL_ID_FILE, id, { mode: 0o600 });
  try { fs.chmodSync(INSTALL_ID_FILE, 0o600); } catch {}
  return id;
}

// Claude Code pipes real session state as JSON on stdin every call -- which
// event fired, plus session cost, token usage, and the working directory.
// A script pinging our endpoint on a timer has none of this; only a
// genuinely running Claude Code session does. Forwarding it lets the server
// tell real activity apart from a faked loop.
//
// cwd is hashed, not sent raw -- enough to detect "did the working
// directory change" without the server ever seeing an actual project path.
function readSessionState() {
  let payload = {};
  try {
    const raw = fs.readFileSync(0, "utf8");
    payload = JSON.parse(raw);
  } catch {}
  const cwd = String((payload.workspace && payload.workspace.current_dir) || "");
  const cwdHash = cwd ? crypto.createHash("sha256").update(cwd, "utf8").digest("hex").slice(0, 16) : "";
  const context = payload.context_window || {};
  const tokens = (Number(context.total_input_tokens) || 0) + (Number(context.total_output_tokens) || 0);
  return {
    event: String(payload.hook_event_name || "unknown"),
    session_id: String(payload.session_id || ""),
    cost: Number((payload.cost && payload.cost.total_cost_usd) || 0),
    tokens,
    cwd_hash: cwdHash,
  };
}

// Some fraction of "tip" slots (never sponsor/bonus/heartbeat/jackpot --
// those have to show the real, billed content) get replaced with a line
// composed entirely here, from signals that never left this machine for
// this purpose. cost/tokens/cwd_hash are still sent to the server, same
// as always, because the server needs them to verify real usage for
// billing -- this is a separate thing: the actual WORDS shown for a tip
// don't require a round trip at all, so some of the time they don't get
// one. No model, no network call, just local composition -- genuinely
// nothing to intercept, because nothing goes out.
const LOCAL_TIP_CHANCE = 0.4;
// Not every single call -- someone actively coding fires this every ~20s,
// so even a low chance still surfaces the reminder several times an hour.
const PAYOUT_REMINDER_CHANCE = 0.15;
const LOCAL_GENERIC_TIPS = [
  "This one didn't come from our servers. It came from your machine.",
  "Nobody but you and this terminal ever saw this line get made.",
  "No round trip for this one -- just you, right now.",
];

// The status line only redraws when a real event fires -- there's no
// animation frame loop to drive smooth motion. But it doesn't need one:
// deriving the emoji's position from the real wall clock instead of a
// stored counter means every refresh lands wherever the "flight" should
// genuinely be at that moment, not wherever the last render left off.
// Two terminals opened seconds apart show the same bird in the same
// place, because it's really just one continuous clock-driven position,
// sampled whenever a redraw happens to occur.
const FLYING_EMOJIS = ["\u{1F426}", "\u{1F680}", "✈️", "\u{1F388}", "\u{1F98B}"];
const RUNWAY_WIDTH = 26;
function generateFlyingEmojiLine() {
  const emoji = FLYING_EMOJIS[Math.floor(Date.now() / 4000) % FLYING_EMOJIS.length];
  const period = 2 * (RUNWAY_WIDTH - 1);
  const t = Math.floor(Date.now() / 250) % period;
  const pos = t <= RUNWAY_WIDTH - 1 ? t : period - t;
  return "-".repeat(pos) + emoji + "-".repeat(RUNWAY_WIDTH - 1 - pos);
}

function generateLocalTip(session) {
  const hour = new Date().getHours();
  const candidates = [generateFlyingEmojiLine()];
  if (session.cost > 0.01) {
    candidates.push(`You've spent $${session.cost.toFixed(2)} on this session so far. That number never left your machine.`);
  }
  if (session.tokens > 50000) {
    candidates.push(`${Math.round(session.tokens / 1000)}k tokens deep this session. Still here.`);
  }
  if (hour >= 0 && hour < 5) {
    candidates.push(`It's past midnight where you are. The agent doesn't know that. You do.`);
  } else if (hour >= 5 && hour < 9) {
    candidates.push(`Early. Whatever you're building, you started before most people were up.`);
  }
  candidates.push(...LOCAL_GENERIC_TIPS);
  return candidates[Math.floor(Math.random() * candidates.length)];
}

async function fetchLine(installId, session) {
  const params = new URLSearchParams({
    id: installId,
    event: session.event,
    sid: session.session_id,
    cost: String(session.cost),
    tok: String(session.tokens),
    cwd: session.cwd_hash,
  });
  const url = `${SERVER_URL}/line?${params}`;
  try {
    // 6s: a cold Node start plus DNS/TLS can eat close to a second before
    // the request even goes out. A short timeout here just means falling
    // back to filler more often than necessary, never a hung terminal.
    const res = await fetch(url, {
      headers: { "User-Agent": "deadtime-client/1.0" },
      signal: AbortSignal.timeout(6000),
    });
    if (!res.ok) return FALLBACK_LINE;
    const data = await res.json();
    // Real money sitting uncollected because someone skipped payout setup
    // and never came back -- surfaced occasionally, not every call, so
    // it's a nudge instead of drowning out every real line with a nag.
    // Never override a sponsor line -- the server already billed the
    // advertiser for this exact impression (deliverImpression runs before
    // this response is even sent), so swapping the text here would mean
    // charging a real advertiser for something that was never shown.
    if (data.kind !== "sponsor" && data.needs_payout && Math.random() < PAYOUT_REMINDER_CHANCE) {
      return "meanwhile: you're earning but haven't added a payout email -- npx trymeanwhile claim";
    }
    if (data.kind === "tip" && Math.random() < LOCAL_TIP_CHANCE) {
      return generateLocalTip(session);
    }
    return data.line;
  } catch {
    // server unreachable -- fail quiet and cheap, never break the terminal
    return FALLBACK_LINE;
  }
}

async function fetchEarnings(installId) {
  const url = `${SERVER_URL}/earnings?id=${installId}`;
  try {
    const res = await fetch(url, {
      headers: { "User-Agent": "deadtime-client/1.0" },
      signal: AbortSignal.timeout(5000),
    });
    if (!res.ok) return null;
    return await res.json();
  } catch {
    return null;
  }
}

async function printClaimInfo() {
  const installId = getInstallId();
  const earnings = await fetchEarnings(installId);
  const claimUrl = `${SERVER_URL}/claim?id=${installId}`;

  console.log("meanwhile -- your account");
  console.log(`  ID:      ${installId}`);
  if (earnings) {
    console.log(`  earned:  $${Number(earnings.user_earnings).toFixed(2)}`);
    console.log(`  shown:   ${earnings.total_calls} lines (${earnings.sponsor_calls} sponsored)`);
  } else {
    console.log("  earned:  (couldn't reach server -- check your connection)");
  }
  console.log();
  console.log(`  register a payout email: ${claimUrl}`);
}

async function main() {
  if (process.argv[2] === "--claim") {
    await printClaimInfo();
    return;
  }
  // Everything above only ever fails quiet and returns a fallback value --
  // but getInstallId() touches the filesystem (permission denied, read-only
  // home dir, disk full are all real possibilities out in the world), and
  // that must never take the terminal down with it. The site's own promise
  // is "never blocks or breaks your terminal" -- this makes that true.
  try {
    const session = readSessionState();
    const installId = getInstallId();
    console.log(await fetchLine(installId, session));
  } catch {
    console.log(FALLBACK_LINE);
  }
}

main();

What it shows

  • A single line of text from trymeanwhile.online
  • Local stand-in tips on some redraws, including a dashed runway with a moving emoji
  • A session-cost sentence when a local tip reads the cost Claude Code put on stdin
  • A sponsored line when the server marks the response as kind sponsor

Requirements

  • Node.js, with no extra npm packages
  • Network access to trymeanwhile.online
  • A writable home directory so it can create ~/.deadtime/install_id

Behavior notes

  • These gallery previews are one snapshot, not a catalog of every line. Three printed meanwhile is here, one a balloon runway, one a $0.41 cost sentence, and the rest local privacy tips. None said sponsored.
  • The next redraw can differ. The server picks a line, then a random roll may replace a tip with text composed on the machine.
  • Git branch and dirty state never appear.
  • If you run the file with --claim it prints earnings and a payout URL instead of a status line. Claude Code does not pass that flag.

More status lines

πŸ€– Opus 4.8 [200K] β”‚ 🎯 high β”‚ πŸ“ app β”‚ 🌿 main πŸ“… 07/07/26 Tue β”‚ ⏰ 23:06:08 β”‚ β‚Ώ$64k β”‚ β˜€οΈ +90Β°F Ctx: ● β–“β–“β–‘β–‘β–‘β–‘ 33% [66K/200K] β”‚ ⏱ 10m β”‚ πŸ”§ 2.1.155
Opus Β· F:15% β–ˆβ–ˆβ– ▏ A:7% β–ˆβ– ▏ ⟳ 3d

Todo Ticker

0 copies

app β”‚ Opus 4.8 β”‚ context: β–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 22% β”‚ Fixing the null check in the parser
Opus 4.8 β”‚ main β”‚ app β”‚ β–“β–“β–‘β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 44k/200k β”‚ $0.41

Activity Feed

45 copies

Opus 4.8 high ✦ β”‚ ctx 22% β”‚ app (main) β”‚ PR #1287 ● β”‚ $0.41 Β· 10m +128 -34 Session ● ● ● ● ● ● ● ● β—‹ β—‹ β—‹ 74% left Resets in 2h 6m Weekly ● ● ● ● ● ● ● ● ● ● β—‹ 93% left Resets in 2d 0h Fable ● ● ● ● ● ● ● ● ● β—‹ β—‹ 85% left Resets in 2d 0h ◐ Read:index.ts βœ“ ReadΓ—1 βœ“ BashΓ—1 βœ“ EditΓ—1 βœ“ GrepΓ—1 ◐ code-reviewer Review the change for regressions
β—† Opus 4.8 β”‚ β–ͺ app β”‚ βŽ‡ main β”‚ β—· 16:20 β”‚ β–ˆβ–ˆβ–„β–‘β–‘β–‘β–‘β–‘β–‘β–‘ 26% (44K/167K) β”‚ 26% ↻2h6m β”‚ 7% ⟳2d0h