awewarm: Subscription Window Warmer 
Keep AI coding-plan windows warm with one minimal request.
Connect once; awewarm detects what your Claude Code / Codex account or subscription endpoint can do, then makes sure the next usage window is always already open.
English · 简体中文
Real case: you start work at 9:00 and normally get only one 5-hour quota for the morning. With awewarm warming at 05:30, the window refreshes again at 10:30 — twice the morning quota, without you touching anything.
awewarm manages two kinds of connections:
- Account — your local
claude/codexCLI logins. awewarm reuses their login state and sends one minimal headless request (Reply with exactly: ok). No credentials are stored. - Subscription plan — any OpenAI Chat / OpenAI Responses / Anthropic-compatible endpoint with a base URL + API key. The key is stored in
~/.config/awewarm/secrets.json(chmod 600) so the background scheduler can always read it.
It schedules those requests in two modes — fixed and interval — explained in Scheduling Modes below. Interval-style renewal stays locked until the window semantics are verified or user-confirmed; fixed is always safe.
Quick Start
Requires Python ≥ 3.9:
1. Install and use awewarm
If you are working in Claude Code, Codex, Cursor, or another coding agent, tell it:
Read https://github.com/wehuman01/awewarm/blob/main/README.ai.md and follow it to install and configure awewarm.
The agent installs the CLI, scans your local accounts read-only, and tells you what it found. It can then explain status, adjust schedules, pause connections, and diagnose configuration in natural language.
Manual install
pip3 install awewarm
awewarm -v
2. Finish the first setup in your terminal
The first setup is intentionally interactive: awewarm init asks which detected accounts to manage, chooses a schedule, sends one test request per connection, and installs the background scheduler. An agent must not run it for you. In your own terminal, run:
awewarm init
For a subscription endpoint instead of a local Claude Code or Codex account, run this in your terminal:
awewarm config add
It asks for the protocol, API base URL, API key, and model, tests the endpoint with one minimal request, then stores the key in ~/.config/awewarm/secrets.json (0600). The same command can re-add a local account you removed earlier.
awewarm init and config add both make real test requests. They consume quota, so the agent may guide you but will not trigger them silently. The scheduler installs on macOS (launchd), Windows (Task Scheduler), and Linux (systemd user timer); on headless Linux/SSH accounts run loginctl enable-linger $USER first, and where systemd is unavailable, cron the tick: * * * * * awewarm tick.
3. Manage warm-ups through natural language
Once the terminal setup finishes, awewarm is ready to use. Ask your agent things like:
Check the next warm-up
When is the next warm-up for claude-code, and is the scheduler healthy?
Equivalent CLI commands
awewarm status
awewarm status claude-code
awewarm status --json
Change a schedule
Set my GLM plan to warm at 06:35, 11:40, and 16:45 on weekdays.
Equivalent CLI commands
awewarm config set glm --times 06:35,11:40,16:45 --days weekday
awewarm status glm
Pause or resume a connection
Pause all warm-ups while I'm on vacation, then tell me how to resume them.
Equivalent CLI commands
awewarm config set glm --off
awewarm config set glm --on
awewarm status glm
Diagnose a problem
Check why my awewarm connection is not warming and fix the first safe configuration problem.
The agent can inspect discover, status, and the redacted config paths, then suggest or apply schedule changes. It will not run awewarm run unless you explicitly ask, because that sends a real request and consumes plan quota.
Beyond the basics: scheduling modes, the health ladder, sleep/wake behavior, and remote-server delegation are covered in the reference sections below — start with Scheduling Modes.
Companion Tools
awewarm is part of a small tool family for AI coding agents:
- awewarm-hub — the multi-tenant companion server: one always-on box keeps a whole team's windows warm through one-time invites. Same org, same MPL-2.0; its engine is this package, pinned to its minor version.
- aweswitch — agent profile switcher for Claude Code, Codex, and OpenCode. aweswitch manages which provider a session launches with; awewarm keeps that provider's subscription window open underneath.
- aweskill — CLI skill package manager for AI agents (47+ agents).
- aweshelf — session bookmark manager for Claude Code and Codex.
- awerouter — smart LLM router: flash/pro split by structural signals.
Scheduling Modes
Both modes send the same one minimal request — what differs is when it fires. Switch with awewarm config set <id> --mode fixed|interval; see the current mode and next due moment with awewarm status. The old hybrid mode was removed — a fixed grid spaced one window apart already keeps windows chained all day, with calendar wake coverage interval cannot offer.
| Mode | Fires when | Needs a verified window | Best for |
|---|---|---|---|
fixed |
fixed times each day | no | predictable hours; unverified plans; sleeping Macs |
interval |
window + grace after each success | yes | 24/7 warmth on an always-on machine |
fixed — absolute times, always safe
One request at each fixed local time (weekday or every-day); each hit opens a fresh window.
- If the machine was asleep at the slot time, the slot still fires late within the catch-up window (default 30 min); past that it is recorded as skipped.
- A slot landing within 30 min of a previous success is skipped — never pay for two windows at once.
--next HH:MM --move-slotis a one-shot slot move: it names the next pending fixed slot and fires that slot at the new moment (today, or tomorrow if the time has passed) — the times list itself is untouched, and the original slot is marked completed on its own day. A special case of--next(same state pin). Works on delegated connections too.- The only mode that works while window semantics are unknown, which is why unverified plans start here.
- During setup, when the window duration is known, awewarm asks for the plan's daily quota reset time and offers a full-day grid anchored on it — one slot per window, spaced window + 5 min apart (e.g. reset 01:14 + a 5 h window → 01:14, 06:19, 11:24, 16:29, 21:34). Declining keeps just the time you entered. Plans added in fixed mode are asked for the window duration first (default 300) — it spaces the grid and is recorded as a user-confirmed window that unlocks interval mode.
awewarm config set claude-code --times 06:35,11:40,16:45 # 5 h + 5 min apart: windows chain across a workday
awewarm config set claude-code --mode fixed
Example — a laptop that sleeps at night: slots at 06:35 / 11:40 / 16:45 keep a window open from 06:35 to ~21:45 every weekday. The machine only needs to be awake within 30 min of each slot.
interval — rolling renewal
After each success the next request is scheduled window + grace later (default 300 min + 75 s, plus up to 30 s jitter). The grace runs after the old window has closed — firing earlier would land inside the old window and start nothing. With no success recorded yet, one request fires immediately as the first anchor — unless you move that start with --next HH:MM: the pin fires once at that moment (today, or tomorrow if it has passed), and the chain continues from that success. Moving fixed slots works with the same pin (--next T --move-slot, see above).
awewarm run my-plan # 1. one minimal request, timestamped
# ...watch when the plan's quota resets, note the elapsed minutes...
awewarm config set my-plan --window 300 # 2. record the window (unlocks interval)
awewarm config set my-plan --mode interval # 3. rolling renewal
A manual run <id> never shifts the renewal chain — the next due moment stays as scheduled. Add --reset-due to restart the chain from this run instead.
Example — an always-on machine you want warm around the clock, nights and weekends included: no wake machinery needed, renewal just keeps rolling.
Temporarily move the next fire
One state pin (nextOverrideAt, optional nextOverrideSlot) backs both forms. Setting a new one replaces the old; the first success clears the pin. Both work on delegated connections (POST /v1/connections/<id>/override — state only, never a connection re-push).
--next HH:MM— pin the next fire: fire once at that moment (asoverride, not as a named slot), then resume the normal schedule. Can be earlier or later than the next slot. On interval the chain renews from that fire — a fresh connection's first anchor can be deferred this way too.--next HH:MM --move-slot— the slot-move special case: names the next pending fixed slot and fires that slot at T (slot identity preserved; times list untouched; slot marked completed on its own day). Interval has no slot to move — plain--nextalready renews the chain from the fire.--clear-next— drop the pin.
awewarm config set glm-remote --next 16:05 --move-slot # today's 16:00 slot fires at 16:05
awewarm config set glm-remote --next 14:38 # one-shot warm at 14:38; 16:00 still later
awewarm config set glm-remote --clear-next # drop the pin
awewarm status glm-remote # shows "Pinned: …" while armed
Quick Templates
Common scheduling patterns to get started:
# Standard workday (morning + afternoon)
awewarm config set <id> --times 06:00,11:05,16:10
# With evening overtime
awewarm config set <id> --times 06:00,11:05,16:10,21:15
# Weekday only
awewarm config set <id> --times 08:00,13:05 --days weekday
# Interval (verified 5h window)
awewarm config set <id> --mode interval --window 300 --anchor 11:05
All fixed-time slots above are 5 h 5 min apart — the subscription window (5 h) plus a 5 min buffer for scheduling imprecision. Each slot fires once and opens a fresh window. With four slots (06:00, 11:05, 16:10, 21:15) you get coverage across the whole day: morning, afternoon, evening, and late night for overtime. Three slots (06:00, 11:05, 16:10) cover a standard workday. --days weekday limits fires to weekdays. Two-slot chains work well for half-day or intermittent use.
When requests fail — the health ladder
Both modes share one ladder: connected → failing → degraded → auto-disabled.
connected ──first failure──▶ failing ──N consecutive lost──▶ degraded ──N more lost──▶ auto-disabled
▲ │ │ │
└──────── any success (node/catch-up/manual run) ──────┘ │
│
└────── --on / run ──┘
- A failed node (a fixed slot, or an interval renewal moment) enters failing and gets catch-up retries — by default 5 attempts within 30 minutes, spaced ~5 minutes apart (defaults via
awewarm config settings; one connection via--catchup-attempts/--catchup-minutes). - 3 consecutive lost nodes (default 3, via
awewarm config settings --degrade-after-nodesor a per-connection--degrade-after-nodes) drop the connection to degraded: single shot per node, no more catch-up. interval probes once per window; fixed fires each slot exactly once. - The same count again while degraded stops it entirely: auto-disabled, silent until you resume with
awewarm config set <id> --on(or a manualrun <id>that succeeds). - Any success — node attempt, catch-up retry, manual run — resets the whole ladder. Manual attempts never count as nodes, and a slot the machine slept through (zero attempts) is not a lost node.
statusshows the rung plus details (Health: failing — 1/3 nodes lost, catch-up attempt 2/5), and prints the last failure with its error right under the last activation.
Sleeping Macs — calendar fire + RTC wakes (macOS)
Two layers, honestly split by what each can do:
-
Calendar fire (default, no sudo).
scheduler installwrites oneStartCalendarIntervalentry per fixed slot into the launchd agent. Those entries run the tick at the exact slot time whenever the machine is awake — launchd does not wake a sleeping Mac; a job whose slot passes during sleep fires, coalesced, at whatever wake happens next (system maintenance dark wakes make this minutes, not hours). Entries fire every day regardless of the slot's day rule: the tick itself decides whether today is an active day, so a weekend entry for a weekday-only slot is a harmless no-op. Editing times/mode updates the entries immediately; the first tick after any edit heals drift automatically. -
RTC wakes (opt-in, one sudo).
awewarm scheduler install --wakearms real wake-from-sleep events (pmset schedule wakeorpoweron) for every moment the schedules need the machine — all fixed slots for today and tomorrow, plus each interval connection's next renewal, including renewals that drift: every minute, the tail of the tick recomputes the needed moments and converges the armed events to them, so a renewal chain that shifts (late wake, manual--reset-due) is followed automatically. A one-line sudoers grant (/etc/sudoers.d/awewarm, scoped to arming/cancelling wake events only — nothing else) lets the unattended tick do this without prompts; the machine wakes into a screen-off dark wake, the tick fires within seconds, and it sleeps again. Non-activation sleep is untouched — there is no prevent-sleep assertion anywhere.Coverage boundary: RTC wakes are reliable while the Mac sleeps normally (lid closed on power, and lid-closed on battery before standby kicks in). After hours on battery the Mac enters standby (RAM powered off) and Apple will not wake it on schedule — for that regime, delegate to an always-on server (below).
awewarm statusshows the layer's state, andscheduler uninstallcancels every armed event and removes the grant.
Per connection, wakeWhenAsleep: false opts out of both layers (asked during setup; change later with awewarm config set <id> --no-wake). Missed slots still fire late within the catch-up window once the machine wakes. A fully shut down Mac stays off — power it on and the first tick catches up anything still inside the catch-up window.
Sleeping PCs — wake tasks (Windows)
The calendar-fire/wake split, mirrored: scheduler install registers one extra Task Scheduler task per fixed slot — a daily trigger at the slot time with Wake to run enabled, running awewarm tick. Interval renewals get their wake coverage through one-shot -Once tasks armed by the same tick-tail convergence as on macOS (no grant needed — users may register wake tasks). The per-minute tick task itself never wakes the machine (a waking tick would keep it from ever staying asleep); only slot and renewal moments do. schtasks.exe cannot set Wake to run, so the tasks are registered through PowerShell's Register-ScheduledTask. The setup flow asks whether fixed slots may wake the machine (same prompt as macOS), awewarm config set <id> --no-wake opts a connection out, and install/uninstall/refresh/self-heal keep the task set in sync with the config.
Always-on servers (Linux)
No wake machinery exists or is needed on a machine that never sleeps — awewarm scheduler install sets up the systemd user timer directly (tick every minute; Persistent=true fires a missed tick at boot). Copy config.json and secrets.json over (or re-run init), and note that delegated accounts warm on the server with or without their CLI installed (native HTTPS fallback). loginctl enable-linger $USER first on headless/SSH accounts. Linux simply cannot wake a suspended machine: the setup flow never asks, connections default to wakeWhenAsleep: false, and missed slots catch up within their catch-up windows once the machine wakes.
Remote Server — your own box, or a shared hub
A lid-closed laptop on battery eventually enters standby, where no scheduled wake can reach it — and an off machine fires nothing at all. For around-the-clock warmth regardless of power state, delegate connections to an always-on machine (VPS, NAS, Raspberry Pi) — awewarm serve for a box of your own, or awewarm-hub serve shared with a team, a family, or a community. Subscriptions delegate their API key; CLI accounts (Claude Code / Codex logins) delegate their login credential the same way — the server runs the CLI with the credential injected (Claude via CLAUDE_CODE_OAUTH_TOKEN, Codex via a per-connection CODEX_HOME sandbox) — or, with no CLI installed there, fires the CLI's own backend protocol over HTTPS with the same credential, so the server needs nothing installed. The local login stays the source of truth: your machine re-reads it on every sync and re-pushes when it rotates.
The two flavors at a glance:
Solo — awewarm serve |
Hub — awewarm-hub serve |
|
|---|---|---|
| Who runs the box | you | one operator; everyone else pairs as a user |
| Who may pair | just you — the first token to reach an unclaimed server claims it | many users, one-time invites (awi_...) the operator mints |
| Software on the box | this package (pip install awewarm) |
the separate awewarm-hub package (pip install awewarm-hub; same MPL-2.0) |
| Pair from your machine | awewarm remote connect <url> |
awewarm remote connect <url> --invite awi_... |
| Trust | your keys, your box | every user's API keys and login credentials pass through the operator's RAM — hub users must trust the operator and root |
| The right pick when… | you have, or can cheaply rent, any always-on box and want it fully yours | you have no always-on box of your own, or want to share one with several people |
Once paired, the two flavors are identical from your laptop: the same delegation commands, the same status --remote view, the same takeback with --local.
Both hold no secrets on disk by default. The pairing token and your API keys stay in the local secrets.json and are pushed over the wire; the server keeps them in RAM only. Restart it and the local machine re-claims and re-pushes automatically the next time it is online. A slot that came due while its key was missing is held, not failed — it still fires inside the catch-up window once the key returns, exactly like a machine that was asleep; past the window it is recorded as skipped.
If your machine is rarely online, that restart dependency may cost you warm-ups. Per connection you may opt into server-side key storage instead — not recommended (the key lands in plaintext on the server's disk, 0600, readable by whoever can read that box), and gated behind a confirmation on every command that would start it:
awewarm config set <id> --persist-key on # asks; decline with a plain Enter
awewarm config set <id> --persist-key off # asks too: the server deletes it, and a
# restart while this machine is offline
# holds warm-ups again until you're back
On a hub the operator must also allow it (awewarm-hub config --persist-keys on, default off). awewarm status <id> shows which side of the line that connection is on (key: server RAM only / key: stored on the server); the summary stays quiet unless a key is missing.
Set up the server (once)
Solo — install this package on the box and run it:
ssh my-server
pip3 install awewarm
awewarm serve # listens on 127.0.0.1:8790, data at ~/.awewarm-server
awewarm serve --data-dir /data/awewarm # ...or keep config/state/log somewhere else
Keep it running with a systemd user unit (~/.config/systemd/user/awewarm.service):
[Unit]
Description=awewarm serve
After=network-online.target
[Service]
ExecStart=awewarm serve --data-dir %h/awewarm-server
Restart=on-failure
[Install]
WantedBy=default.target
systemctl --user enable --now awewarm (with loginctl enable-linger $USER on headless boxes). Expose it through a cloudflared tunnel — free TLS, no open inbound ports, your origin IP stays hidden:
cloudflared tunnel create awewarm
cloudflared tunnel route dns awewarm warm.example.com
cloudflared tunnel run --url http://127.0.0.1:8790 awewarm
Solo pairing safety: an unclaimed server trusts the first token that reaches it — anyone who finds the URL before you connect could claim it instead (your own connect then fails loudly with 403). Keep the URL private, connect promptly after starting serve, or pin the token ahead of time with awewarm serve --token awt_....
Hub — the operator installs the separate package on the box and hands out invite codes:
pip3 install awewarm-hub
awewarm-hub serve # same ~/.awewarm-server data dir, now multi-tenant
awewarm-hub invite --name alice # prints awi_... (one use, 7 d)
Admin lives there too (awewarm-hub status / list / invite revoke|restore|rename / config); an existing ~/.awewarm-server data dir carries over unchanged. The old spellings (awewarm serve --hub, awewarm hub ...) now die with a tombstone naming their replacement. No always-on box of your own? The project's developer runs a community hub at https://awewarm.wehuman.top (invite-based — request a code at peng@wehuman.top); docs/community-hub/ is a step-by-step user tutorial that starts from installing awewarm and setting up your first connection (中文版).
Delegate from the laptop
Both flavors pair over https (e.g. via the cloudflared tunnel): remote connect asks for confirmation before sending the token and any API keys over plain http:// to a non-local host.
awewarm remote connect https://warm.example.com # solo: token generated + stored locally, server claimed
awewarm remote connect https://warm.example.com --invite awi_... # hub: burn an invite for a personal token
awewarm config set glm --remote # the server takes over this connection
awewarm config set glm --duplicate --remote # ...or keep glm local and delegate a copy of it
awewarm config set codex --remote # an account too: asks first, then pushes its login credential
awewarm status # merged view: local + delegated truth
awewarm status --remote # delegated only, plus the server health line (version/uptime/last tick)
awewarm status --local # locally scheduled connections only
Delegating an account (a Claude Code or Codex login) works like delegating a key, with one extra gate: the credential is account-wide — it unlocks every subscription under that login, not one scoped plan — so the command shows the target server's URL and asks before reading the login and pushing it (non-interactive shells pass --yes). On the server the credential lives in RAM like any key and reaches only the CLI subprocess: Claude Code gets CLAUDE_CODE_OAUTH_TOKEN, Codex gets a private CODEX_HOME directory whose auth.json is re-written from your push before every fire — any token refresh the server-side CLI performs is discarded, because rotation flows one way, local → server. awewarm remote push re-reads the login each time, and the background sync (at most twice an hour) re-pushes automatically when the credential's fingerprint no longer matches. Takeback (--local) and status <id> (which shows credential: server RAM only plus the fingerprint) behave exactly like subscriptions'. The server box needs nothing installed: with the CLI on its PATH it runs its own resolved copy; without one the account warms natively over HTTPS — the same backend protocol the CLI itself speaks, fired with the pushed credential (transport.baseUrl overrides the endpoint, activation.model the model — the codex default is gpt-5.6-luna, the claude default claude-sonnet-5). status <id> names the mode. Native firing never refreshes the token: a 401 means the pushed credential went stale — use the CLI once on the local machine and the sync re-pushes it.
Moving to a new machine? One command carries everything — connections, keys, schedule state, and the machine-id that makes the hub treat the new box as the same machine (no new pairing slot):
awewarm config backup # ./awewarm-backup-<ts>.tar.gz (0600, plaintext secrets inside)
awewarm config backup --output /safe/path.tar.gz
awewarm config restore /safe/path.tar.gz # on the new machine; --force overwrites, refuses collision otherwise
The archive holds your API keys and pairing token in plaintext — store it somewhere safe and encrypt it yourself for transit (e.g. gpg). If it contains --persist-key on connections, restore asks before re-establishing key storage on their servers. Afterwards run awewarm scheduler install on the new machine and verify with awewarm status.
--remote only lands after the server accepted the push, so a connection is never left with nobody ticking it. --duplicate copies a connection under a fresh id (glm-copy) — the API key is re-stored under the new id, runtime state starts blank — and with --remote the copy is delegated and the original disabled, so one subscription is never ticked twice. Everything keeps working on delegated connections: config set pushes schedule edits automatically (offline edits stay local and pending; awewarm remote push reconciles later), awewarm run glm fires on the server and reports back — and, same as locally, a successful manual run clears an auto-disabled ladder — and awewarm config set glm --local takes a connection back — server state is pulled first so local scheduling resumes where the server left off. awewarm remote disconnect refuses while anything is still delegated, then forgets the server and releases its claim (another machine can pair immediately); the pairing token stays in secrets.json, so reconnecting later is instant even against a server that kept the old claim. Fixed times run in the delegating machine's timezone (it travels with the push; machines whose zone has no IANA name, e.g. Windows, push a fixed UTC±HH:MM offset instead); wake-from-sleep does not apply on a server that never sleeps.
Security
Local mode: your API keys never leave your machine (secrets.json, 0600). Delegating hands a key to that server's RAM — solo is your own box; a hub means trusting its operator (and root). An account's login credential is broader than a scoped API key (it covers every subscription under that login), so delegating one adds an explicit confirmation naming the server, and its --persist-key warning is worded harder. Every other ordinary path is closed by design: no secrets on the server's disk (the one exception is the opt-in --persist-key, plaintext keys.json 0600, discouraged, confirmed at every enabling command), none in its logs, none readable back over the API, tenants invisible to one another. Delegate a dedicated, revocable key — and see the hub README → Security for the full picture.
Config
Users never hand-edit config; init / config add generate it at ~/.config/awewarm/config.json (state at ~/.local/state/awewarm/state.json). The shape, for reference:
{
"version": 3,
"proxyUrl": null,
"settings": {
"catchupMinutes": 30,
"catchupAttempts": 5,
"degradeAfterNodes": 3,
"wakeWhenAsleep": false,
"prompt": "Reply with exactly: ok",
"maxTokens": 4,
"schedule": {
"mode": "fixed",
"times": ["06:35"],
"days": "weekday",
"skipIfActivatedMinutes": 30,
"windowMinutes": 300,
"graceSeconds": 75,
"jitterSeconds": 30
}
},
"connections": {
"local": {
"settings": {
"wakeWhenAsleep": true,
"schedule": {"times": ["06:35"], "days": "weekday"}
},
"claude-code": {
"label": "Claude Code",
"cli": "/usr/local/bin/claude",
"model": "haiku",
"schedule": {"times": ["06:35"], "mode": "fixed"}
}
},
"remote": {
"settings": {
"schedule": {"times": ["08:00"], "days": "every-day"}
},
"glm": {
"label": "glm",
"url": "https://open.bigmodel.cn/api/coding/paas/v4",
"protocol": "openai-chat",
"apiKey": "file:glm",
"model": "GLM-5-Turbo"
}
}
},
"remote": {
"url": "https://warm.example.com",
"tokenRef": "file:remote:token"
}
}
A connection with url + apiKey is a subscription; one with cli is a local account. apiKey is file:<id> — the pasted key lives in ~/.config/awewarm/secrets.json (chmod 600), readable by the background scheduler. An account connection may also carry authHome — the CLI config dir it logs in from (an aweswitch account dir); see Multiple logins of one provider. A connection nested under connections.remote is ticked by the paired awewarm serve server (whose URL and token ref live in the top-level remote block); the group alone says so — no per-connection location field. The window duration (windowMinutes) is a schedule field inherited through the layers (below); a confirmed window unlocks interval renewal — it only takes effect while the schedule mode is interval, fixed connections merely record it. "hide": true keeps a connection out of status listings — it still warms on its schedule, and status <id> still shows it.
Settings are layered three deep — every level carries the same knobs and a schedule block, and each field resolves through them. The split is semantic: the schedule block answers when a connection fires (mode, times, days, skipIfActivatedMinutes, windowMinutes, graceSeconds, jitterSeconds); the knobs answer how an activation behaves — catchupMinutes/catchupAttempts/degradeAfterNodes (catch-up and the degrade ladder), wakeWhenAsleep (may fixed slots wake a sleeping machine), and prompt/maxTokens (the warm-up request's prompt and token cap). Setting windowMinutes on a layer vouches for that duration for every connection under it without its own record, unlocking interval; a CLI account's builtin window is never overridden by a layer:
- global — the top-level
settings: knobs every connection inherits, plus default schedule fields (the saved block always names itsmode, so the file shows whether the default isfixedorinterval). - connections.local / connections.remote — per-location overrides nested under each location group.
- profile — a connection's own overrides, written directly on it (
scheduleplus any knob, nosettingswrapper — the layers need one to share a dict with the connection ids, a connection does not); they always win, and--inherit-scheduledrops them back to the layers above. One field is always present here:mode. Every saved connection names its mode (fixedorinterval) even when it matches the layers, so the file shows it without runningstatus— and, deliberately, changing a layer's mode never re-modes an existing connection; switch each one explicitly.
One deliberate asymmetry: a delegated (remote) connection never follows the global schedule — it describes this machine's day. Remote connections resolve their schedule from their own settings and connections.remote.settings only (knobs still inherit globally) — with one exception: windowMinutes is a fact about the plan, not about any machine's day, so the global block's window duration reaches delegated connections too. An inherited interval mode never breaks a connection whose window is unverified — such connections stay on fixed until their window is recorded. Delegating a connection freezes its then-effective schedule as its own settings, so handover never changes what fires. Configs saved by slightly older builds (a knob-position windowMinutes, a schedule-position wakeWhenAsleep) fold into the current positions on first load and are never written back.
Network egress — direct by default
Every request awewarm itself makes — the hub control channel, subscription warm-ups, update checks — goes direct, ignoring http_proxy/https_proxy/all_proxy from the environment. A machine-wide proxy is ambient state (usually set for git/npm or a desktop Clash) that must not capture the scheduler's traffic: a warm-up system sells determinism, and routing it through a proxy nobody asked for couples it to that proxy's failures. Direct-by-default also makes the interactive CLI and the background tick (which runs without your shell environment) behave identically. A network that genuinely requires egress through a proxy opts in explicitly with the top-level proxyUrl:
awewarm config proxy http://127.0.0.1:7890 # route every egress awewarm triggers through it
awewarm config proxy # show the current egress
awewarm config proxy none # back to direct
One switch, one route: the configured proxyUrl carries awewarm's own requests, and the codex/claude subprocesses awewarm starts get it as their http_proxy/https_proxy/HTTP_PROXY/HTTPS_PROXY — the explicit URL wins over ambient proxy variables, while the rest of the child environment (delegated logins, authHome pointers, PATH, the codex sandbox) rides along unchanged. No all_proxy and no NO_PROXY are injected: CLI traffic speaks HTTP(S), and one switch means one route. Unset, nothing changes — awewarm's own requests stay direct with the environment ignored, and the CLIs keep inheriting the ambient environment. Network-level failures name the egress they used in the error, so a broken route says which side to fix. A serve (or hub) box follows the same rule as a client: its warm-up fires — subscription, native, and CLI-mode delegation alike — read the proxyUrl of the config on that machine — run awewarm config proxy there — and an absent or unreadable config means direct.
Multiple logins of one provider — authHome
Several Claude Code or Codex logins can warm side by side, each on its own schedule. Official accounts managed by aweswitch (each lives in a private CLI config dir under ~/.config/aweswitch/accounts/<provider>/<name>/) show up in awewarm discover, awewarm config add, and awewarm init as their own entries — Codex (cxo-heck), Codex (cxo-peng), Claude Code (work) — one per account dir that holds a login file. Adding one stores that dir on the connection as authHome, and from then on:
- locally, the CLI subprocess is pointed at the dir: codex gets
CODEX_HOME, claude getsCLAUDE_CONFIG_DIRplusCLAUDE_CODE_DONT_USE_KEYCHAIN=1(the flag makes Claude Code read the dir's file-based login instead of the machine-wide Keychain — the same env aweswitch itself launches with). Each connection fires as its own account; - delegated, the login is read from that dir for the push, and the background sync's fingerprint-drift re-push stays per connection — each account rotates independently. The server side is unchanged: its per-connection sandbox (or native fire) already keys off the pushed credential.
A connection without authHome reads the machine's default login (~/.codex, the Keychain or ~/.claude) — the old single-account behavior is exactly the absent-field case.
Commands
awewarm init # interactive onboarding: scan accounts, pick schedules, install scheduler
awewarm discover # read-only scan of local CLIs and logins
awewarm config add # add a connection: a detected account or a subscription endpoint
awewarm config set <id> [flags] # show or change settings: --times, --days, --mode, --on/--off, --hide/--show,
# --anchor, --next/--move-slot/--clear-next, --window, --api-key, --wake/--no-wake, --remote/--local,
# --catchup-minutes, --catchup-attempts, --degrade-after-nodes,
# --inherit-schedule (drop own schedule overrides, follow the layers)
awewarm config settings [scope] [flags] # show or change the settings layers: scope is global (default), local,
# or remote; flags: --catchup-*, --degrade-after-nodes, --window-minutes,
# --prompt, --max-tokens, --times, --days, --mode, --wake/--no-wake, --reset
awewarm config remove <id> # delete a connection, its state, and its stored API key
awewarm config show / edit # print the on-disk config / open it in $EDITOR (validated on exit)
awewarm config template # print the reference config shape (what hand-edits must match)
awewarm config path # config / state / log locations
awewarm config proxy [<url>|none] # show / set / clear the egress proxy for everything awewarm triggers
# (its own requests and the CLI subprocesses it starts)
awewarm status [<id>] [--json] # summary; one connection in detail; redacted machine-readable dump
awewarm status --remote / --local # delegated connections only (with the server health line) / locally scheduled only
awewarm run [--force] # fire every enabled connection now, ignoring the schedule (prompts; --force skips)
awewarm run <id> [--reset-due] # fire one connection now (schedule untouched unless --reset-due)
awewarm scheduler install [--wake] / uninstall # background scheduler (launchd / Task Scheduler / systemd); --wake also arms RTC wake-from-sleep
awewarm serve [--data-dir /data] [--token awt_...] # run the always-on server that ticks delegated connections
# one server, many invited users: the separate awewarm-hub package
awewarm remote connect <url> [--invite awi_...|--token awt_...]
# pair with a server: solo serve generates + stores a token;
# a hub takes --invite awi_... (or a saved --token)
awewarm remote push [<id>] # re-sync delegated connections to the server (config + keys)
awewarm remote disconnect # forget the server + release its claim (refuses while delegations exist)
awewarm self-update [--check] # upgrade to the latest PyPI release
Commands from pre-0.3 releases (add plan, times, enable, disable, verify, anchor, activate, remove, install, uninstall, inspect) still work as hidden aliases; they print their new spelling and will be removed in v1.0. remote status folded into status --remote (kept as a hidden alias the same way). awewarm update was removed outright in v0.5.0 — use awewarm self-update.
Self-Update
awewarm checks PyPI in the background — at most once a day, and never during scheduler ticks. When a newer release exists, interactive commands print a reminder to stderr.
awewarm self-update # upgrade to the latest release
awewarm self-update --check # show versions only
To disable the background check:
export AWEWARM_NO_UPDATE_CHECK=1
Development
pip install -e .
python3 -m unittest discover -s tests
awewarm -v says editable (with the git state) when running from this checkout; pip's recorded metadata freezes at pip install -e . time, so re-run it after a version bump to keep pip show in sync. awewarm self-update refuses on a checkout — pull and re-install instead.
See docs/CONTRIBUTING.md for the engineering doctrine and docs/CHANGELOG.md for release history.
Support
If awewarm saves your quota, consider supporting it:
- ⭐ Star the repo — it helps others find it.
- ☕ Ko-fi — buy me a coffee.
- 💬 WeChat — scan the QR code below.
awewarm is free and open source. Sponsors keep it maintained — thank you.
Awesome Ecosystem
awewarm is part of a growing family of "awesome" tools — CLI-first, local-first, and operable by AI agents.
CLI Tools
- aweskill — CLI-first skill package manager supporting 47+ AI coding agents.
- aweswitch — Agent profile switcher for Claude Code, Codex, and OpenCode.
- awerouter — Smart router that splits requests between Flash and Pro models using structural signals, cutting unnecessary model spend.
- aweshelf — Bookmark, categorize, and restore AI coding sessions; pairs with aweswitch to save profiles and launch with one command.
- aweshare — Share local Ollama/vLLM backends, domestic coding plans, or authorized OpenAI/Anthropic subscriptions through a self-hosted hub — a sharing economy for tokens.
- awewarm — Subscription window warmer that keeps AI coding-plan windows active, for local setups and through a remote hub server.
- awescholar — AI-agent-operable scientific literature discovery and curation.
Desktop Apps
- awedot — A floating orb at your screen edge keeps track of the current AI session: bookmark it in one click, resume anytime, and pair with aweswitch to pin the agent's config (e.g., relaunch with the GLM model).
Project Collections
- Awesome AI Meets Biology — A curated survey of AI applications in biology, bioinformatics, and biomedical research. Powered by awescholar.
- Awesome AI Virtual Tumor — A curated collection of state-of-the-art AI systems for virtual tumor modeling and simulation: static models, dynamic models, agents, benchmarks, and reviews.
Metadata
Release files for awewarm 0.7.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| awewarm-0.7.2.tar.gz | 327.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| awewarm-0.7.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 458.6 kB
Release files / awewarm-0.7.2.tar.gz
| Download URL | awewarm-0.7.2.tar.gz |
|---|---|
| Size | 327.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
18959afe400d67f8817198d7d7fcb09f74b2c1e633c8834e735f5f572a5e05c8
|
|
BLAKE2b-256 checksum How to use checksums |
b09ba13e2c9f5d8570187bb6be786570f6a3c5d0fa1458c351365362eebb61aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|
Release files / awewarm-0.7.2-py3-none-any.whl
| Download URL | awewarm-0.7.2-py3-none-any.whl |
|---|---|
| Size | 131.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8ce6d9f447535704cbebe4a589c8904b7fc38397d76a5542602fd761c1c5616c
|
|
BLAKE2b-256 checksum How to use checksums |
1fae3f9d7e8c466f2d8a52aa832f242b6f5e7ff71b07ba2ceb9ab08f6e21f83b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.15
|