Your Claude Code TODOs, worked while you're not watching.
Work around the clock to squeeze the latest drop of juice from Claude Code. Most of your 5-hour window goes idle once you step away — squeezer keeps working through it instead of letting that paid-for capacity sit unused. A background daemon that works through your project TODOs while you're away or rate-limited, texts you over Telegram, escalates only the in-doubt calls, and survives 5-hour usage resets automatically — with a fully autonomous mode so your nights stay yours.
It always keeps a configurable slice of the window free so you can grab manual control the moment something looks off, and an optional human-in-loop mode hands control back to you at the start of every fresh window (or once a day) instead of running fully unattended.
There's no tmux session and no interactive pane to babysit: the daemon
spawns a fresh headless claude -p --resume <session-id> turn whenever
there's work to do, resuming the same ongoing conversation each time, and
rides out Pro-plan rate-limit resets by simply waiting for the next window.
- Budget-aware, not just time-aware — it sums real token usage from the session transcript and enforces a reserve in code, not by asking the model nicely.
- Talks to you, doesn't just log — Telegram summaries and escalations, not a file you forget to check.
- Survives rate-limit resets — no long-lived process to babysit through a 5-hour window; it resumes the same conversation on the next run.
- Multi-project by default — one daemon, one TODO backlog per registered repo, prioritized across all of them.
- Safe by construction — git is the undo button (no repo without commit history gets registered), and this repo ships with zero private data: see below.
# 1. Add this repo as a plugin marketplace source, then install it, then:
/squeezer:setup
That one command walks you through registering your projects, setting up
the Telegram bot, and installing the background daemon as an OS service
(launchd on macOS, systemd --user on Linux). It's idempotent — safe to
re-run any time. Full step-by-step details are in Setup below.
This repo is generic on purpose: no private project names, paths, or
secrets are committed. Everything project-specific lives in your local
SQUEEZER_HOME (default ~/.config/squeezer) — entirely separate from
wherever this plugin package itself is installed, so upgrading the plugin
never touches your registered projects, secrets, or state.
daemon/daemon.pyis the only long-running process — installed as a launchd (macOS) or systemd--user(Linux) service by/squeezer:setup, so it survives reboots and crashes on its own. It long-polls Telegram, paces continuation turns against the token budget, and (in human-in-loop mode) asks what to work on next.- Each turn is a headless
claude -p --resume <session-id>process,--add-dir'd into every project inconfig.json, run withcwdset toSQUEEZER_HOMEso it picks upCLAUDE.md/ESCALATION_POLICY.md/ROUTINE.md/todos/from there. - The reserve is skipped (treated as 0%) during
no_reserve_hoursinconfig.json— hours when no one needs it free to grab manual control, default02:00–07:00local time. Set it tonullto disable. hooks/budget_guard.shsums real token usage from the session's own transcript and blocks further tool calls once the configured reserve is breached — enforced in code, not by asking the model nicely.- The model calls the
telegram_sendMCP tool to proactively notify you or escalate, perESCALATION_POLICY.md.
See templates/CLAUDE.md.template (copied to SQUEEZER_HOME/CLAUDE.md on
setup) for the full operating policy a running instance follows.
SQUEEZER_HOME/state/worklog.md is the only durable record of why the
orchestrator did what it did — which task it picked and on what grounds,
what it escalated, what you replied over Telegram, what it deliberately
chose not to do. After a few weeks that history is only answerable by
opening a large file and reading it. The /why feature asks it a question
in plain English and gets back the decision, the reasoning behind it, and
the ## <date> heading it came from.
From the command line:
SQUEEZER_HOME=retrieval-demo python3 daemon/worklog_query.py \
"why does elevation use a --settings overlay instead of --dangerously-skip-permissions?"
runs against the placeholder worklog shipped in retrieval-demo/
(never your real SQUEEZER_HOME — see its own README) and answers something
like:
Elevation rejected `--dangerously-skip-permissions`: unscoped, bypasses tool
sandboxing entirely — could read `~/.ssh/id_rsa` or overwrite `~/.zshrc`.
Chose `--settings` overlay with `autoMode.allow` instead: scoped to lifting
`soft_deny`-class actions only, never touches `~/.claude/settings.json`, no
leak into human's interactive sessions. `hard_deny` stays untouched
regardless — permanent floor, no runtime override.
Citation: `## 2026-09-04 — Telegram TOTP elevation: why 2FA, and why this
design specifically`
Against a real install, just drop SQUEEZER_HOME=retrieval-demo and ask
about your own history. From Telegram, /why <question> does the same
thing but is answered instantly — handled inline next to /pause and
/resume, never queued behind a worker turn.
Limitations, stated plainly:
claude -pruns with all tools disabled (--tools "") on purpose. Found the hard way: with tools enabled, a query about squeezer's own design once answered from a spec file on disk instead of the worklog actually in the prompt — correct-sounding, uncited, and silently wrong source. Disabling tools makes the worklog the only thing it can see.- Answer quality is unmeasured and untested. There's no labelled question set and no accuracy check — nothing verifies the model read the log correctly, so a confidently wrong citation is possible.
- Cost scales with the worklog, not the question. Every query sends the
entire file to
claude -p; a one-line question costs roughly as many input tokens as the log is long, and that only grows over time. - It can only surface reasoning that was actually written down. If a turn logged what it did without logging why, no query recovers the reasoning that was never recorded.
- Very long worklogs answer only about recent history. Above
MAX_WORKLOG_CHARS(400,000 characters) the log is truncated to its most recent portion before being sent, so questions about older decisions stop being answerable. The answer says when this happened rather than quietly replying from a partial record. How long that takes depends entirely on how much the instance writes — the worklog this was measured against grew ~3.4KB/day, which is roughly a year; a busier one gets there far sooner. - Only tested on macOS.
The first design for this was a real retrieval pipeline: an entry parser, inverse-document-frequency-weighted term scoring, a decision-marker boost, a recency tiebreak, and token-budgeted selection with a minimum-entry floor.
It was all cut before any code was written, after measuring the corpus — about 13.8k tokens against a 200k-token context window. The whole worklog fits in one prompt, so every one of those components could only make recall worse than sending everything, in exchange for a token saving nobody needs on a hand-triggered query. What shipped instead reads the file and asks the question, and recall is 100% by construction because nothing selects between entries.
That cut is the main trade-off in this feature, and it is what the
truncation and cost limitations above are the price of. The reasoning, the
measurement, and the two thresholds that would justify building the ranker
after all are recorded in
planning/.
Set "mode": "human_in_loop" in config.json, or send /manual to the bot
anytime (/auto switches back). In this mode, whenever a fresh 5-hour budget
window opens — or, with "human_in_loop": {"ask_cadence": "daily"}, once a
day right at no_reserve_hours.start — the daemon messages you the top open
TODO items and waits for a reply before doing anything else. You can reply
with a number, describe any other task, or name a brand-new project path to
register; you can also cap that session's spend (e.g. "cap it at 40%"), which
the daemon enforces as a hard stop. You're never asked or blocked during
no_reserve_hours — the daemon just runs fully automatically through the
night either way.
Add this repo as a plugin marketplace source and install it, then run:
/squeezer:setup
This walks you through registering your projects, setting up the Telegram bot, and installing the background daemon as an OS service. It's idempotent — safe to re-run any time (e.g. after adding a project).
/squeezer:setup seeds SQUEEZER_HOME/config.json from
templates/config.example.json. Replace the placeholder entries with your
own:
{
"name": "example-project-1",
"path": "/absolute/path/to/example-project-1",
"notes": "what this project is / any constraints the agent should know"
}Each project needs git — if one doesn't have it yet, run git init in it
first (no remote required). This gives the agent an undo mechanism before
it's allowed to touch that project; /squeezer:setup's validation step
refuses to register a project with no git history until you do this.
Use templates/TODO.md.example as the format reference — a structured
checklist, not freeform notes. If a project already has its own informal
TODO.md at its repo root, leave it alone: the agent treats that as
read-only reference material, never as a task source.
- Message @BotFather on Telegram, run
/newbot, and save the token it gives you. - Send your new bot any message (e.g. "hi") so it has something to read.
/squeezer:setupfetchesgetUpdatesand prints your numericchat_idanduser_idso only you can drive the bot. Every inbound message is checked against both — not just the chat, but the actual sender — so it stays locked to you even if the bot is ever added to a group.
Nothing in this repo needs to change — it's generic plugin code. Install the
plugin on the new machine and run /squeezer:setup again with that
machine's real projects; SQUEEZER_HOME is where all machine/user-specific
state lives.
/plugin uninstall removes squeezer from Claude Code's plugin list, but it
doesn't know squeezer also registered a background OS service and edited
your global ~/.claude/settings.json statusLine during /squeezer:setup.
Run /squeezer:uninstall (before or after) to tear those down — it stops
the daemon service and strips just squeezer's own line from the statusLine,
leaving any other chained statusline command (e.g. claude-hud) intact. It
never touches SQUEEZER_HOME — your registered projects, TODOs, worklog,
and Telegram credentials are yours to remove by hand if you actually want
them gone.
See templates/ESCALATION_POLICY.md.template (copied to
SQUEEZER_HOME/ESCALATION_POLICY.md on setup) for what the agent handles
autonomously vs. what it escalates to you over Telegram.
Send /elevate <6-digit code> <hours> (hours: 2, 4, 8, or 24) to temporarily
widen what the daemon can do unattended — it layers a scoped authorization
onto the next turn so auto-mode's classifier can cross soft_deny-class
restrictions with your explicit consent. hard_deny and every credential/
sandbox protection stay completely untouched, no matter what. /lockdown
ends an active elevation immediately. Run /squeezer:2fa-setup once to
enroll a TOTP secret (Google Authenticator or any compatible app) before
using either command.
Issues and PRs welcome — especially reports of escalation-policy edge cases or daemon reliability bugs. If squeezer is saving you a rate-limit window's worth of babysitting, a star on the repo is the easiest way to help other Claude Code users find it.